143 lines
6.7 KiB
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 $$;
|