-- One row per distinct work, shared by every bookmark that tracks it -- (ADR-0003). Keyed (site, series_id), the pair a bookmark key decomposes -- into. title/series_url/cover are written once, at creation, and never -- again: client-supplied values are ignored once the row exists and the -- poller is the only party that may change them. kind and the latest-chapter -- fields are last-write-wins like the bookmark's own fields. CREATE TABLE series ( site text NOT NULL, series_id text NOT NULL, title text NOT NULL DEFAULT '', series_url text NOT NULL DEFAULT '', cover text NOT NULL DEFAULT '', kind text NOT NULL DEFAULT 'manga', latest_chapter text NOT NULL DEFAULT '', latest_chapter_num double precision, -- When the server last polled this series, unix ms; 0 means never, and sorts -- first so a new bookmark is picked up on the next tick with no special case. latest_checked_at bigint NOT NULL DEFAULT 0, PRIMARY KEY (site, series_id) ); -- Backfill from today's rows. The bookmark key's uniqueness makes -- (site, series_id) unique in practice; DISTINCT is belt and braces. INSERT INTO series (site, series_id, title, series_url, cover, kind, latest_chapter, latest_chapter_num, latest_checked_at) SELECT DISTINCT site, series_id, title, series_url, cover, kind, latest_chapter, latest_chapter_num, latest_checked_at FROM bookmarks; -- The bookmark keeps only what differs between readers (ADR-0003): progress, -- favourite, lifecycle bucket. The dropped columns now live on series. ALTER TABLE bookmarks DROP COLUMN title, DROP COLUMN series_url, DROP COLUMN cover, DROP COLUMN kind, DROP COLUMN latest_chapter, DROP COLUMN latest_chapter_num, DROP COLUMN latest_checked_at; -- A bookmark may not point at a series that does not exist. No cascade: a -- series outlives its last bookmark, and deleting one is not a store operation. ALTER TABLE bookmarks ADD CONSTRAINT bookmarks_series_fk FOREIGN KEY (site, series_id) REFERENCES series (site, series_id);