From 89952185a9d712f8234dd4922305a321467f3efd Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 9 Nov 2024 11:45:44 +0100 Subject: [PATCH] Cleanup video schema & add descrpitions of api fields --- api/drizzle/0001_video.sql | 7 + api/drizzle/meta/0001_snapshot.json | 597 ++++++++++++++++++++++++++++ api/drizzle/meta/_journal.json | 7 + api/src/db/schema/videos.ts | 15 +- api/src/models/examples.ts | 5 +- api/src/models/video.ts | 28 +- 6 files changed, 647 insertions(+), 12 deletions(-) create mode 100644 api/drizzle/0001_video.sql create mode 100644 api/drizzle/meta/0001_snapshot.json diff --git a/api/drizzle/0001_video.sql b/api/drizzle/0001_video.sql new file mode 100644 index 00000000..ab8ec07c --- /dev/null +++ b/api/drizzle/0001_video.sql @@ -0,0 +1,7 @@ +ALTER TABLE "kyoo"."videos" DROP CONSTRAINT "rendering_pos";--> statement-breakpoint +ALTER TABLE "kyoo"."videos" ALTER COLUMN "rendering" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "kyoo"."videos" ALTER COLUMN "rendering" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "kyoo"."videos" ALTER COLUMN "version" SET DEFAULT 1;--> statement-breakpoint +ALTER TABLE "kyoo"."videos" ALTER COLUMN "version" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "kyoo"."videos" ADD COLUMN "slug" varchar(255) NOT NULL;--> statement-breakpoint +ALTER TABLE "kyoo"."videos" ADD CONSTRAINT "videos_slug_unique" UNIQUE("slug"); \ No newline at end of file diff --git a/api/drizzle/meta/0001_snapshot.json b/api/drizzle/meta/0001_snapshot.json new file mode 100644 index 00000000..400a806a --- /dev/null +++ b/api/drizzle/meta/0001_snapshot.json @@ -0,0 +1,597 @@ +{ + "id": "32090852-33a7-430a-9df1-97608c063124", + "prevId": "82560792-5f4a-4723-9543-808719ade682", + "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": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "season_number": { + "name": "season_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "episode_number": { + "name": "episode_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "entry_type", + "typeSchema": "kyoo", + "primaryKey": false, + "notNull": true + }, + "air_date": { + "name": "air_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "runtime": { + "name": "runtime", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "thumbnails": { + "name": "thumbnails", + "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": false, + "default": "now()" + }, + "next_refresh": { + "name": "next_refresh", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "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": "\"entries\".\"order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "kyoo.entries_translation": { + "name": "entries_translation", + "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 + } + }, + "indexes": {}, + "foreignKeys": { + "entries_translation_pk_entries_pk_fk": { + "name": "entries_translation_pk_entries_pk_fk", + "tableFrom": "entries_translation", + "tableTo": "entries", + "schemaTo": "kyoo", + "columnsFrom": ["pk"], + "columnsTo": ["pk"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entries_translation_pk_language_pk": { + "name": "entries_translation_pk_language_pk", + "columns": ["pk", "language"] + } + }, + "uniqueConstraints": {}, + "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 + }, + "trailer_url": { + "name": "trailer_url", + "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 + }, + "logo": { + "name": "logo", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "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 + }, + "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": {}, + "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": "\"shows\".\"rating\" between 0 and 100" + }, + "runtime_valid": { + "name": "runtime_valid", + "value": "\"shows\".\"runtime\" >= 0" + } + }, + "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()" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "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_slug_unique": { + "name": "videos_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "videos_path_unique": { + "name": "videos_path_unique", + "nullsNotDistinct": false, + "columns": ["path"] + } + }, + "policies": {}, + "checkConstraints": { + "part_pos": { + "name": "part_pos", + "value": "\"videos\".\"part\" >= 0" + }, + "version_pos": { + "name": "version_pos", + "value": "\"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"] + }, + "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/drizzle/meta/_journal.json b/api/drizzle/meta/_journal.json index 3e5d4a5e..d3522956 100644 --- a/api/drizzle/meta/_journal.json +++ b/api/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1731105746157, "tag": "0000_init", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1731149082556, + "tag": "0001_video", + "breakpoints": true } ] } diff --git a/api/src/db/schema/videos.ts b/api/src/db/schema/videos.ts index 86574f81..efe32d24 100644 --- a/api/src/db/schema/videos.ts +++ b/api/src/db/schema/videos.ts @@ -1,5 +1,12 @@ import { sql } from "drizzle-orm"; -import { check, integer, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { + check, + integer, + text, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; import { schema } from "./utils"; export const videos = schema.table( @@ -7,15 +14,15 @@ export const videos = schema.table( { pk: integer().primaryKey().generatedAlwaysAsIdentity(), id: uuid().notNull().unique().defaultRandom(), + slug: varchar({ length: 255 }).notNull().unique(), path: text().notNull().unique(), - rendering: integer(), + rendering: text().notNull(), part: integer(), - version: integer(), + version: integer().notNull().default(1), createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(), }, (t) => [ - check("rendering_pos", sql`${t.rendering} >= 0`), check("part_pos", sql`${t.part} >= 0`), check("version_pos", sql`${t.version} >= 0`), ], diff --git a/api/src/models/examples.ts b/api/src/models/examples.ts index 8fa3e73d..757c7169 100644 --- a/api/src/models/examples.ts +++ b/api/src/models/examples.ts @@ -20,9 +20,10 @@ export const registerExamples = ( export const bubble: CompleteVideo = { id: "0934da28-4a49-404e-920b-a150404a3b6d", + slug: "bubble", path: "/video/Bubble/Bubble (2022).mkv", - rendering: 0, - part: 0, + rendering: "459429fa062adeebedcc2bb04b9965de0262bfa453369783132d261be79021bd", + part: null, version: 1, createdAt: "2023-11-29T11:42:06.030838Z", movie: { diff --git a/api/src/models/video.ts b/api/src/models/video.ts index e8cdb9fa..794c2a94 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -1,19 +1,35 @@ import { t } from "elysia"; -import { Movie } from "./movie"; +import { comment } from "../utils"; import { bubble, registerExamples } from "./examples"; +import { Movie } from "./movie"; export const Video = t.Object({ id: t.String({ format: "uuid" }), + slug: t.String(), path: t.String(), - rendering: t.Number({ minimum: 0 }), - part: t.Nullable(t.Number({ minimum: 0 })), - version: t.Nullable( + rendering: t.String({ + description: comment` + Sha of the path except \`part\` & \`version\`. + If there are multiples files for the same entry, it can be used to know if each + file is the same content or if it's unrelated (like long-version vs short-version, monochrome vs colored etc) + `, + }), + part: t.Nullable( t.Number({ minimum: 0, - description: - "Kyoo will prefer playing back the highest `version` number if there's rendering.", + description: comment` + If the episode/movie is split into multiples files, the \`part\` field can be used to order them. + The \`rendering\` field is used to know if two parts are in the same group or + if it's another unrelated video file of the same entry. + `, }), ), + version: t.Number({ + minimum: 0, + default: 1, + description: + "Kyoo will prefer playing back the highest `version` number if there are multiples rendering.", + }), createdAt: t.String({ format: "date-time" }), });