feat(podcasts): generic-surface exclusions — podcasts never render as books (task 1.3)
§9 / D3 / D13 / D15 / D16: podcasts-type libraries are excluded from every generic book surface. Under RLS these are pure UX/type filters (engine already hides other users' episodes); their job is preventing episode grids in surfaces designed for books. The discriminator is the denormalized media_items.library_type_name (IS DISTINCT FROM 'podcasts' — NULL rows predate the column and stay visible). SQL (queries.sql, marked '-- podcasts exclusion (plan §9)'): ListMediaItems/ListMediaItemsSorted (All Books) · SearchMediaItems + SearchMediaItemsUnified (generic search) · FacetCountsUnderFilters (all 14 arms) + the six Search*Values facet-suggestion queries · GetDistinctSeries + Count (series pages) · GetLibraryItems (collections picker) · dashboard system sections (Continue Reading / Recently Added / Recently Read / Not Started) · GetPopularBooks + GetTopBooksByMinutes (book rankings must not rank episodes). Go: statistics summary counts episodes' sessions into time-spent (D15 keeps that) but skips them in BooksActive/BooksFinished — an episode is not a book. OPDS filters podcasts libraries via one helper at all 7 library-walk sites (D16). Templ: LibrarySwitcher skips podcasts entries and TotalMediaCount excludes them — presentation-layer filter so every switcher consumer (dashboard, collections, custom-section builder) is correct at once (D13); podcasts surface via /podcasts only.
This commit is contained in:
1 parent
c0e6993273
commit
9a148c2344
8 files changed
+174
-47
No files matched your search
@@ -180,12 +180,16 @@ SELECT * FROM media_items WHERE library_id = $1 AND file_path = $2;
|
||||
SELECT * FROM media_items WHERE id = $1;
|
||||
|
||||
-- name: ListMediaItems :many
|
||||
-- Podcasts exclusion (plan §9): generic book surfaces never list
|
||||
-- podcast episodes (library_type_name is the denormalized, trigger/
|
||||
-- fetcher-maintained discriminator; NULL rows predate it and stay).
|
||||
SELECT mi.*, l.name as library_name, lt.name as library_type_name
|
||||
FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
JOIN library_types lt ON l.library_type_id = lt.id
|
||||
WHERE mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts')
|
||||
AND (sqlc.narg('visible_library_ids')::uuid[] IS NULL OR mi.library_id = ANY(sqlc.narg('visible_library_ids')::uuid[]))
|
||||
ORDER BY mi.created_at DESC LIMIT $1 OFFSET $2;
|
||||
|
||||
@@ -214,6 +218,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
JOIN library_types lt ON l.library_type_id = lt.id
|
||||
WHERE mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.library_id = sqlc.narg('library_id')
|
||||
ORDER BY
|
||||
CASE
|
||||
@@ -660,6 +665,7 @@ LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.na
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND (
|
||||
mi.title ILIKE sqlc.narg('search_pattern') OR
|
||||
@@ -750,6 +756,7 @@ LEFT JOIN reading_progress rp ON rp.media_item_id = mi.id AND rp.user_id = sqlc.
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL
|
||||
OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
-- Fuzzy author filter — per-element against the authors[] array (A2c
|
||||
@@ -934,6 +941,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -953,6 +961,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -973,6 +982,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
CROSS JOIN LATERAL unnest(mi.tags_search) AS tag
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND tag IS NOT NULL
|
||||
AND word_similarity(sqlc.narg('search_query'), tag) > 0.3
|
||||
@@ -990,6 +1000,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
CROSS JOIN LATERAL unnest(mi.narrators) AS narrator
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND narrator IS NOT NULL
|
||||
AND word_similarity(sqlc.narg('search_query'), narrator) > 0.3
|
||||
@@ -1006,6 +1017,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1025,6 +1037,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1063,6 +1076,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
CROSS JOIN LATERAL unnest(mi.authors) AS a
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1131,6 +1145,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
CROSS JOIN LATERAL unnest(mi.publishers) AS p
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1198,6 +1213,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
CROSS JOIN LATERAL unnest(mi.narrators) AS n
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1267,6 +1283,7 @@ JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
CROSS JOIN LATERAL unnest(mi.tags) AS t
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1335,6 +1352,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1406,6 +1424,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1477,6 +1496,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1548,6 +1568,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1615,6 +1636,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1682,6 +1704,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1749,6 +1772,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1820,6 +1844,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1891,6 +1916,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -1962,6 +1988,7 @@ FROM media_items mi
|
||||
JOIN libraries l ON mi.library_id = l.id
|
||||
LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id')
|
||||
WHERE COALESCE(lv.is_visible, true) = true
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
@@ -2606,6 +2633,7 @@ SELECT
|
||||
FROM media_items mi
|
||||
JOIN reading_history rh ON rh.media_item_id = mi.id
|
||||
WHERE rh.user_id = $1
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
GROUP BY mi.id, mi.title, mi.author
|
||||
ORDER BY read_count DESC
|
||||
LIMIT $2;
|
||||
@@ -2668,6 +2696,7 @@ SELECT
|
||||
FROM reading_history rh
|
||||
JOIN media_items mi ON mi.id = rh.media_item_id
|
||||
WHERE rh.user_id = sqlc.arg('user_id')
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND COALESCE(rh.reading_session_end, rh.created_at) >= sqlc.arg('start_time')
|
||||
AND COALESCE(rh.reading_session_end, rh.created_at) <= sqlc.arg('end_time')
|
||||
AND (sqlc.narg('visible_library_ids')::uuid[] IS NULL OR mi.library_id = ANY(sqlc.narg('visible_library_ids')::uuid[]))
|
||||
@@ -3964,6 +3993,7 @@ INNER JOIN (
|
||||
ORDER BY media_item_id, last_read_at DESC
|
||||
) rp ON rp.media_item_id = mi.id
|
||||
WHERE (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
ORDER BY rp.last_read_at DESC
|
||||
@@ -3972,6 +4002,7 @@ LIMIT sqlc.narg('limit');
|
||||
-- name: GetRecentlyAddedItems :many
|
||||
SELECT mi.* FROM media_items mi
|
||||
WHERE (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
ORDER BY mi.imported_at DESC NULLS LAST, mi.created_at DESC
|
||||
@@ -3987,6 +4018,7 @@ INNER JOIN (
|
||||
ORDER BY media_item_id, last_read_at DESC
|
||||
) rp ON rp.media_item_id = mi.id
|
||||
WHERE (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
ORDER BY rp.last_read_at DESC
|
||||
@@ -3995,6 +4027,7 @@ LIMIT sqlc.narg('limit');
|
||||
-- name: GetNotStartedItems :many
|
||||
SELECT mi.* FROM media_items mi
|
||||
WHERE (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND NOT EXISTS (
|
||||
@@ -4081,6 +4114,7 @@ SELECT mi.* FROM media_items mi
|
||||
WHERE (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid)
|
||||
AND mi.archived_at IS NULL
|
||||
AND mi.missing_scan_count = 0
|
||||
AND (mi.library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9: collections picker)
|
||||
ORDER BY mi.created_at DESC;
|
||||
|
||||
-- name: GetDistinctSeries :many
|
||||
@@ -4113,6 +4147,7 @@ WHERE (sqlc.narg('library_id')::uuid IS NULL OR library_id = sqlc.narg('library_
|
||||
AND (sqlc.narg('visible_library_ids')::uuid[] IS NULL OR library_id = ANY(sqlc.narg('visible_library_ids')::uuid[]))
|
||||
AND archived_at IS NULL
|
||||
AND missing_scan_count = 0
|
||||
AND (library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9: series pages)
|
||||
AND series IS NOT NULL AND series != ''
|
||||
-- Multi-field search (series page): the query matches the series
|
||||
-- NAME, any book's AUTHOR, or any book's TITLE in the group. It is
|
||||
@@ -4173,6 +4208,7 @@ SELECT COUNT(*)::int FROM (
|
||||
AND (sqlc.narg('visible_library_ids')::uuid[] IS NULL OR library_id = ANY(sqlc.narg('visible_library_ids')::uuid[]))
|
||||
AND archived_at IS NULL
|
||||
AND missing_scan_count = 0
|
||||
AND (library_type_name IS DISTINCT FROM 'podcasts') -- podcasts exclusion (plan §9: series pages)
|
||||
AND series IS NOT NULL AND series != ''
|
||||
AND (sqlc.narg('series_query')::text IS NULL OR
|
||||
series ILIKE '%' || sqlc.narg('series_query')::text || '%'
|
||||
|
||||
Reference in new issue
Block a user