From 01183df4e983bc18448d513e857a03d349cd8bb5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 00:19:32 +0100 Subject: [PATCH 1/8] Define collection type --- api/src/controllers/movies.ts | 2 +- api/src/models/collections.ts | 76 +++++++++++++++++++++++++++++++++++ api/src/models/movie.ts | 2 + api/src/models/serie.ts | 5 ++- 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 api/src/models/collections.ts diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index 83f209ac..86eb0b52 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -1,5 +1,6 @@ import { type SQL, and, eq, exists, sql } from "drizzle-orm"; import { Elysia, t } from "elysia"; +import { db } from "~/db"; import { entries, entryVideoJoin, showTranslations, shows } from "~/db/schema"; import { getColumns, sqlarr } from "~/db/utils"; import { KError } from "~/models/error"; @@ -25,7 +26,6 @@ import { sortToSql, } from "~/models/utils"; import { desc } from "~/models/utils/descriptions"; -import { db } from "../db"; const movieFilters: FilterDef = { genres: { diff --git a/api/src/models/collections.ts b/api/src/models/collections.ts new file mode 100644 index 00000000..ffa94233 --- /dev/null +++ b/api/src/models/collections.ts @@ -0,0 +1,76 @@ +import { t } from "elysia"; +import type { Prettify } from "elysia/dist/types"; +import { + ExternalId, + Genre, + Image, + Resource, + SeedImage, + TranslationRecord, +} from "./utils"; + +const BaseCollection = t.Object({ + genres: t.Array(Genre), + rating: t.Nullable(t.Integer({ minimum: 0, maximum: 100 })), + startAir: t.Nullable( + t.String({ + format: "date", + descrpition: "Date of the first item of the collection", + }), + ), + endAir: t.Nullable( + t.String({ + format: "date", + descrpition: "Date of the last item of the collection", + }), + ), + + createdAt: t.String({ format: "date-time" }), + nextRefresh: t.String({ format: "date-time" }), + + externalId: ExternalId, +}); + +export const CollectionTranslation = t.Object({ + name: t.String(), + description: t.Nullable(t.String()), + tagline: t.Nullable(t.String()), + aliases: t.Array(t.String()), + tags: t.Array(t.String()), + + poster: t.Nullable(Image), + thumbnail: t.Nullable(Image), + banner: t.Nullable(Image), + logo: t.Nullable(Image), +}); + +export const Collection = t.Intersect([ + Resource(), + CollectionTranslation, + BaseCollection, +]); +export type Collection = Prettify; + +export const SeedCollection = t.Intersect([ + t.Omit(BaseCollection, ["createdAt", "nextRefresh"]), + t.Object({ + slug: t.String({ format: "slug" }), + translations: TranslationRecord( + t.Intersect([ + t.Omit(CollectionTranslation, [ + "poster", + "thumbnail", + "banner", + "logo", + ]), + t.Object({ + poster: t.Nullable(SeedImage), + thumbnail: t.Nullable(SeedImage), + banner: t.Nullable(SeedImage), + logo: t.Nullable(SeedImage), + }), + ]), + ), + }), +]); +export type SeedCollection = Prettify; diff --git a/api/src/models/movie.ts b/api/src/models/movie.ts index a6630fb7..452d6071 100644 --- a/api/src/models/movie.ts +++ b/api/src/models/movie.ts @@ -12,6 +12,7 @@ import { TranslationRecord, } from "./utils"; import { Video } from "./video"; +import { SeedCollection } from "./collections"; export const MovieStatus = t.UnionEnum(["unknown", "finished", "planned"]); export type MovieStatus = typeof MovieStatus.static; @@ -85,6 +86,7 @@ export const SeedMovie = t.Intersect([ ]), ), videos: t.Optional(t.Array(t.String({ format: "uuid" }))), + collection: t.Optional(SeedCollection), }), ]); export type SeedMovie = Prettify; diff --git a/api/src/models/serie.ts b/api/src/models/serie.ts index 7344a9c3..5a408d9b 100644 --- a/api/src/models/serie.ts +++ b/api/src/models/serie.ts @@ -7,6 +7,8 @@ import { Genre } from "./utils/genres"; import { Image, SeedImage } from "./utils/image"; import { Language, TranslationRecord } from "./utils/language"; import { Resource } from "./utils/resource"; +import { Prettify } from "~/utils"; +import { SeedCollection } from "./collections"; export const SerieStatus = t.UnionEnum([ "unknown", @@ -57,7 +59,7 @@ export const SerieTranslation = t.Object({ export type SerieTranslation = typeof SerieTranslation.static; export const Serie = t.Intersect([Resource(), SerieTranslation, BaseSerie]); -export type Serie = typeof Serie.static; +export type Serie = Prettify; export const SeedSerie = t.Intersect([ t.Omit(BaseSerie, ["createdAt", "nextRefresh"]), @@ -77,6 +79,7 @@ export const SeedSerie = t.Intersect([ seasons: t.Array(SeedSeason), entries: t.Array(SeedEntry), extras: t.Optional(t.Array(SeedExtra)), + collection: t.Optional(SeedCollection), }), ]); export type SeedSerie = typeof SeedSerie.static; From 55355074693f76dd0bfb33cf4ba0f9b513e71527 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 00:20:55 +0100 Subject: [PATCH 2/8] Rework collection relation on db schema --- api/README.md | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/api/README.md b/api/README.md index eb50f915..709637a5 100644 --- a/api/README.md +++ b/api/README.md @@ -8,7 +8,7 @@ The many-to-many relation between entries (episodes/movies) & videos is NOT a mi erDiagram shows { guid id PK - kind kind "serie|movie" + kind kind "serie|movie|collection" string(128) slug UK genre[] genres int rating "From 0 to 100" @@ -20,6 +20,7 @@ erDiagram jsonb external_id guid studio_id FK string original_language + guid collection_id FK } show_translations { guid id PK, FK @@ -37,6 +38,7 @@ erDiagram } shows ||--|{ show_translations : has shows |o--|| entries : has + shows |o--|| shows : has_collection entries { guid id PK @@ -70,23 +72,6 @@ erDiagram } video }|--|{ entries : for - collections { - guid id PK - string(256) slug UK - datetime added_date - datetime next_refresh - } - - collection_translations { - guid id PK, FK - string language PK - string name "NN" - jsonb poster - jsonb thumbnail - } - collections ||--|{ collection_translations : has - collections |o--|{ shows : has - seasons { guid id PK string(256) slug UK From dbfe836ce3c4a095fd7346ff8bdeb3b58ad32d19 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 00:39:54 +0100 Subject: [PATCH 3/8] Add collection type in database --- api/drizzle/0009_collections.sql | 3 + api/drizzle/meta/0009_snapshot.json | 1053 +++++++++++++++++++++++++++ api/drizzle/meta/_journal.json | 7 + api/src/db/schema/shows.ts | 11 +- 4 files changed, 1073 insertions(+), 1 deletion(-) create mode 100644 api/drizzle/0009_collections.sql create mode 100644 api/drizzle/meta/0009_snapshot.json diff --git a/api/drizzle/0009_collections.sql b/api/drizzle/0009_collections.sql new file mode 100644 index 00000000..b6599877 --- /dev/null +++ b/api/drizzle/0009_collections.sql @@ -0,0 +1,3 @@ +ALTER TYPE "kyoo"."show_kind" ADD VALUE 'collection';--> statement-breakpoint +ALTER TABLE "kyoo"."shows" ADD COLUMN "collection_pk" integer;--> statement-breakpoint +ALTER TABLE "kyoo"."shows" ADD CONSTRAINT "shows_collection_pk_shows_pk_fk" FOREIGN KEY ("collection_pk") REFERENCES "kyoo"."shows"("pk") ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/api/drizzle/meta/0009_snapshot.json b/api/drizzle/meta/0009_snapshot.json new file mode 100644 index 00000000..05dd26b4 --- /dev/null +++ b/api/drizzle/meta/0009_snapshot.json @@ -0,0 +1,1053 @@ +{ + "id": "7a04670c-5fb9-4535-b6be-dc291b8b0b09", + "prevId": "5c17dd71-409a-4c80-870d-f12386676738", + "version": "7", + "dialect": "postgresql", + "tables": { + "kyoo.entries": { + "name": "entries", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "entries_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "show_pk": { + "name": "show_pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "season_number": { + "name": "season_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "episode_number": { + "name": "episode_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "entry_type", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "extra_kind": { + "name": "extra_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "air_date": { + "name": "air_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "runtime": { + "name": "runtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entries_show_pk_shows_pk_fk": { + "name": "entries_show_pk_shows_pk_fk", + "tableFrom": "entries", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": [ + "show_pk" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entries_id_unique": { + "name": "entries_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "entries_slug_unique": { + "name": "entries_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "entries_showPk_seasonNumber_episodeNumber_unique": { + "name": "entries_showPk_seasonNumber_episodeNumber_unique", + "nullsNotDistinct": false, + "columns": [ + "show_pk", + "season_number", + "episode_number" + ] + } + }, + "policies": {}, + "checkConstraints": { + "order_positive": { + "name": "order_positive", + "value": "\"kyoo\".\"entries\".\"order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "kyoo.entry_translations": { + "name": "entry_translations", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "poster": { + "name": "poster", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "entry_translations_pk_entries_pk_fk": { + "name": "entry_translations_pk_entries_pk_fk", + "tableFrom": "entry_translations", + "tableTo": "entries", + "schemaTo": "kyoo", + "columnsFrom": [ + "pk" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entry_translations_pk_language_pk": { + "name": "entry_translations_pk_language_pk", + "columns": [ + "pk", + "language" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.season_translations": { + "name": "season_translations", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "poster": { + "name": "poster", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "banner": { + "name": "banner", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "season_translations_pk_seasons_pk_fk": { + "name": "season_translations_pk_seasons_pk_fk", + "tableFrom": "season_translations", + "tableTo": "seasons", + "schemaTo": "kyoo", + "columnsFrom": [ + "pk" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "season_translations_pk_language_pk": { + "name": "season_translations_pk_language_pk", + "columns": [ + "pk", + "language" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.seasons": { + "name": "seasons", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "seasons_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "show_pk": { + "name": "show_pk", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "season_number": { + "name": "season_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_air": { + "name": "start_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_air": { + "name": "end_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "show_fk": { + "name": "show_fk", + "columns": [ + { + "expression": "show_pk", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + } + }, + "foreignKeys": { + "seasons_show_pk_shows_pk_fk": { + "name": "seasons_show_pk_shows_pk_fk", + "tableFrom": "seasons", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": [ + "show_pk" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_id_unique": { + "name": "seasons_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "seasons_slug_unique": { + "name": "seasons_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "seasons_showPk_seasonNumber_unique": { + "name": "seasons_showPk_seasonNumber_unique", + "nullsNotDistinct": false, + "columns": [ + "show_pk", + "season_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.show_translations": { + "name": "show_translations", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "poster": { + "name": "poster", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "banner": { + "name": "banner", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trailer_url": { + "name": "trailer_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "name_trgm": { + "name": "name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "tags": { + "name": "tags", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "show_translations_pk_shows_pk_fk": { + "name": "show_translations_pk_shows_pk_fk", + "tableFrom": "show_translations", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": [ + "pk" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "show_translations_pk_language_pk": { + "name": "show_translations_pk_language_pk", + "columns": [ + "pk", + "language" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.shows": { + "name": "shows", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "shows_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "show_kind", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "genres": { + "name": "genres", + "type": "genres[]", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "runtime": { + "name": "runtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "show_status", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "start_air": { + "name": "start_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_air": { + "name": "end_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "original_language": { + "name": "original_language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "collection_pk": { + "name": "collection_pk", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kind": { + "name": "kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "rating": { + "name": "rating", + "columns": [ + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "startAir": { + "name": "startAir", + "columns": [ + { + "expression": "start_air", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shows_collection_pk_shows_pk_fk": { + "name": "shows_collection_pk_shows_pk_fk", + "tableFrom": "shows", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": [ + "collection_pk" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shows_id_unique": { + "name": "shows_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "shows_slug_unique": { + "name": "shows_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "rating_valid": { + "name": "rating_valid", + "value": "\"kyoo\".\"shows\".\"rating\" between 0 and 100" + }, + "runtime_valid": { + "name": "runtime_valid", + "value": "\"kyoo\".\"shows\".\"runtime\" >= 0" + } + }, + "isRLSEnabled": false + }, + "kyoo.entry_video_join": { + "name": "entry_video_join", + "schema": "kyoo", + "columns": { + "entry": { + "name": "entry", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "video": { + "name": "video", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entry_video_join_entry_entries_pk_fk": { + "name": "entry_video_join_entry_entries_pk_fk", + "tableFrom": "entry_video_join", + "tableTo": "entries", + "schemaTo": "kyoo", + "columnsFrom": [ + "entry" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entry_video_join_video_videos_pk_fk": { + "name": "entry_video_join_video_videos_pk_fk", + "tableFrom": "entry_video_join", + "tableTo": "videos", + "schemaTo": "kyoo", + "columnsFrom": [ + "video" + ], + "columnsTo": [ + "pk" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entry_video_join_entry_video_pk": { + "name": "entry_video_join_entry_video_pk", + "columns": [ + "entry", + "video" + ] + } + }, + "uniqueConstraints": { + "entry_video_join_slug_unique": { + "name": "entry_video_join_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.videos": { + "name": "videos", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "videos_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rendering": { + "name": "rendering", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "part": { + "name": "part", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "guess": { + "name": "guess", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "videos_id_unique": { + "name": "videos_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id" + ] + }, + "videos_path_unique": { + "name": "videos_path_unique", + "nullsNotDistinct": false, + "columns": [ + "path" + ] + } + }, + "policies": {}, + "checkConstraints": { + "part_pos": { + "name": "part_pos", + "value": "\"kyoo\".\"videos\".\"part\" >= 0" + }, + "version_pos": { + "name": "version_pos", + "value": "\"kyoo\".\"videos\".\"version\" >= 0" + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "kyoo.entry_type": { + "name": "entry_type", + "schema": "kyoo", + "values": [ + "unknown", + "episode", + "movie", + "special", + "extra" + ] + }, + "kyoo.genres": { + "name": "genres", + "schema": "kyoo", + "values": [ + "action", + "adventure", + "animation", + "comedy", + "crime", + "documentary", + "drama", + "family", + "fantasy", + "history", + "horror", + "music", + "mystery", + "romance", + "science-fiction", + "thriller", + "war", + "western", + "kids", + "reality", + "politics", + "soap", + "talk" + ] + }, + "kyoo.show_kind": { + "name": "show_kind", + "schema": "kyoo", + "values": [ + "serie", + "movie", + "collection" + ] + }, + "kyoo.show_status": { + "name": "show_status", + "schema": "kyoo", + "values": [ + "unknown", + "finished", + "airing", + "planned" + ] + } + }, + "schemas": { + "kyoo": "kyoo" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/api/drizzle/meta/_journal.json b/api/drizzle/meta/_journal.json index ea8303e2..28c40aaf 100644 --- a/api/drizzle/meta/_journal.json +++ b/api/drizzle/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1738064522937, "tag": "0008_entries", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1740872363604, + "tag": "0009_collections", + "breakpoints": true } ] } diff --git a/api/src/db/schema/shows.ts b/api/src/db/schema/shows.ts index 6745eed0..744f0554 100644 --- a/api/src/db/schema/shows.ts +++ b/api/src/db/schema/shows.ts @@ -1,5 +1,6 @@ import { relations, sql } from "drizzle-orm"; import { + type AnyPgColumn, check, date, index, @@ -16,7 +17,11 @@ import { entries } from "./entries"; import { seasons } from "./seasons"; import { image, language, schema } from "./utils"; -export const showKind = schema.enum("show_kind", ["serie", "movie"]); +export const showKind = schema.enum("show_kind", [ + "serie", + "movie", + "collection", +]); export const showStatus = schema.enum("show_status", [ "unknown", "finished", @@ -78,6 +83,10 @@ export const shows = schema.table( endAir: date(), originalLanguage: language(), + collectionPk: integer().references((): AnyPgColumn => shows.pk, { + onDelete: "set null", + }), + externalId: externalid(), createdAt: timestamp({ withTimezone: true, mode: "string" }) From fc9afa5ede9f3c19d590c8443df4e648bce742f5 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 01:03:38 +0100 Subject: [PATCH 4/8] Allow collections to be created with movies or series --- api/src/controllers/seed/insert/collection.ts | 18 ++++++++++++++++++ api/src/controllers/seed/insert/shows.ts | 11 ++++++++--- api/src/controllers/seed/movies.ts | 13 ++++++++++++- api/src/controllers/seed/series.ts | 16 +++++++++++++++- api/src/models/collections.ts | 2 +- 5 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 api/src/controllers/seed/insert/collection.ts diff --git a/api/src/controllers/seed/insert/collection.ts b/api/src/controllers/seed/insert/collection.ts new file mode 100644 index 00000000..99890bfe --- /dev/null +++ b/api/src/controllers/seed/insert/collection.ts @@ -0,0 +1,18 @@ +import type { SeedCollection } from "~/models/collections"; +import { insertShow } from "./shows"; + +export const insertCollection = async (collection?: SeedCollection) => { + if (!collection) return null; + const { translations: colTrans, ...col } = collection; + // TODO: need to compute start/end year & if missing tags & genres + const { updated, status, ...ret } = await insertShow( + { + kind: "collection", + status: "unknown", + nextRefresh, + ...col, + }, + colTrans, + ); + return ret; +}; diff --git a/api/src/controllers/seed/insert/shows.ts b/api/src/controllers/seed/insert/shows.ts index 8986e33e..48e14d62 100644 --- a/api/src/controllers/seed/insert/shows.ts +++ b/api/src/controllers/seed/insert/shows.ts @@ -2,6 +2,7 @@ import { eq, sql } from "drizzle-orm"; import { db } from "~/db"; import { showTranslations, shows } from "~/db/schema"; import { conflictUpdateAllExcept } from "~/db/utils"; +import type { SeedCollection } from "~/models/collections"; import type { SeedMovie } from "~/models/movie"; import type { SeedSerie } from "~/models/serie"; import { getYear } from "~/utils"; @@ -12,7 +13,10 @@ type ShowTrans = typeof showTranslations.$inferInsert; export const insertShow = async ( show: Show, - translations: SeedMovie["translations"] | SeedSerie["translations"], + translations: + | SeedMovie["translations"] + | SeedSerie["translations"] + | SeedCollection["translations"], ) => { return await db.transaction(async (tx) => { const ret = await insertBaseShow(tx, show); @@ -77,13 +81,14 @@ async function insertBaseShow( // if at this point ret is still undefined, we could not reconciliate. // simply bail and let the caller handle this. - const [{ id }] = await db - .select({ id: shows.id }) + const [{ pk, id }] = await db + .select({ pk: shows.pk, id: shows.id }) .from(shows) .where(eq(shows.slug, show.slug)) .limit(1); return { status: 409 as const, + pk, id, slug: show.slug, }; diff --git a/api/src/controllers/seed/movies.ts b/api/src/controllers/seed/movies.ts index eeab21f5..c20188e0 100644 --- a/api/src/controllers/seed/movies.ts +++ b/api/src/controllers/seed/movies.ts @@ -1,6 +1,7 @@ import { t } from "elysia"; import type { SeedMovie } from "~/models/movie"; import { getYear } from "~/utils"; +import { insertCollection } from "./insert/collection"; import { insertEntries } from "./insert/entries"; import { insertShow } from "./insert/shows"; import { guessNextRefresh } from "./refresh"; @@ -11,6 +12,12 @@ export const SeedMovieResponse = t.Object({ videos: t.Array( t.Object({ slug: t.String({ format: "slug", examples: ["bubble-v2"] }) }), ), + collection: t.Nullable( + t.Object({ + id: t.String({ format: "uuid" }), + slug: t.String({ format: "slug", examples: ["sawano-collection"] }), + }), + ), }); export type SeedMovieResponse = typeof SeedMovieResponse.static; @@ -31,14 +38,17 @@ export const seedMovie = async ( seed.slug = `random-${getYear(seed.airDate)}`; } - const { translations, videos, ...bMovie } = seed; + const { translations, videos, collection, ...bMovie } = seed; const nextRefresh = guessNextRefresh(bMovie.airDate ?? new Date()); + const col = await insertCollection(collection); + const show = await insertShow( { kind: "movie", startAir: bMovie.airDate, nextRefresh, + collectionPk: col?.pk, ...bMovie, }, translations, @@ -65,5 +75,6 @@ export const seedMovie = async ( id: show.id, slug: show.slug, videos: entry.videos, + collection: col, }; }; diff --git a/api/src/controllers/seed/series.ts b/api/src/controllers/seed/series.ts index c8c2667d..124d3b9c 100644 --- a/api/src/controllers/seed/series.ts +++ b/api/src/controllers/seed/series.ts @@ -1,6 +1,7 @@ import { t } from "elysia"; import type { SeedSerie } from "~/models/serie"; import { getYear } from "~/utils"; +import { insertCollection } from "./insert/collection"; import { insertEntries } from "./insert/entries"; import { insertSeasons } from "./insert/seasons"; import { insertShow } from "./insert/shows"; @@ -35,6 +36,15 @@ export const SeedSerieResponse = t.Object({ slug: t.String({ format: "slug", examples: ["made-in-abyss-s1e1"] }), }), ), + collection: t.Nullable( + t.Object({ + id: t.String({ format: "uuid" }), + slug: t.String({ + format: "slug", + examples: ["made-in-abyss-collection"], + }), + }), + ), }); export type SeedSerieResponse = typeof SeedSerieResponse.static; @@ -55,13 +65,16 @@ export const seedSerie = async ( seed.slug = `random-${getYear(seed.startAir)}`; } - const { translations, seasons, entries, extras, ...serie } = seed; + const { translations, seasons, entries, extras, collection, ...serie } = seed; const nextRefresh = guessNextRefresh(serie.startAir ?? new Date()); + const col = await insertCollection(collection); + const show = await insertShow( { kind: "serie", nextRefresh, + collectionPk: col?.pk, ...serie, }, translations, @@ -82,5 +95,6 @@ export const seedSerie = async ( seasons: retSeasons, entries: retEntries, extras: retExtras, + collection: col, }; }; diff --git a/api/src/models/collections.ts b/api/src/models/collections.ts index ffa94233..d62f3632 100644 --- a/api/src/models/collections.ts +++ b/api/src/models/collections.ts @@ -52,7 +52,7 @@ export const Collection = t.Intersect([ export type Collection = Prettify; export const SeedCollection = t.Intersect([ - t.Omit(BaseCollection, ["createdAt", "nextRefresh"]), + t.Omit(BaseCollection, ["startAir", "endAir", "createdAt", "nextRefresh"]), t.Object({ slug: t.String({ format: "slug" }), translations: TranslationRecord( From e8c9dfce567c4d7fa4e3790f78083949ff4b51c2 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 17:08:46 +0100 Subject: [PATCH 5/8] Create insert method --- api/src/controllers/seed/insert/collection.ts | 79 +++++++++++++++---- api/src/controllers/seed/movies.ts | 6 +- api/src/controllers/seed/series.ts | 6 +- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/api/src/controllers/seed/insert/collection.ts b/api/src/controllers/seed/insert/collection.ts index 99890bfe..a93fa443 100644 --- a/api/src/controllers/seed/insert/collection.ts +++ b/api/src/controllers/seed/insert/collection.ts @@ -1,18 +1,69 @@ +import { sql } from "drizzle-orm"; +import { db } from "~/db"; +import { showTranslations, shows } from "~/db/schema"; +import { conflictUpdateAllExcept } from "~/db/utils"; import type { SeedCollection } from "~/models/collections"; -import { insertShow } from "./shows"; +import type { SeedMovie } from "~/models/movie"; +import type { SeedSerie } from "~/models/serie"; +import { processOptImage } from "../images"; -export const insertCollection = async (collection?: SeedCollection) => { +type ShowTrans = typeof showTranslations.$inferInsert; + +export const insertCollection = async ( + collection: SeedCollection | undefined, + show: (({ kind: "movie" } & SeedMovie) | ({ kind: "serie" } & SeedSerie)) & { + nextRefresh: string; + }, +) => { if (!collection) return null; - const { translations: colTrans, ...col } = collection; - // TODO: need to compute start/end year & if missing tags & genres - const { updated, status, ...ret } = await insertShow( - { - kind: "collection", - status: "unknown", - nextRefresh, - ...col, - }, - colTrans, - ); - return ret; + const { translations, ...col } = collection; + + return await db.transaction(async (tx) => { + const [ret] = await tx + .insert(shows) + .values({ + kind: "collection", + status: "unknown", + startAir: show.kind === "movie" ? show.airDate : show.startAir, + endAir: show.kind === "movie" ? show.airDate : show.endAir, + nextRefresh: show.nextRefresh, + ...col, + }) + .onConflictDoUpdate({ + target: shows.slug, + set: { + ...conflictUpdateAllExcept(shows, [ + "pk", + "id", + "slug", + "createdAt", + "startAir", + "endAir", + ]), + startAir: sql`least(${shows.startAir}, excluded.start_air)`, + endAir: sql`greatest(${shows.endAir}, excluded.end_air)`, + }, + }) + .returning({ pk: shows.pk, id: shows.id, slug: shows.slug }); + + const trans: ShowTrans[] = Object.entries(translations).map( + ([lang, tr]) => ({ + pk: ret.pk, + language: lang, + ...tr, + poster: processOptImage(tr.poster), + thumbnail: processOptImage(tr.thumbnail), + logo: processOptImage(tr.logo), + banner: processOptImage(tr.banner), + }), + ); + await tx + .insert(showTranslations) + .values(trans) + .onConflictDoUpdate({ + target: [showTranslations.pk, showTranslations.language], + set: conflictUpdateAllExcept(showTranslations, ["pk", "language"]), + }); + return ret; + }); }; diff --git a/api/src/controllers/seed/movies.ts b/api/src/controllers/seed/movies.ts index c20188e0..968461d2 100644 --- a/api/src/controllers/seed/movies.ts +++ b/api/src/controllers/seed/movies.ts @@ -41,7 +41,11 @@ export const seedMovie = async ( const { translations, videos, collection, ...bMovie } = seed; const nextRefresh = guessNextRefresh(bMovie.airDate ?? new Date()); - const col = await insertCollection(collection); + const col = await insertCollection(collection, { + kind: "movie", + nextRefresh, + ...seed, + }); const show = await insertShow( { diff --git a/api/src/controllers/seed/series.ts b/api/src/controllers/seed/series.ts index 124d3b9c..53e9e777 100644 --- a/api/src/controllers/seed/series.ts +++ b/api/src/controllers/seed/series.ts @@ -68,7 +68,11 @@ export const seedSerie = async ( const { translations, seasons, entries, extras, collection, ...serie } = seed; const nextRefresh = guessNextRefresh(serie.startAir ?? new Date()); - const col = await insertCollection(collection); + const col = await insertCollection(collection, { + kind: "serie", + nextRefresh, + ...seed, + }); const show = await insertShow( { From 4538d9ce98868096d63f7b48d5f7ddf69d27e7ce Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 17:24:58 +0100 Subject: [PATCH 6/8] Add collection example --- api/src/models/collections.ts | 7 ++++++ api/src/models/examples/dune-2021.ts | 8 +++--- api/src/models/examples/dune-collection.ts | 29 ++++++++++++++++++++++ api/src/models/examples/index.ts | 4 +++ 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 api/src/models/examples/dune-collection.ts diff --git a/api/src/models/collections.ts b/api/src/models/collections.ts index d62f3632..db393f3e 100644 --- a/api/src/models/collections.ts +++ b/api/src/models/collections.ts @@ -1,5 +1,6 @@ import { t } from "elysia"; import type { Prettify } from "elysia/dist/types"; +import { bubbleImages, duneCollection, registerExamples } from "./examples"; import { ExternalId, Genre, @@ -74,3 +75,9 @@ export const SeedCollection = t.Intersect([ }), ]); export type SeedCollection = Prettify; + +registerExamples(Collection, { + ...duneCollection, + ...duneCollection.translations.en, + ...bubbleImages, +}); diff --git a/api/src/models/examples/dune-2021.ts b/api/src/models/examples/dune-2021.ts index 64f5985f..fa2b017f 100644 --- a/api/src/models/examples/dune-2021.ts +++ b/api/src/models/examples/dune-2021.ts @@ -1,5 +1,5 @@ -import type { SeedMovie } from "../movie"; -import type { Video } from "../video"; +import type { SeedMovie } from "~/models/movie"; +import type { Video } from "~/models/video"; export const duneVideo: Video = { id: "c9a0d02e-6b8e-4ac1-b431-45b022ec0708", @@ -38,8 +38,8 @@ export const dune: SeedMovie = { originalLanguage: "en", externalId: { themoviedatabase: { - dataId: "496243", - link: "https://www.themoviedb.org/movie/496243", + dataId: "438631", + link: "https://www.themoviedb.org/movie/438631-dune", }, imdb: { dataId: "tt1160419", diff --git a/api/src/models/examples/dune-collection.ts b/api/src/models/examples/dune-collection.ts new file mode 100644 index 00000000..c0b636aa --- /dev/null +++ b/api/src/models/examples/dune-collection.ts @@ -0,0 +1,29 @@ +import type { SeedCollection } from "~/models/collections"; + +export const duneCollection: SeedCollection = { + slug: "dune-collection", + translations: { + en: { + name: " Dune Collection", + tagline: "A mythic and emotionally charged hero's journey.", + description: + "The saga of Paul Atreides and his rise to power on the deadly planet Arrakis.", + aliases: [], + tags: ["sci-fi", "adventure", "drama", "action", "epic"], + poster: + "https://image.tmdb.org/t/p/original/wD57HqZ6fXwwDdfQLo4hXLRwGV1.jpg", + thumbnail: + "https://image.tmdb.org/t/p/original/k2ocXnNkmvE6rJomRkExIStFq3v.jpg", + banner: null, + logo: "https://image.tmdb.org/t/p/original/5nDsd3u1c6kDphbtIqkHseLg7HL.png", + }, + }, + genres: ["adventure", "science-fiction"], + rating: 80, + externalId: { + themoviedatabase: { + dataId: "726871", + link: "https://www.themoviedb.org/collection/726871-dune-collection", + }, + }, +}; diff --git a/api/src/models/examples/index.ts b/api/src/models/examples/index.ts index 2fe6e6a8..7bc7a3ba 100644 --- a/api/src/models/examples/index.ts +++ b/api/src/models/examples/index.ts @@ -32,3 +32,7 @@ export const registerExamples = ( export * from "./bubble"; export * from "./made-in-abyss"; +export * from "./dune-1984"; +export * from "./dune-2021"; +export * from "./dune-collection"; +export * from "./others"; From 98c6263036f2ecc234703b837579352259e2a62c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 17:27:41 +0100 Subject: [PATCH 7/8] Add basic collection test --- api/tests/collection/seed-collection.test.ts | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 api/tests/collection/seed-collection.test.ts diff --git a/api/tests/collection/seed-collection.test.ts b/api/tests/collection/seed-collection.test.ts new file mode 100644 index 00000000..b1946813 --- /dev/null +++ b/api/tests/collection/seed-collection.test.ts @@ -0,0 +1,24 @@ +import { beforeAll, describe, expect, it } from "bun:test"; +import { createMovie } from "tests/helpers"; +import { expectStatus } from "tests/utils"; +import { db } from "~/db"; +import { shows } from "~/db/schema"; +import { dune } from "~/models/examples/dune-2021"; +import { duneCollection } from "~/models/examples/dune-collection"; + +beforeAll(async () => { + await db.delete(shows); +}); + +describe("Collection seeding", () => { + it("Can create a movie with a collection", async () => { + const [resp, body] = await createMovie({ + ...dune, + collection: duneCollection, + }); + expectStatus(resp, body).toBe(201); + expect(body.id).toBeString(); + expect(body.slug).toBe("dune"); + expect(body.collection.slug).toBe("dune-collection"); + }); +}); From 019be569dd4d600fee6cf877d2310d08c64ac273 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 2 Mar 2025 17:30:00 +0100 Subject: [PATCH 8/8] Format stuff --- api/drizzle/meta/0009_snapshot.json | 2020 +++++++++++++-------------- api/src/models/movie.ts | 2 +- api/src/models/serie.ts | 4 +- 3 files changed, 971 insertions(+), 1055 deletions(-) diff --git a/api/drizzle/meta/0009_snapshot.json b/api/drizzle/meta/0009_snapshot.json index 05dd26b4..e2783cdb 100644 --- a/api/drizzle/meta/0009_snapshot.json +++ b/api/drizzle/meta/0009_snapshot.json @@ -1,1053 +1,969 @@ { - "id": "7a04670c-5fb9-4535-b6be-dc291b8b0b09", - "prevId": "5c17dd71-409a-4c80-870d-f12386676738", - "version": "7", - "dialect": "postgresql", - "tables": { - "kyoo.entries": { - "name": "entries", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": true, - "notNull": true, - "identity": { - "type": "always", - "name": "entries_pk_seq", - "schema": "kyoo", - "increment": "1", - "startWith": "1", - "minValue": "1", - "maxValue": "2147483647", - "cache": "1", - "cycle": false - } - }, - "id": { - "name": "id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "slug": { - "name": "slug", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "show_pk": { - "name": "show_pk", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "order": { - "name": "order", - "type": "real", - "primaryKey": false, - "notNull": false - }, - "season_number": { - "name": "season_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "episode_number": { - "name": "episode_number", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "kind": { - "name": "kind", - "type": "entry_type", - "typeSchema": "kyoo", - "primaryKey": false, - "notNull": true - }, - "extra_kind": { - "name": "extra_kind", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "air_date": { - "name": "air_date", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "runtime": { - "name": "runtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "thumbnail": { - "name": "thumbnail", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "next_refresh": { - "name": "next_refresh", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "entries_show_pk_shows_pk_fk": { - "name": "entries_show_pk_shows_pk_fk", - "tableFrom": "entries", - "tableTo": "shows", - "schemaTo": "kyoo", - "columnsFrom": [ - "show_pk" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "entries_id_unique": { - "name": "entries_id_unique", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - }, - "entries_slug_unique": { - "name": "entries_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - }, - "entries_showPk_seasonNumber_episodeNumber_unique": { - "name": "entries_showPk_seasonNumber_episodeNumber_unique", - "nullsNotDistinct": false, - "columns": [ - "show_pk", - "season_number", - "episode_number" - ] - } - }, - "policies": {}, - "checkConstraints": { - "order_positive": { - "name": "order_positive", - "value": "\"kyoo\".\"entries\".\"order\" >= 0" - } - }, - "isRLSEnabled": false - }, - "kyoo.entry_translations": { - "name": "entry_translations", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "language": { - "name": "language", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tagline": { - "name": "tagline", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "poster": { - "name": "poster", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "entry_translations_pk_entries_pk_fk": { - "name": "entry_translations_pk_entries_pk_fk", - "tableFrom": "entry_translations", - "tableTo": "entries", - "schemaTo": "kyoo", - "columnsFrom": [ - "pk" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "entry_translations_pk_language_pk": { - "name": "entry_translations_pk_language_pk", - "columns": [ - "pk", - "language" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "kyoo.season_translations": { - "name": "season_translations", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "language": { - "name": "language", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "poster": { - "name": "poster", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "thumbnail": { - "name": "thumbnail", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "banner": { - "name": "banner", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "season_translations_pk_seasons_pk_fk": { - "name": "season_translations_pk_seasons_pk_fk", - "tableFrom": "season_translations", - "tableTo": "seasons", - "schemaTo": "kyoo", - "columnsFrom": [ - "pk" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "season_translations_pk_language_pk": { - "name": "season_translations_pk_language_pk", - "columns": [ - "pk", - "language" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "kyoo.seasons": { - "name": "seasons", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": true, - "notNull": true, - "identity": { - "type": "always", - "name": "seasons_pk_seq", - "schema": "kyoo", - "increment": "1", - "startWith": "1", - "minValue": "1", - "maxValue": "2147483647", - "cache": "1", - "cycle": false - } - }, - "id": { - "name": "id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "slug": { - "name": "slug", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "show_pk": { - "name": "show_pk", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "season_number": { - "name": "season_number", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "start_air": { - "name": "start_air", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "end_air": { - "name": "end_air", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "next_refresh": { - "name": "next_refresh", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "show_fk": { - "name": "show_fk", - "columns": [ - { - "expression": "show_pk", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hash", - "with": {} - } - }, - "foreignKeys": { - "seasons_show_pk_shows_pk_fk": { - "name": "seasons_show_pk_shows_pk_fk", - "tableFrom": "seasons", - "tableTo": "shows", - "schemaTo": "kyoo", - "columnsFrom": [ - "show_pk" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "seasons_id_unique": { - "name": "seasons_id_unique", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - }, - "seasons_slug_unique": { - "name": "seasons_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - }, - "seasons_showPk_seasonNumber_unique": { - "name": "seasons_showPk_seasonNumber_unique", - "nullsNotDistinct": false, - "columns": [ - "show_pk", - "season_number" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "kyoo.show_translations": { - "name": "show_translations", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "language": { - "name": "language", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tagline": { - "name": "tagline", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "aliases": { - "name": "aliases", - "type": "text[]", - "primaryKey": false, - "notNull": true - }, - "tags": { - "name": "tags", - "type": "text[]", - "primaryKey": false, - "notNull": true - }, - "poster": { - "name": "poster", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "thumbnail": { - "name": "thumbnail", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "banner": { - "name": "banner", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "logo": { - "name": "logo", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "trailer_url": { - "name": "trailer_url", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "name_trgm": { - "name": "name_trgm", - "columns": [ - { - "expression": "\"name\" gin_trgm_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - }, - "tags": { - "name": "tags", - "columns": [ - { - "expression": "tags", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "show_translations_pk_shows_pk_fk": { - "name": "show_translations_pk_shows_pk_fk", - "tableFrom": "show_translations", - "tableTo": "shows", - "schemaTo": "kyoo", - "columnsFrom": [ - "pk" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "show_translations_pk_language_pk": { - "name": "show_translations_pk_language_pk", - "columns": [ - "pk", - "language" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "kyoo.shows": { - "name": "shows", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": true, - "notNull": true, - "identity": { - "type": "always", - "name": "shows_pk_seq", - "schema": "kyoo", - "increment": "1", - "startWith": "1", - "minValue": "1", - "maxValue": "2147483647", - "cache": "1", - "cycle": false - } - }, - "id": { - "name": "id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "slug": { - "name": "slug", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "show_kind", - "typeSchema": "kyoo", - "primaryKey": false, - "notNull": true - }, - "genres": { - "name": "genres", - "type": "genres[]", - "primaryKey": false, - "notNull": true - }, - "rating": { - "name": "rating", - "type": "smallint", - "primaryKey": false, - "notNull": false - }, - "runtime": { - "name": "runtime", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "show_status", - "typeSchema": "kyoo", - "primaryKey": false, - "notNull": true - }, - "start_air": { - "name": "start_air", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "end_air": { - "name": "end_air", - "type": "date", - "primaryKey": false, - "notNull": false - }, - "original_language": { - "name": "original_language", - "type": "varchar(255)", - "primaryKey": false, - "notNull": false - }, - "collection_pk": { - "name": "collection_pk", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "next_refresh": { - "name": "next_refresh", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "kind": { - "name": "kind", - "columns": [ - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hash", - "with": {} - }, - "rating": { - "name": "rating", - "columns": [ - { - "expression": "rating", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "startAir": { - "name": "startAir", - "columns": [ - { - "expression": "start_air", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "shows_collection_pk_shows_pk_fk": { - "name": "shows_collection_pk_shows_pk_fk", - "tableFrom": "shows", - "tableTo": "shows", - "schemaTo": "kyoo", - "columnsFrom": [ - "collection_pk" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "shows_id_unique": { - "name": "shows_id_unique", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - }, - "shows_slug_unique": { - "name": "shows_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": { - "rating_valid": { - "name": "rating_valid", - "value": "\"kyoo\".\"shows\".\"rating\" between 0 and 100" - }, - "runtime_valid": { - "name": "runtime_valid", - "value": "\"kyoo\".\"shows\".\"runtime\" >= 0" - } - }, - "isRLSEnabled": false - }, - "kyoo.entry_video_join": { - "name": "entry_video_join", - "schema": "kyoo", - "columns": { - "entry": { - "name": "entry", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "video": { - "name": "video", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "entry_video_join_entry_entries_pk_fk": { - "name": "entry_video_join_entry_entries_pk_fk", - "tableFrom": "entry_video_join", - "tableTo": "entries", - "schemaTo": "kyoo", - "columnsFrom": [ - "entry" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "entry_video_join_video_videos_pk_fk": { - "name": "entry_video_join_video_videos_pk_fk", - "tableFrom": "entry_video_join", - "tableTo": "videos", - "schemaTo": "kyoo", - "columnsFrom": [ - "video" - ], - "columnsTo": [ - "pk" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "entry_video_join_entry_video_pk": { - "name": "entry_video_join_entry_video_pk", - "columns": [ - "entry", - "video" - ] - } - }, - "uniqueConstraints": { - "entry_video_join_slug_unique": { - "name": "entry_video_join_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "kyoo.videos": { - "name": "videos", - "schema": "kyoo", - "columns": { - "pk": { - "name": "pk", - "type": "integer", - "primaryKey": true, - "notNull": true, - "identity": { - "type": "always", - "name": "videos_pk_seq", - "schema": "kyoo", - "increment": "1", - "startWith": "1", - "minValue": "1", - "maxValue": "2147483647", - "cache": "1", - "cycle": false - } - }, - "id": { - "name": "id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "rendering": { - "name": "rendering", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "part": { - "name": "part", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1 - }, - "guess": { - "name": "guess", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "videos_id_unique": { - "name": "videos_id_unique", - "nullsNotDistinct": false, - "columns": [ - "id" - ] - }, - "videos_path_unique": { - "name": "videos_path_unique", - "nullsNotDistinct": false, - "columns": [ - "path" - ] - } - }, - "policies": {}, - "checkConstraints": { - "part_pos": { - "name": "part_pos", - "value": "\"kyoo\".\"videos\".\"part\" >= 0" - }, - "version_pos": { - "name": "version_pos", - "value": "\"kyoo\".\"videos\".\"version\" >= 0" - } - }, - "isRLSEnabled": false - } - }, - "enums": { - "kyoo.entry_type": { - "name": "entry_type", - "schema": "kyoo", - "values": [ - "unknown", - "episode", - "movie", - "special", - "extra" - ] - }, - "kyoo.genres": { - "name": "genres", - "schema": "kyoo", - "values": [ - "action", - "adventure", - "animation", - "comedy", - "crime", - "documentary", - "drama", - "family", - "fantasy", - "history", - "horror", - "music", - "mystery", - "romance", - "science-fiction", - "thriller", - "war", - "western", - "kids", - "reality", - "politics", - "soap", - "talk" - ] - }, - "kyoo.show_kind": { - "name": "show_kind", - "schema": "kyoo", - "values": [ - "serie", - "movie", - "collection" - ] - }, - "kyoo.show_status": { - "name": "show_status", - "schema": "kyoo", - "values": [ - "unknown", - "finished", - "airing", - "planned" - ] - } - }, - "schemas": { - "kyoo": "kyoo" - }, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "7a04670c-5fb9-4535-b6be-dc291b8b0b09", + "prevId": "5c17dd71-409a-4c80-870d-f12386676738", + "version": "7", + "dialect": "postgresql", + "tables": { + "kyoo.entries": { + "name": "entries", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "entries_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "show_pk": { + "name": "show_pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "season_number": { + "name": "season_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "episode_number": { + "name": "episode_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "entry_type", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "extra_kind": { + "name": "extra_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "air_date": { + "name": "air_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "runtime": { + "name": "runtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entries_show_pk_shows_pk_fk": { + "name": "entries_show_pk_shows_pk_fk", + "tableFrom": "entries", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": ["show_pk"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entries_id_unique": { + "name": "entries_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "entries_slug_unique": { + "name": "entries_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "entries_showPk_seasonNumber_episodeNumber_unique": { + "name": "entries_showPk_seasonNumber_episodeNumber_unique", + "nullsNotDistinct": false, + "columns": ["show_pk", "season_number", "episode_number"] + } + }, + "policies": {}, + "checkConstraints": { + "order_positive": { + "name": "order_positive", + "value": "\"kyoo\".\"entries\".\"order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "kyoo.entry_translations": { + "name": "entry_translations", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "poster": { + "name": "poster", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "entry_translations_pk_entries_pk_fk": { + "name": "entry_translations_pk_entries_pk_fk", + "tableFrom": "entry_translations", + "tableTo": "entries", + "schemaTo": "kyoo", + "columnsFrom": ["pk"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entry_translations_pk_language_pk": { + "name": "entry_translations_pk_language_pk", + "columns": ["pk", "language"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.season_translations": { + "name": "season_translations", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "poster": { + "name": "poster", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "banner": { + "name": "banner", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "season_translations_pk_seasons_pk_fk": { + "name": "season_translations_pk_seasons_pk_fk", + "tableFrom": "season_translations", + "tableTo": "seasons", + "schemaTo": "kyoo", + "columnsFrom": ["pk"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "season_translations_pk_language_pk": { + "name": "season_translations_pk_language_pk", + "columns": ["pk", "language"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.seasons": { + "name": "seasons", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "seasons_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "show_pk": { + "name": "show_pk", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "season_number": { + "name": "season_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_air": { + "name": "start_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_air": { + "name": "end_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "show_fk": { + "name": "show_fk", + "columns": [ + { + "expression": "show_pk", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + } + }, + "foreignKeys": { + "seasons_show_pk_shows_pk_fk": { + "name": "seasons_show_pk_shows_pk_fk", + "tableFrom": "seasons", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": ["show_pk"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_id_unique": { + "name": "seasons_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "seasons_slug_unique": { + "name": "seasons_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "seasons_showPk_seasonNumber_unique": { + "name": "seasons_showPk_seasonNumber_unique", + "nullsNotDistinct": false, + "columns": ["show_pk", "season_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.show_translations": { + "name": "show_translations", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aliases": { + "name": "aliases", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "poster": { + "name": "poster", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "thumbnail": { + "name": "thumbnail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "banner": { + "name": "banner", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "trailer_url": { + "name": "trailer_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "name_trgm": { + "name": "name_trgm", + "columns": [ + { + "expression": "\"name\" gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "tags": { + "name": "tags", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "show_translations_pk_shows_pk_fk": { + "name": "show_translations_pk_shows_pk_fk", + "tableFrom": "show_translations", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": ["pk"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "show_translations_pk_language_pk": { + "name": "show_translations_pk_language_pk", + "columns": ["pk", "language"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.shows": { + "name": "shows", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "shows_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "show_kind", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "genres": { + "name": "genres", + "type": "genres[]", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "runtime": { + "name": "runtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "show_status", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "start_air": { + "name": "start_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_air": { + "name": "end_air", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "original_language": { + "name": "original_language", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "collection_pk": { + "name": "collection_pk", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kind": { + "name": "kind", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hash", + "with": {} + }, + "rating": { + "name": "rating", + "columns": [ + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "startAir": { + "name": "startAir", + "columns": [ + { + "expression": "start_air", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shows_collection_pk_shows_pk_fk": { + "name": "shows_collection_pk_shows_pk_fk", + "tableFrom": "shows", + "tableTo": "shows", + "schemaTo": "kyoo", + "columnsFrom": ["collection_pk"], + "columnsTo": ["pk"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "shows_id_unique": { + "name": "shows_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "shows_slug_unique": { + "name": "shows_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": { + "rating_valid": { + "name": "rating_valid", + "value": "\"kyoo\".\"shows\".\"rating\" between 0 and 100" + }, + "runtime_valid": { + "name": "runtime_valid", + "value": "\"kyoo\".\"shows\".\"runtime\" >= 0" + } + }, + "isRLSEnabled": false + }, + "kyoo.entry_video_join": { + "name": "entry_video_join", + "schema": "kyoo", + "columns": { + "entry": { + "name": "entry", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "video": { + "name": "video", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "entry_video_join_entry_entries_pk_fk": { + "name": "entry_video_join_entry_entries_pk_fk", + "tableFrom": "entry_video_join", + "tableTo": "entries", + "schemaTo": "kyoo", + "columnsFrom": ["entry"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "entry_video_join_video_videos_pk_fk": { + "name": "entry_video_join_video_videos_pk_fk", + "tableFrom": "entry_video_join", + "tableTo": "videos", + "schemaTo": "kyoo", + "columnsFrom": ["video"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entry_video_join_entry_video_pk": { + "name": "entry_video_join_entry_video_pk", + "columns": ["entry", "video"] + } + }, + "uniqueConstraints": { + "entry_video_join_slug_unique": { + "name": "entry_video_join_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "kyoo.videos": { + "name": "videos", + "schema": "kyoo", + "columns": { + "pk": { + "name": "pk", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "videos_pk_seq", + "schema": "kyoo", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rendering": { + "name": "rendering", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "part": { + "name": "part", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "guess": { + "name": "guess", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "videos_id_unique": { + "name": "videos_id_unique", + "nullsNotDistinct": false, + "columns": ["id"] + }, + "videos_path_unique": { + "name": "videos_path_unique", + "nullsNotDistinct": false, + "columns": ["path"] + } + }, + "policies": {}, + "checkConstraints": { + "part_pos": { + "name": "part_pos", + "value": "\"kyoo\".\"videos\".\"part\" >= 0" + }, + "version_pos": { + "name": "version_pos", + "value": "\"kyoo\".\"videos\".\"version\" >= 0" + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "kyoo.entry_type": { + "name": "entry_type", + "schema": "kyoo", + "values": ["unknown", "episode", "movie", "special", "extra"] + }, + "kyoo.genres": { + "name": "genres", + "schema": "kyoo", + "values": [ + "action", + "adventure", + "animation", + "comedy", + "crime", + "documentary", + "drama", + "family", + "fantasy", + "history", + "horror", + "music", + "mystery", + "romance", + "science-fiction", + "thriller", + "war", + "western", + "kids", + "reality", + "politics", + "soap", + "talk" + ] + }, + "kyoo.show_kind": { + "name": "show_kind", + "schema": "kyoo", + "values": ["serie", "movie", "collection"] + }, + "kyoo.show_status": { + "name": "show_status", + "schema": "kyoo", + "values": ["unknown", "finished", "airing", "planned"] + } + }, + "schemas": { + "kyoo": "kyoo" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/api/src/models/movie.ts b/api/src/models/movie.ts index 452d6071..d0c0aa7f 100644 --- a/api/src/models/movie.ts +++ b/api/src/models/movie.ts @@ -1,5 +1,6 @@ import { t } from "elysia"; import type { Prettify } from "~/utils"; +import { SeedCollection } from "./collections"; import { bubble, registerExamples } from "./examples"; import { bubbleImages } from "./examples/bubble"; import { @@ -12,7 +13,6 @@ import { TranslationRecord, } from "./utils"; import { Video } from "./video"; -import { SeedCollection } from "./collections"; export const MovieStatus = t.UnionEnum(["unknown", "finished", "planned"]); export type MovieStatus = typeof MovieStatus.static; diff --git a/api/src/models/serie.ts b/api/src/models/serie.ts index 5a408d9b..dafeeefe 100644 --- a/api/src/models/serie.ts +++ b/api/src/models/serie.ts @@ -1,4 +1,6 @@ import { t } from "elysia"; +import type { Prettify } from "~/utils"; +import { SeedCollection } from "./collections"; import { SeedEntry, SeedExtra } from "./entry"; import { bubbleImages, madeInAbyss, registerExamples } from "./examples"; import { SeedSeason } from "./season"; @@ -7,8 +9,6 @@ import { Genre } from "./utils/genres"; import { Image, SeedImage } from "./utils/image"; import { Language, TranslationRecord } from "./utils/language"; import { Resource } from "./utils/resource"; -import { Prettify } from "~/utils"; -import { SeedCollection } from "./collections"; export const SerieStatus = t.UnionEnum([ "unknown",