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
|
|
@ -23,11 +23,11 @@ func NewFaveStore(db *sql.DB) *FaveStore {
|
|||
}
|
||||
|
||||
// Create inserts a new fave and returns it with its ID populated.
|
||||
func (s *FaveStore) Create(userID int64, description, url, imagePath, privacy string) (*model.Fave, error) {
|
||||
func (s *FaveStore) Create(userID int64, description, url, imagePath, notes, privacy string) (*model.Fave, error) {
|
||||
result, err := s.db.Exec(
|
||||
`INSERT INTO faves (user_id, description, url, image_path, privacy)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
userID, description, url, imagePath, privacy,
|
||||
`INSERT INTO faves (user_id, description, url, image_path, notes, privacy)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
userID, description, url, imagePath, notes, privacy,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("insert fave: %w", err)
|
||||
|
|
@ -42,13 +42,13 @@ func (s *FaveStore) GetByID(id int64) (*model.Fave, error) {
|
|||
f := &model.Fave{}
|
||||
var createdAt, updatedAt string
|
||||
err := s.db.QueryRow(
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.notes, f.privacy,
|
||||
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
|
||||
FROM faves f
|
||||
JOIN users u ON u.id = f.user_id
|
||||
WHERE f.id = ?`, id,
|
||||
).Scan(
|
||||
&f.ID, &f.UserID, &f.Description, &f.URL, &f.ImagePath, &f.Privacy,
|
||||
&f.ID, &f.UserID, &f.Description, &f.URL, &f.ImagePath, &f.Notes, &f.Privacy,
|
||||
&createdAt, &updatedAt, &f.Username, &f.DisplayName,
|
||||
)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
|
@ -63,12 +63,12 @@ func (s *FaveStore) GetByID(id int64) (*model.Fave, error) {
|
|||
}
|
||||
|
||||
// Update modifies an existing fave's fields.
|
||||
func (s *FaveStore) Update(id int64, description, url, imagePath, privacy string) error {
|
||||
func (s *FaveStore) Update(id int64, description, url, imagePath, notes, privacy string) error {
|
||||
_, err := s.db.Exec(
|
||||
`UPDATE faves SET description = ?, url = ?, image_path = ?, privacy = ?,
|
||||
`UPDATE faves SET description = ?, url = ?, image_path = ?, notes = ?, privacy = ?,
|
||||
updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
|
||||
WHERE id = ?`,
|
||||
description, url, imagePath, privacy, id,
|
||||
description, url, imagePath, notes, privacy, id,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ func (s *FaveStore) ListByUser(userID int64, limit, offset int) ([]*model.Fave,
|
|||
}
|
||||
|
||||
rows, err := s.db.Query(
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.notes, f.privacy,
|
||||
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
|
||||
FROM faves f
|
||||
JOIN users u ON u.id = f.user_id
|
||||
|
|
@ -125,7 +125,7 @@ func (s *FaveStore) ListPublicByUser(userID int64, limit, offset int) ([]*model.
|
|||
}
|
||||
|
||||
rows, err := s.db.Query(
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.notes, f.privacy,
|
||||
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
|
||||
FROM faves f
|
||||
JOIN users u ON u.id = f.user_id
|
||||
|
|
@ -152,7 +152,7 @@ func (s *FaveStore) ListPublic(limit, offset int) ([]*model.Fave, int, error) {
|
|||
}
|
||||
|
||||
rows, err := s.db.Query(
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.notes, f.privacy,
|
||||
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
|
||||
FROM faves f
|
||||
JOIN users u ON u.id = f.user_id
|
||||
|
|
@ -184,7 +184,7 @@ func (s *FaveStore) ListByTag(tagName string, limit, offset int) ([]*model.Fave,
|
|||
}
|
||||
|
||||
rows, err := s.db.Query(
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
|
||||
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.notes, f.privacy,
|
||||
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
|
||||
FROM faves f
|
||||
JOIN users u ON u.id = f.user_id
|
||||
|
|
@ -261,7 +261,7 @@ func (s *FaveStore) scanFaves(rows *sql.Rows) ([]*model.Fave, error) {
|
|||
f := &model.Fave{}
|
||||
var createdAt, updatedAt string
|
||||
err := rows.Scan(
|
||||
&f.ID, &f.UserID, &f.Description, &f.URL, &f.ImagePath, &f.Privacy,
|
||||
&f.ID, &f.UserID, &f.Description, &f.URL, &f.ImagePath, &f.Notes, &f.Privacy,
|
||||
&createdAt, &updatedAt, &f.Username, &f.DisplayName,
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue