feat(unlinked-books): dismiss (ignore) or delete queue entries
Release / build-and-push (push) Successful in 3m15s
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:
1 parent
afbf1029a9
commit
f6a73ab5aa
10 files changed
+1241
-375
No files matched your search
@@ -3701,12 +3701,12 @@ INSERT INTO unlinked_books (device_id, content_id, file_path, title, author, con
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *;
|
||||
|
||||
-- Get unlinked books for a device
|
||||
-- Get unlinked books for a device (ignored rows stay hidden)
|
||||
-- name: GetUnlinkedBooksByDevice :many
|
||||
SELECT ub.*, d.device_name, d.device_type
|
||||
FROM unlinked_books ub
|
||||
JOIN devices d ON ub.device_id = d.id
|
||||
WHERE ub.device_id = $1 AND ub.resolved = false
|
||||
WHERE ub.device_id = $1 AND ub.resolved = false AND ub.ignored_at IS NULL
|
||||
ORDER BY ub.last_seen_at DESC;
|
||||
|
||||
-- Get unlinked book by ContentId
|
||||
@@ -3724,11 +3724,11 @@ SET
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- Count unlinked books for a device
|
||||
-- Count unlinked books for a device (ignored rows stay hidden)
|
||||
-- name: CountUnlinkedBooks :one
|
||||
SELECT COUNT(*) as count
|
||||
FROM unlinked_books
|
||||
WHERE device_id = $1 AND resolved = false;
|
||||
WHERE device_id = $1 AND resolved = false AND ignored_at IS NULL;
|
||||
|
||||
-- Link unlinked book to media item
|
||||
-- name: LinkUnlinkedBook :one
|
||||
@@ -3772,21 +3772,48 @@ SET file_path = EXCLUDED.file_path,
|
||||
last_seen_at = NOW()
|
||||
RETURNING *;
|
||||
|
||||
-- Banner count: unresolved queue rows across the caller's own devices.
|
||||
-- Banner count: unresolved, non-ignored queue rows across the caller's own
|
||||
-- devices. Ignored rows must not nag from the dashboard badge.
|
||||
-- name: CountUnresolvedUnlinkedBooksForUser :one
|
||||
SELECT COUNT(*) as count
|
||||
FROM unlinked_books ub
|
||||
JOIN devices d ON ub.device_id = d.id
|
||||
WHERE d.user_id = $1 AND ub.resolved = false;
|
||||
WHERE d.user_id = $1 AND ub.resolved = false AND ub.ignored_at IS NULL;
|
||||
|
||||
-- Page list: unresolved queue rows for the caller's own devices.
|
||||
-- Page list: unresolved queue rows for the caller's own devices (ignored
|
||||
-- rows are listed by ListIgnoredUnlinkedBooksForUser instead).
|
||||
-- name: ListUnresolvedUnlinkedBooksForUser :many
|
||||
SELECT ub.*, d.device_name, d.device_type
|
||||
FROM unlinked_books ub
|
||||
JOIN devices d ON ub.device_id = d.id
|
||||
WHERE d.user_id = $1 AND ub.resolved = false
|
||||
WHERE d.user_id = $1 AND ub.resolved = false AND ub.ignored_at IS NULL
|
||||
ORDER BY ub.last_seen_at DESC;
|
||||
|
||||
-- Page list, ignored bucket: rows the user dismissed, kept for the
|
||||
-- collapsible "Ignored" section so a misclick is recoverable.
|
||||
-- name: ListIgnoredUnlinkedBooksForUser :many
|
||||
SELECT ub.*, d.device_name, d.device_type
|
||||
FROM unlinked_books ub
|
||||
JOIN devices d ON ub.device_id = d.id
|
||||
WHERE d.user_id = $1 AND ub.resolved = false AND ub.ignored_at IS NOT NULL
|
||||
ORDER BY ub.ignored_at DESC;
|
||||
|
||||
-- Soft-dismiss one row (ownership is checked by the caller against the
|
||||
-- device's user_id before calling this).
|
||||
-- name: IgnoreUnlinkedBook :exec
|
||||
UPDATE unlinked_books SET ignored_at = NOW() WHERE id = $1;
|
||||
|
||||
-- Undo a soft-dismiss.
|
||||
-- name: UnignoreUnlinkedBook :exec
|
||||
UPDATE unlinked_books SET ignored_at = NULL WHERE id = $1;
|
||||
|
||||
-- Fetch one row together with its device's owner for authorization.
|
||||
-- name: GetUnlinkedBookWithOwner :one
|
||||
SELECT ub.*, d.user_id, d.device_name, d.device_type
|
||||
FROM unlinked_books ub
|
||||
JOIN devices d ON ub.device_id = d.id
|
||||
WHERE ub.id = $1;
|
||||
|
||||
-- ============================================
|
||||
-- CAROUSEL-STYLE DASHBOARD
|
||||
-- ============================================
|
||||
|
||||
Reference in new issue
Block a user