Audiobook and ebook libraries previously never entered the Recently Added pipeline: availability detection only ran for TV/movie/mixed libraries and release_events only knew episode/movie kinds, so server channels (Discord/generic webhooks) could not announce new audiobooks or ebooks. Generalize the movie path into a flat-item-kind registry (internal/notifications/item_kind.go) driving availability detection, recording, channel toggles, payload rendering, test fixtures, and the admin backfill seeder. New kinds share a kind-discriminated item_availability table; movie_availability stays as-is. Channels gain notify_new_audiobooks/notify_new_ebooks toggles (default on, additive API fields) and embeds carry the author from item_people. Flood-safe by construction: existing libraries seed silently on their first post-upgrade full scan. Extract internal/librarykind to replace the is*LibraryType helper copies that had drifted across scanner, libraryingest, and metadata (metadata's movie check silently included mixed; now spelled explicitly). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
66 lines
3.0 KiB
SQL
66 lines
3.0 KiB
SQL
-- +goose Up
|
|
-- +goose StatementBegin
|
|
-- Audiobook and ebook "recently added" notifications. Both kinds are flat
|
|
-- item kinds like movies (item_id only, no series/episode columns): they
|
|
-- widen the existing item-shaped branch of the release_events constraints
|
|
-- and get their own server-channel toggles.
|
|
|
|
ALTER TABLE public.release_events
|
|
DROP CONSTRAINT release_events_kind_check,
|
|
DROP CONSTRAINT release_events_kind_shape_check;
|
|
ALTER TABLE public.release_events
|
|
ADD CONSTRAINT release_events_kind_check
|
|
CHECK (kind IN ('episode', 'movie', 'audiobook', 'ebook')),
|
|
ADD CONSTRAINT release_events_kind_shape_check CHECK (
|
|
(kind = 'episode' AND series_id IS NOT NULL AND episode_id IS NOT NULL
|
|
AND season_number IS NOT NULL AND episode_number IS NOT NULL
|
|
AND episode_key IS NOT NULL)
|
|
OR (kind IN ('movie', 'audiobook', 'ebook') AND item_id IS NOT NULL)
|
|
);
|
|
|
|
-- Generic flat-item availability facts for every kind after movie: one-way
|
|
-- "item first became available in this library" rows, movie_availability's
|
|
-- shape plus a kind discriminator. Movies deliberately stay in their original
|
|
-- table (renaming a populated hot-path table buys nothing); a future kind
|
|
-- widens the kind CHECKs here and on release_events, no new table.
|
|
CREATE TABLE public.item_availability (
|
|
library_id integer NOT NULL,
|
|
item_id text NOT NULL,
|
|
kind text NOT NULL,
|
|
available_at timestamptz NOT NULL DEFAULT now(),
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
CONSTRAINT item_availability_pkey PRIMARY KEY (library_id, item_id, kind),
|
|
CONSTRAINT item_availability_kind_check CHECK (kind IN ('audiobook', 'ebook'))
|
|
);
|
|
|
|
-- Per-channel content toggles for the new kinds, defaulting on like the
|
|
-- movie/episode toggles. No back-catalog flood risk: existing audiobook and
|
|
-- ebook libraries have no notification_content_seed_state rows for these
|
|
-- kinds yet, so their first full scan seeds silently.
|
|
ALTER TABLE public.notification_server_channels
|
|
ADD COLUMN notify_new_audiobooks boolean NOT NULL DEFAULT true,
|
|
ADD COLUMN notify_new_ebooks boolean NOT NULL DEFAULT true;
|
|
-- +goose StatementEnd
|
|
|
|
-- +goose Down
|
|
-- +goose StatementBegin
|
|
ALTER TABLE public.notification_server_channels
|
|
DROP COLUMN IF EXISTS notify_new_ebooks,
|
|
DROP COLUMN IF EXISTS notify_new_audiobooks;
|
|
|
|
DROP TABLE IF EXISTS public.item_availability;
|
|
|
|
DELETE FROM public.release_events WHERE kind IN ('audiobook', 'ebook');
|
|
ALTER TABLE public.release_events
|
|
DROP CONSTRAINT IF EXISTS release_events_kind_shape_check,
|
|
DROP CONSTRAINT IF EXISTS release_events_kind_check;
|
|
ALTER TABLE public.release_events
|
|
ADD CONSTRAINT release_events_kind_check CHECK (kind IN ('episode', 'movie')),
|
|
ADD CONSTRAINT release_events_kind_shape_check CHECK (
|
|
(kind = 'episode' AND series_id IS NOT NULL AND episode_id IS NOT NULL
|
|
AND season_number IS NOT NULL AND episode_number IS NOT NULL
|
|
AND episode_key IS NOT NULL)
|
|
OR (kind = 'movie' AND item_id IS NOT NULL)
|
|
);
|
|
-- +goose StatementEnd
|