feat(unlinked-books): dismiss (ignore) or delete queue entries
Release / build-and-push (push) Successful in 3m15s

Unidentified sync files used to be stuck: every KOReader re-sync
re-queues them, and there was no way to say "this isn't a book" or "I
don't want this one on the server".

Ignore is the durable dismiss: a new ignored_at column hides the row
from the page list, dashboard badge, and per-device API while the sync
keeps refreshing its metadata (the upsert never clears the column).
A collapsible "Ignored" section lists dismissed rows with Restore and
Delete so a misclick is recoverable. Delete is the hard exit — the row
is gone, and honestly re-queued if the device syncs that file again
(the confirm dialog says so).

Actions are page-scoped SSR routes: htmx swaps in the re-rendered
section so counts and buckets can never drift from the DB; ownership
is row → device → user (admins may act on any row). Integration tests
pin the lifecycle (hide everywhere, survive the sync upsert, restore,
delete), foreign-user 403s, and the render surface.
This commit is contained in:
John O'Keefe committed 2026-10-26 14:58:50 -04:00
1 parent afbf1029a9
commit f6a73ab5aa
10 files changed
+1241 -375

No files matched your search

+6
View File
@@ -1496,6 +1496,12 @@ CREATE TABLE IF NOT EXISTS unlinked_books (
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Soft-dismiss for unlinked_books: ignored rows stay on the device's sync
-- queue (UpsertUnlinkedBook keeps refreshing their file metadata, and must
-- never clear this column) but are hidden from the page list, badge count,
-- and per-device API. Reversible via UnignoreUnlinkedBook.
ALTER TABLE unlinked_books ADD COLUMN IF NOT EXISTS ignored_at TIMESTAMP WITH TIME ZONE;
CREATE INDEX IF NOT EXISTS idx_unlinked_books_device ON unlinked_books(device_id);
CREATE INDEX IF NOT EXISTS idx_unlinked_books_content_id ON unlinked_books(content_id);
-- One queue row per (device, document): the sync ingest upserts on this pair.