feat(bookmarks): location identity, origin provenance, KOReader-style labels

- Drop UNIQUE(media_item_id,user_id,title): titles are display labels
  shared verbatim across clients; same-title bookmarks on different pages
  now coexist instead of 500ing (deleting over a tombstone no longer
  blocks future creates with that title)
- Add origin_source column recording the creating client (android/web/
  koreader), set once at insert, exposed in API responses
- Web reader auto-title mirrors KOReader's 'in <chapter>' convention,
  falling back to 'Bookmark'; adds bookmark rename in the drawer
This commit is contained in:
2026-09-09 08:17:43 -04:00
parent f7c4dfe2e8
commit 7b1c809ae3
9 changed files with 115 additions and 17 deletions
+12 -2
View File
@@ -1344,8 +1344,10 @@ CREATE TABLE IF NOT EXISTS media_bookmarks (
title VARCHAR(255) NOT NULL,
position VARCHAR(100), -- 'pdf:page:45', 'comic:page:12', 'chapter:3' for consistency
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(media_item_id, user_id, title)
created_at TIMESTAMPTZ DEFAULT NOW()
-- No UNIQUE(media_item_id, user_id, title): bookmarks are identified by
-- their location (dedup_key), titles are display labels shared verbatim
-- across clients (KOReader auto-labels repeat within a chapter).
);
CREATE INDEX IF NOT EXISTS idx_media_bookmarks_media ON media_bookmarks(media_item_id);
@@ -1386,6 +1388,14 @@ ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS epubcfi_location TEXT;
ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS chapter_reference INTEGER;
ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS deleted BOOLEAN DEFAULT FALSE;
ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
-- Provenance: the client that CREATED the bookmark (unlike
-- last_modified_source, which tracks the last writer). Set once at insert.
ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS origin_source VARCHAR(30);
-- Identity is the dedup_key (location), not the title; drop the legacy
-- unique-title constraint so same-title bookmarks on different pages can
-- coexist (re-creating over a tombstone with a changed position also
-- relied on this). Catalog-only change, safe to re-run.
ALTER TABLE media_bookmarks DROP CONSTRAINT IF EXISTS media_bookmarks_media_item_id_user_id_title_key;
CREATE UNIQUE INDEX IF NOT EXISTS idx_media_highlights_dedup
ON media_highlights (user_id, media_item_id, dedup_key)