feat: add notes field to favorites and enhance OG meta tags
Add an optional long-form "notes" text field to each favorite for reviews, thoughts, or extended descriptions. The field is stored in SQLite via a new migration (002_add_fave_notes.sql) and propagated through the entire stack: - Model: Notes field on Fave struct - Store: All SQL queries (Create, GetByID, Update, list methods, scanFaves) updated with notes column - Web handlers: Read/write notes in create, edit, update forms - API handlers: Notes in create, update, get, import request/response - Export: Notes included in both JSON and CSV exports - Import: Notes parsed from both JSON and CSV imports - Feed: Notes used as Atom feed item summary when present - Form template: New textarea between URL and image fields - Detail template: Display notes, enhanced og:description with cascade: notes (truncated) → URL → generic fallback text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8f3aa6f7e
commit
485d01ce45
14 changed files with 151 additions and 71 deletions
|
|
@ -364,7 +364,7 @@ func TestEditFaveNotOwner(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
userA, _ := h.deps.Users.Create("usera", "pass123", "user")
|
||||
fave, _ := h.deps.Faves.Create(userA.ID, "A's fave", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(userA.ID, "A's fave", "", "", "", "public")
|
||||
|
||||
cookieB := loginUser(t, h, "userb", "pass123", "user")
|
||||
|
||||
|
|
@ -382,7 +382,7 @@ func TestDeleteFaveHTMX(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
user, _ := h.deps.Users.Create("testuser", "pass123", "user")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Delete me", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Delete me", "", "", "", "public")
|
||||
token, _ := h.deps.Sessions.Create(user.ID)
|
||||
cookie := &http.Cookie{Name: "session", Value: token}
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ func TestDeleteFaveNotOwner(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
userA, _ := h.deps.Users.Create("usera", "pass123", "user")
|
||||
fave, _ := h.deps.Faves.Create(userA.ID, "A's fave", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(userA.ID, "A's fave", "", "", "", "public")
|
||||
|
||||
cookieB := loginUser(t, h, "userb", "pass123", "user")
|
||||
|
||||
|
|
@ -623,7 +623,7 @@ func TestAdminTags(t *testing.T) {
|
|||
|
||||
// Create a tag via a fave.
|
||||
admin, _ := h.deps.Users.GetByUsername("admin")
|
||||
fave, _ := h.deps.Faves.Create(admin.ID, "Test", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(admin.ID, "Test", "", "", "", "public")
|
||||
h.deps.Tags.SetFaveTags(fave.ID, []string{"testmerke"})
|
||||
|
||||
req := httptest.NewRequest("GET", "/admin/tags", nil)
|
||||
|
|
@ -646,7 +646,7 @@ func TestUserFeed(t *testing.T) {
|
|||
|
||||
user, _ := h.deps.Users.Create("feeduser", "pass123", "user")
|
||||
h.deps.Users.UpdateProfile(user.ID, "Feed User", "", "public", "public")
|
||||
h.deps.Faves.Create(user.ID, "User fave", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "User fave", "", "", "", "public")
|
||||
|
||||
req := httptest.NewRequest("GET", "/u/feeduser/feed.xml", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
|
@ -668,8 +668,8 @@ func TestFeedExcludesPrivate(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
user, _ := h.deps.Users.Create("testuser", "pass123", "user")
|
||||
h.deps.Faves.Create(user.ID, "Public fave", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "Secret fave", "", "", "private")
|
||||
h.deps.Faves.Create(user.ID, "Public fave", "", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "Secret fave", "", "", "", "private")
|
||||
|
||||
req := httptest.NewRequest("GET", "/feed.xml", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
|
@ -688,7 +688,7 @@ func TestTagFeed(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
user, _ := h.deps.Users.Create("testuser", "pass123", "user")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Tagged fave", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Tagged fave", "", "", "", "public")
|
||||
h.deps.Tags.SetFaveTags(fave.ID, []string{"golang"})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tags/golang/feed.xml", nil)
|
||||
|
|
@ -725,7 +725,7 @@ func TestExportJSON(t *testing.T) {
|
|||
cookie := loginUser(t, h, "testuser", "pass123", "user")
|
||||
|
||||
user, _ := h.deps.Users.GetByUsername("testuser")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Export me", "https://example.com", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Export me", "https://example.com", "", "", "public")
|
||||
h.deps.Tags.SetFaveTags(fave.ID, []string{"test"})
|
||||
|
||||
req := httptest.NewRequest("GET", "/export/json", nil)
|
||||
|
|
@ -759,7 +759,7 @@ func TestExportCSV(t *testing.T) {
|
|||
cookie := loginUser(t, h, "testuser", "pass123", "user")
|
||||
|
||||
user, _ := h.deps.Users.GetByUsername("testuser")
|
||||
h.deps.Faves.Create(user.ID, "CSV fave", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "CSV fave", "", "", "", "public")
|
||||
|
||||
req := httptest.NewRequest("GET", "/export/csv", nil)
|
||||
req.AddCookie(cookie)
|
||||
|
|
@ -871,7 +871,7 @@ func TestProfileOwnerSeesPrivateFaves(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
user, _ := h.deps.Users.Create("owner", "pass123", "user")
|
||||
h.deps.Faves.Create(user.ID, "Private fave", "", "", "private")
|
||||
h.deps.Faves.Create(user.ID, "Private fave", "", "", "", "private")
|
||||
token, _ := h.deps.Sessions.Create(user.ID)
|
||||
cookie := &http.Cookie{Name: "session", Value: token}
|
||||
|
||||
|
|
@ -893,8 +893,8 @@ func TestProfileVisitorCannotSeePrivateFaves(t *testing.T) {
|
|||
|
||||
user, _ := h.deps.Users.Create("owner", "pass123", "user")
|
||||
h.deps.Users.UpdateProfile(user.ID, "Owner", "", "public", "public")
|
||||
h.deps.Faves.Create(user.ID, "Only public", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "Hidden secret", "", "", "private")
|
||||
h.deps.Faves.Create(user.ID, "Only public", "", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "Hidden secret", "", "", "", "private")
|
||||
|
||||
req := httptest.NewRequest("GET", "/u/owner", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
|
@ -915,7 +915,7 @@ func TestTagBrowse(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
user, _ := h.deps.Users.Create("testuser", "pass123", "user")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Tagged", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Tagged", "", "", "", "public")
|
||||
h.deps.Tags.SetFaveTags(fave.ID, []string{"golang"})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tags/golang", nil)
|
||||
|
|
@ -949,7 +949,7 @@ func TestHomePageAuthenticated(t *testing.T) {
|
|||
cookie := loginUser(t, h, "testuser", "pass123", "user")
|
||||
|
||||
user, _ := h.deps.Users.GetByUsername("testuser")
|
||||
h.deps.Faves.Create(user.ID, "Home fave", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "Home fave", "", "", "", "public")
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.AddCookie(cookie)
|
||||
|
|
@ -1074,7 +1074,7 @@ func TestTagSuggestionsNoInlineHandlers(t *testing.T) {
|
|||
h, mux := testServer(t)
|
||||
|
||||
user, _ := h.deps.Users.Create("testuser", "pass123", "user")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Test", "", "", "public")
|
||||
fave, _ := h.deps.Faves.Create(user.ID, "Test", "", "", "", "public")
|
||||
h.deps.Tags.SetFaveTags(fave.ID, []string{"golang", "goroutines"})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tags/search?q=go", nil)
|
||||
|
|
@ -1117,7 +1117,7 @@ func TestDisplayNameFallbackToUsername(t *testing.T) {
|
|||
|
||||
// Create a user WITHOUT a display name and a public fave.
|
||||
user, _ := h.deps.Users.GetByUsername("testuser")
|
||||
h.deps.Faves.Create(user.ID, "Test fave", "", "", "public")
|
||||
h.deps.Faves.Create(user.ID, "Test fave", "", "", "", "public")
|
||||
|
||||
// The home page shows "av <display_name>" — with no display name set,
|
||||
// it should fall back to the username.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue