feat(podcasts): schema, RLS policy extension, sqlc queries — feed core foundations
Podcast integration task 1.1 (plan: podcast-integration.md §6/§8, D20).
WHY: podcasts are a new first-class media domain — RSS feeds users
subscribe to personally, with episodes as media_items rows so progress,
stats and the audio player work unchanged. Visibility is per-user
(strict: only your subscriptions), which after the RLS migration
(2026-10-29) belongs in the POLICIES, not Go helpers.
Schema (idempotent — schema.sql re-executes every boot):
- 'podcasts' library type seeded (ON CONFLICT DO NOTHING, ARRAY[]::TEXT[]
cast required — bare ARRAY[] 42802s under ON_ERROR_STOP)
- podcast_feeds: per-library feed entity (UNIQUE(library_id, feed_url)),
full RSS/iTunes/Podcasting-2.0 metadata inventory, conditional-GET
validators, is_complete poll backoff
- podcast_subscriptions: the ONLY user-writable podcast state (display
name, auto-download policy); deliberately UNGOVERNED — it IS the read
policy's lookup table
- media_items episode columns: source/source_feed_id/episode_guid/
feed_position/season/episode/enclosure_url/declared_duration/
transcript_url/chapters_url/hosts/guests + download state machine
(pending|queued|downloading|downloaded|failed + error + attempts),
all partial-indexed so scan rows never touch them
- libraries.allow_member_feeds (first per-library knob); library_folders
.purpose ('podcast' download roots, scanner-skipped)
RLS (inside the labeled policy block):
- p_media_items_visible gains the podcast-episode subscription predicate
+ admin escape (source IS DISTINCT FROM 'podcast_episode' guard keeps
scan rows on the cheap path); unset identity still fails loud
- podcast_feeds governed identically: library-visibility join AND
subscriber-or-admin SELECT; permissive write stand-ins (mandatory at
ENABLE time; fetcher/worker declare first-admin, add-feed upsert uses
ON CONFLICT DO UPDATE RETURNING so creators never need SELECT on
foreign rows)
- Verified against dev DB: migration identity sees nothing, admin sees
all, member sees feeds/episodes only post-subscribe (probed in a
rolled-back tx — no rows persisted)
sqlc: 30 new queries (feed upsert/CRUD, subscriptions, episode
upsert-with-write-after-success-guard, download state transitions,
poller worklist, subscription-scoped search) — no visibility clauses
anywhere, per the D20 scoping model.
This commit is contained in:
1 parent
4ab4f9fa90
commit
0b7ad0f376
6 files changed
+3799
-673
No files matched your search
@@ -29,6 +29,12 @@ const (
|
||||
LibraryTypeComics = "comics"
|
||||
LibraryTypeManga = "manga"
|
||||
LibraryTypeAudiobooks = "audiobooks"
|
||||
// Podcasts libraries are feed containers (RSS subscribe+download),
|
||||
// not FS-scanned media: AllowedExtensions is deliberately empty —
|
||||
// the scanner skips these folders wholesale (podcast downloads are
|
||||
// written by the fetcher's download worker, never discovered by
|
||||
// the walker). See podcast_service.go.
|
||||
LibraryTypePodcasts = "podcasts"
|
||||
)
|
||||
|
||||
var AllowedExtensions = map[string][]string{
|
||||
@@ -43,6 +49,9 @@ var AllowedExtensions = map[string][]string{
|
||||
// browser-playable lossless/next-gen containers join for Batch B
|
||||
// metadata parity (.wma deliberately absent: browsers can't play it).
|
||||
LibraryTypeAudiobooks: {".m4b", ".m4a", ".mp3", ".flac", ".zip", ".opus", ".ogg", ".oga", ".aac", ".wav"},
|
||||
// Empty BY DESIGN: nothing in a podcasts library is scanner-facing.
|
||||
// The scanner additionally skips these folders outright.
|
||||
LibraryTypePodcasts: {},
|
||||
}
|
||||
|
||||
var MimeTypes = map[string]string{
|
||||
|
||||
Reference in new issue
Block a user