Files
website/drizzle/20261026000335_movies/migration.sql

143 lines
6.7 KiB
SQL

CREATE SCHEMA "movies";
--> statement-breakpoint
CREATE TABLE "movies"."episodes" (
"id" serial PRIMARY KEY,
"title_id" integer NOT NULL,
"ep_key" text NOT NULL,
"season" integer,
"episode" integer,
"name" text,
"runtime_min" integer,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "movies"."plays" (
"id" serial PRIMARY KEY,
"title_id" integer NOT NULL,
"episode_id" integer,
"watched_on" date NOT NULL,
"watched_at" timestamp with time zone,
"duration_min" integer,
"who" text DEFAULT 'stan' NOT NULL,
"platform" text,
"source" text NOT NULL,
"source_key" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "plays_who_check" CHECK ("who" IN ('stan','juhee','both')),
CONSTRAINT "plays_platform_check" CHECK ("platform" IS NULL OR "platform" IN ('netflix','plex','cinema','other'))
);
--> statement-breakpoint
CREATE TABLE "movies"."recs" (
"id" serial PRIMARY KEY,
"tmdb_id" integer NOT NULL,
"type" text NOT NULL,
"title" text NOT NULL,
"for_whom" text NOT NULL,
"reason" text,
"added_at" timestamp with time zone DEFAULT now() NOT NULL,
"poster_path" text,
"year" integer,
"genres" text[] DEFAULT '{}'::text[] NOT NULL,
"metadata_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "recs_type_check" CHECK ("type" IN ('movie','show')),
CONSTRAINT "recs_for_check" CHECK ("for_whom" IN ('stan','juhee','both')),
CONSTRAINT "recs_title_check" CHECK (length(btrim("title")) > 0)
);
--> statement-breakpoint
CREATE TABLE "movies"."settings" (
"key" text PRIMARY KEY,
"value" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "settings_key_check" CHECK ("key" IN ('visibility'))
);
--> statement-breakpoint
CREATE TABLE "movies"."titles" (
"id" serial PRIMARY KEY,
"type" text NOT NULL,
"tmdb_id" integer,
"match_key" text NOT NULL,
"title" text NOT NULL,
"original_title" text,
"year" integer,
"status" text DEFAULT 'watched' NOT NULL,
"rating" real,
"rewatch_count" integer DEFAULT 0 NOT NULL,
"first_watched" date,
"last_watched" date,
"watched_by" text,
"platform" text,
"source" text NOT NULL,
"sources" text[] DEFAULT '{}'::text[] NOT NULL,
"notes" text,
"match_status" text DEFAULT 'pending' NOT NULL,
"poster_path" text,
"backdrop_path" text,
"overview" text,
"genres" text[] DEFAULT '{}'::text[] NOT NULL,
"cast" text[] DEFAULT '{}'::text[] NOT NULL,
"runtime_min" integer,
"total_seasons" integer,
"total_episodes" integer,
"metadata_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "titles_type_check" CHECK ("type" IN ('movie','show')),
CONSTRAINT "titles_status_check" CHECK ("status" IN ('watching','watched','watchlist','dropped')),
CONSTRAINT "titles_rating_check" CHECK ("rating" IS NULL OR ("rating" >= 0.5 AND "rating" <= 5 AND "rating" * 2 = round("rating" * 2))),
CONSTRAINT "titles_rewatch_check" CHECK ("rewatch_count" >= 0),
CONSTRAINT "titles_who_check" CHECK ("watched_by" IS NULL OR "watched_by" IN ('stan','juhee','both')),
CONSTRAINT "titles_platform_check" CHECK ("platform" IS NULL OR "platform" IN ('netflix','plex','cinema','other')),
CONSTRAINT "titles_source_check" CHECK ("source" IN ('netflix','plex','letterboxd','manual','ju','site')),
CONSTRAINT "titles_match_check" CHECK ("match_status" IN ('pending','matched','manual','not_found')),
CONSTRAINT "titles_title_check" CHECK (length(btrim("title")) > 0)
);
--> statement-breakpoint
CREATE UNIQUE INDEX "episodes_title_key" ON "movies"."episodes" ("title_id","ep_key");--> statement-breakpoint
CREATE INDEX "episodes_title_idx" ON "movies"."episodes" ("title_id","season","episode");--> statement-breakpoint
CREATE UNIQUE INDEX "plays_source_key" ON "movies"."plays" ("source_key");--> statement-breakpoint
CREATE INDEX "plays_title_idx" ON "movies"."plays" ("title_id");--> statement-breakpoint
CREATE INDEX "plays_watched_idx" ON "movies"."plays" ("watched_on");--> statement-breakpoint
CREATE UNIQUE INDEX "recs_tmdb_key" ON "movies"."recs" ("type","tmdb_id");--> statement-breakpoint
CREATE UNIQUE INDEX "titles_tmdb_key" ON "movies"."titles" ("type","tmdb_id") WHERE "tmdb_id" IS NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "titles_match_key" ON "movies"."titles" ("match_key");--> statement-breakpoint
CREATE INDEX "titles_status_idx" ON "movies"."titles" ("status");--> statement-breakpoint
ALTER TABLE "movies"."episodes" ADD CONSTRAINT "episodes_title_id_titles_id_fkey" FOREIGN KEY ("title_id") REFERENCES "movies"."titles"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "movies"."plays" ADD CONSTRAINT "plays_title_id_titles_id_fkey" FOREIGN KEY ("title_id") REFERENCES "movies"."titles"("id") ON DELETE CASCADE;--> statement-breakpoint
ALTER TABLE "movies"."plays" ADD CONSTRAINT "plays_episode_id_episodes_id_fkey" FOREIGN KEY ("episode_id") REFERENCES "movies"."episodes"("id") ON DELETE CASCADE;--> statement-breakpoint
-- Hand-written below this line (not generated by drizzle-kit).
-- updated_at triggers, same as every other core table.
DO $$
DECLARE t text;
BEGIN
IF to_regprocedure('meta.touch_updated_at()') IS NOT NULL THEN
FOREACH t IN ARRAY ARRAY['titles','episodes','plays','recs','settings'] LOOP
EXECUTE format('CREATE TRIGGER touch_updated_at BEFORE UPDATE ON movies.%I FOR EACH ROW EXECUTE FUNCTION meta.touch_updated_at()', t);
END LOOP;
END IF;
END $$;
--> statement-breakpoint
-- Public by default; the owner can switch the tracker to private on /trackers/movies.
INSERT INTO movies.settings (key, value) VALUES ('visibility', 'public') ON CONFLICT (key) DO NOTHING;
--> statement-breakpoint
-- Grants: app role read/write only (like ai and tasks; no personal_ro). Prod only, where
-- migrations run as `personal`.
REVOKE ALL ON SCHEMA movies FROM PUBLIC;
--> statement-breakpoint
DO $$
BEGIN
IF current_user = 'personal' AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_app') THEN
GRANT USAGE ON SCHEMA movies TO personal_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA movies TO personal_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA movies TO personal_app;
ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA movies
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO personal_app;
ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA movies
GRANT USAGE, SELECT ON SEQUENCES TO personal_app;
END IF;
END $$;