a48aba67b8
Series becomes a shared row keyed (site, series_id) owning title, cover, canonical URL, kind, latest chapter and last-checked time (ADR-0003). A bookmark keeps only progress, favourite, lifecycle bucket, updated_at. Store.Upsert decomposes one flat body across both tables in one transaction: client title/series_url/cover apply only when the series row is new, then only the poll may change them (security boundary — the row is shared and the values are scraped page content). Reads join series back in, so GET/PUT emit and accept exactly the flat field set they did before (ADR-0004), asserted by TestFlatWireFieldSet. The poller walks Series instead of Bookmarks: one fetch per shared series per due cycle, due queue ordered reader_count DESC then latest_checked_at ASC, orphaned series never due and never deleted, row still stamped before the fetch. Batch/stagger/interval unchanged. Migration 0002 backfills series from existing bookmarks; verified by TestMigration0002BackfillsExistingBookmarks.
46 lines
2.1 KiB
SQL
46 lines
2.1 KiB
SQL
-- 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);
|