CREATE TABLE file ( id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT NOT NULL UNIQUE ); CREATE TABLE card ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- deriveUid(source) uid BLOB NOT NULL UNIQUE, -- e.g., jsonb('{"type": "page", "url": "http://example.com/"}') source BLOB NOT NULL UNIQUE, -- TODO move to detail or remove entirely? url TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_card_created_at ON card(created_at DESC); CREATE TABLE detail ( id INTEGER PRIMARY KEY AUTOINCREMENT, card_id INTEGER NOT NULL, image_file_id INTEGER NOT NULL, title TEXT, -- e.g., jsonb('{"crawl_id": 42}') metadata BLOB, FOREIGN KEY(card_id) REFERENCES card(id) ON DELETE RESTRICT, FOREIGN KEY(image_file_id) REFERENCES file(id) ON DELETE RESTRICT ); CREATE INDEX idx_detail_card_id ON detail(card_id); CREATE TABLE thumbnail ( id INTEGER PRIMARY KEY AUTOINCREMENT, detail_id INTEGER NOT NULL UNIQUE, file_id INTEGER NOT NULL, FOREIGN KEY(detail_id) REFERENCES detail(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES file(id) ON DELETE RESTRICT ); CREATE TABLE tile ( id INTEGER PRIMARY KEY AUTOINCREMENT, detail_id INTEGER NOT NULL, tile_index INTEGER NOT NULL, file_id INTEGER NOT NULL, FOREIGN KEY(detail_id) REFERENCES detail(id) ON DELETE CASCADE, FOREIGN KEY(file_id) REFERENCES file(id) ON DELETE RESTRICT, UNIQUE(detail_id, tile_index) ); CREATE INDEX idx_tile_detail_id ON tile(detail_id); CREATE INDEX idx_tile_file_id ON tile(file_id); CREATE TABLE crawl ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE, har_file_id INTEGER NOT NULL, -- e.g., jsonb('{"crawled_at": "2025-10-16T05:17:00Z"}') metadata BLOB, FOREIGN KEY(har_file_id) REFERENCES file(id) ON DELETE SET NULL ); CREATE TABLE collect_job ( id INTEGER PRIMARY KEY AUTOINCREMENT, request BLOB NOT NULL UNIQUE, domain TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), claimed_at TEXT, claimed_by TEXT ); CREATE INDEX idx_collect_job_domain ON collect_job(domain); CREATE INDEX idx_collect_job_claimed_at ON collect_job(claimed_at) WHERE claimed_at IS NULL; CREATE TABLE postprocess_job ( id INTEGER PRIMARY KEY AUTOINCREMENT, detail_id INTEGER NOT NULL UNIQUE, created_at TEXT NOT NULL DEFAULT (datetime('now')), claimed_at TEXT, claimed_by TEXT, FOREIGN KEY(detail_id) REFERENCES detail(id) ON DELETE RESTRICT ); CREATE INDEX idx_postprocess_job_detail_id ON postprocess_job(detail_id); CREATE INDEX idx_postprocess_job_claimed_at ON postprocess_job(claimed_at) WHERE claimed_at IS NULL; CREATE TABLE domain_token_lease ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL, leased_until TEXT NOT NULL ); CREATE INDEX idx_domain_token_lease_domain ON domain_token_lease(domain); CREATE INDEX idx_domain_token_lease_until ON domain_token_lease(leased_until); CREATE TABLE deck ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE card_set ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- e.g., jsonb('[1, 2, 3]') card_ids BLOB NOT NULL UNIQUE ); CREATE TABLE revision ( id INTEGER PRIMARY KEY AUTOINCREMENT, deck_id INTEGER NOT NULL, card_set_id INTEGER NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY(deck_id) REFERENCES deck(id) ON DELETE CASCADE, FOREIGN KEY(card_set_id) REFERENCES card_set(id) ON DELETE RESTRICT ); CREATE TABLE workspace ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE operation ( -- id is used as a (noncontiguous) sequence number. id INTEGER PRIMARY KEY AUTOINCREMENT, workspace_id INTEGER NOT NULL, -- e.g., jsonb('["moveCard", [0, 1], [1, 0]]') command BLOB NOT NULL, client TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE ); CREATE INDEX idx_operation_workspace_id ON operation(workspace_id);