diff --git a/api/src/db/schema/entries.ts b/api/src/db/schema/entries.ts index 72d1f3f5..6d8dc4b3 100644 --- a/api/src/db/schema/entries.ts +++ b/api/src/db/schema/entries.ts @@ -79,6 +79,7 @@ export const entriesTranslation = schema.table( language: language().notNull(), name: text(), description: text(), + tagline: text(), }, (t) => [primaryKey({ columns: [t.pk, t.language] })], ); diff --git a/api/src/db/schema/seasons.ts b/api/src/db/schema/seasons.ts index 419d3400..da65f3d0 100644 --- a/api/src/db/schema/seasons.ts +++ b/api/src/db/schema/seasons.ts @@ -57,7 +57,6 @@ export const seasonTranslation = schema.table( description: text(), poster: image(), thumbnail: image(), - logo: image(), banner: image(), }, (t) => [primaryKey({ columns: [t.pk, t.language] })], diff --git a/api/src/models/entry.ts b/api/src/models/entry.ts index aa2e209a..fb818235 100644 --- a/api/src/models/entry.ts +++ b/api/src/models/entry.ts @@ -2,6 +2,7 @@ import { t } from "elysia"; import { Image } from "./utils/image"; import { ExternalId, EpisodeId } from "./utils/external-id"; import { comment } from "../utils"; +import { madeInAbyss, registerExamples } from "./examples"; const BaseEntry = t.Object({ id: t.String({ format: "uuid" }), @@ -14,7 +15,7 @@ const BaseEntry = t.Object({ ), thumbnail: t.Nullable(Image), - createtAt: t.String({ format: "date-time" }), + createdAt: t.String({ format: "date-time" }), nextRefresh: t.String({ format: "date-time" }), }); @@ -32,13 +33,15 @@ export type Episode = typeof Episode.static; export const MovieEntry = t.Intersect( [ - BaseEntry, + t.Omit(BaseEntry, ["thumbnail"]), t.Object({ kind: t.Literal("movie"), order: t.Number({ minimum: 1, description: "Absolute playback order. Can be mixed with episodes.", }), + tagline: t.String(), + poster: BaseEntry.properties.thumbnail, externalId: ExternalId, }), ], @@ -80,7 +83,6 @@ export const ExtraType = t.UnionEnum([ "behind-the-scenes", "deleted-scenes", "bloopers", - "mini-story", ]); export type ExtraType = typeof ExtraType.static; @@ -88,9 +90,7 @@ export const Extra = t.Intersect( [ BaseEntry, t.Object({ - kind: t.Literal("extra"), - number: t.Number({ minimum: 1 }), - extraType: ExtraType, + kind: ExtraType, // not sure about this id type externalId: EpisodeId, }), @@ -122,3 +122,17 @@ export type UnknownEntry = typeof UnknownEntry.static; export const Entry = t.Union([Episode, MovieEntry, Special]); export type Entry = typeof Entry.static; + +registerExamples( + Episode, + ...madeInAbyss.entries.filter((x) => x.kind === "episode"), +); +registerExamples( + MovieEntry, + ...madeInAbyss.entries.filter((x) => x.kind === "movie"), +); +registerExamples( + Special, + ...madeInAbyss.entries.filter((x) => x.kind === "special"), +); +registerExamples(Extra, ...madeInAbyss.extras); diff --git a/api/src/models/examples/bubble.ts b/api/src/models/examples/bubble.ts index 58acab3f..48014a71 100644 --- a/api/src/models/examples/bubble.ts +++ b/api/src/models/examples/bubble.ts @@ -1,59 +1,65 @@ -import type { CompleteVideo } from "../video"; +import type { Movie } from "../movie"; +import type { Video } from "../video"; -export const bubble: CompleteVideo = { - id: "0934da28-4a49-404e-920b-a150404a3b6d", +type CompleteMovie = Movie & { videos: Video[] }; + +export const bubble: CompleteMovie = { + id: "008f0b42-61b8-4155-857a-cbe5f40dd35d", slug: "bubble", - path: "/video/Bubble/Bubble (2022).mkv", - rendering: "459429fa062adeebedcc2bb04b9965de0262bfa453369783132d261be79021bd", - part: null, - version: 1, + name: "Bubble", + tagline: "Is she a calamity or a blessing?", + description: + "In an abandoned Tokyo overrun by bubbles and gravitational abnormalities, one gifted young man has a fateful meeting with a mysterious girl.", + aliases: ["Baburu", "バブル:2022", "Bubble"], + tags: ["adolescence", "disaster", "battle", "gravity", "anime"], + genres: ["animation", "adventure", "science-fiction", "fantasy"], + rating: 74, + status: "finished", + runtime: 101, + airDate: "2022-02-14", + originalLanguage: "ja", + poster: { + id: "befdc7dd-2a67-0704-92af-90d49eee0315", + source: + "https://image.tmdb.org/t/p/original/65dad96VE8FJPEdrAkhdsuWMWH9.jpg", + blurhash: "LFC@2F;K$%xZ5?W.MwNF0iD~MxR:", + }, + thumbnail: { + id: "b29908f3-a64d-ae98-923b-18bf7995ab04", + source: + "https://image.tmdb.org/t/p/original/a8Q2g0g7XzAF6gcB8qgn37ccb9Y.jpg", + blurhash: "LpH3afE1XAveyGS7t6V[R4xZn+S6", + }, + banner: null, + logo: { + id: "3357fad0-de40-4ca5-15e6-eb065d35be86", + source: + "https://image.tmdb.org/t/p/original/ihIs7fayAmZieMlMQbs6TWM77uf.png", + blurhash: "LMDc5#MwE0,sTKE0R*S~4mxunhb_", + }, + trailerUrl: "https://www.youtube.com/watch?v=vs7zsyIZkMM", createdAt: "2023-11-29T11:42:06.030838Z", - movie: { - id: "008f0b42-61b8-4155-857a-cbe5f40dd35d", - slug: "bubble", - name: "Bubble", - tagline: "Is she a calamity or a blessing?", - description: - "In an abandoned Tokyo overrun by bubbles and gravitational abnormalities, one gifted young man has a fateful meeting with a mysterious girl.", - aliases: ["Baburu", "バブル:2022", "Bubble"], - tags: ["adolescence", "disaster", "battle", "gravity", "anime"], - genres: ["animation", "adventure", "science-fiction", "fantasy"], - rating: 74, - status: "finished", - runtime: 101, - airDate: "2022-02-14", - originalLanguage: "ja", - poster: { - id: "befdc7dd-2a67-0704-92af-90d49eee0315", - source: - "https://image.tmdb.org/t/p/original/65dad96VE8FJPEdrAkhdsuWMWH9.jpg", - blurhash: "LFC@2F;K$%xZ5?W.MwNF0iD~MxR:", + nextRefresh: "2025-01-07T22:40:59.960952Z", + externalId: { + themoviedatabase: { + dataId: "912598", + link: "https://www.themoviedb.org/movie/912598", }, - thumbnail: { - id: "b29908f3-a64d-ae98-923b-18bf7995ab04", - source: - "https://image.tmdb.org/t/p/original/a8Q2g0g7XzAF6gcB8qgn37ccb9Y.jpg", - blurhash: "LpH3afE1XAveyGS7t6V[R4xZn+S6", - }, - banner: null, - logo: { - id: "3357fad0-de40-4ca5-15e6-eb065d35be86", - source: - "https://image.tmdb.org/t/p/original/ihIs7fayAmZieMlMQbs6TWM77uf.png", - blurhash: "LMDc5#MwE0,sTKE0R*S~4mxunhb_", - }, - trailerUrl: "https://www.youtube.com/watch?v=vs7zsyIZkMM", - createdAt: "2023-11-29T11:42:06.030838Z", - nextRefresh: "2025-01-07T22:40:59.960952Z", - externalId: { - themoviedatabase: { - dataId: "912598", - link: "https://www.themoviedb.org/movie/912598", - }, - imdb: { - dataId: "tt16360006", - link: "https://www.imdb.com/title/tt16360006", - }, + imdb: { + dataId: "tt16360006", + link: "https://www.imdb.com/title/tt16360006", }, }, + videos: [ + { + id: "0934da28-4a49-404e-920b-a150404a3b6d", + slug: "bubble", + path: "/video/Bubble/Bubble (2022).mkv", + rendering: + "459429fa062adeebedcc2bb04b9965de0262bfa453369783132d261be79021bd", + part: null, + version: 1, + createdAt: "2023-11-29T11:42:06.030838Z", + }, + ], }; diff --git a/api/src/models/examples/index.ts b/api/src/models/examples/index.ts index 8a2e2f3d..5e59939f 100644 --- a/api/src/models/examples/index.ts +++ b/api/src/models/examples/index.ts @@ -6,7 +6,13 @@ export const registerExamples = ( ) => { if ("anyOf" in schema) { for (const union of schema.anyOf) { - registerExamples(union, examples); + registerExamples(union, ...examples); + } + return; + } + if ("allOf" in schema) { + for (const intersec of schema.allOf) { + registerExamples(intersec, ...examples); } return; } diff --git a/api/src/models/examples/made-in-abyss.ts b/api/src/models/examples/made-in-abyss.ts index b4390358..b9c92490 100644 --- a/api/src/models/examples/made-in-abyss.ts +++ b/api/src/models/examples/made-in-abyss.ts @@ -1,6 +1,15 @@ +import type { Entry, Extra } from "../entry"; +import type { Season } from "../season"; import type { Serie } from "../serie"; +import type { Video } from "../video"; -export const madeInAbyss: Serie = { +type CompleteSerie = Serie & { + seasons: Season[]; + entries: (Entry & { videos: Video[] })[]; + extras: (Extra & { video: Video })[]; +}; + +export const madeInAbyss: CompleteSerie = { id: "04bcf2ac-3c09-42f6-8357-b003798f9562", slug: "made-in-abyss", name: "Made in Abyss", @@ -76,4 +85,265 @@ export const madeInAbyss: Serie = { }, createdAt: "2023-11-29T11:12:11.949503Z", nextRefresh: "2025-01-07T11:42:50.948248Z", + seasons: [ + { + id: "490aa312-53b9-43c2-845d-7cbf32642c98", + slug: "made-in-abyss-s1", + seasonNumber: 1, + name: "Season 1", + description: + "Within the depths of the Abyss, a girl named Riko stumbles upon a robot who looks like a young boy. Riko and her new friend descend into uncharted territory to unlock its mysteries, but what lies in wait for them in the darkness?", + startAir: "2017-07-07", + endAir: "2017-09-29", + poster: { + id: "1c121a2b-d3a2-4ce8-e22a-79b13dde3f7d", + source: + "https://image.tmdb.org/t/p/original/uVK3H8CgtrVgySFpdImvNXkN7RK.jpg", + blurhash: "LYG9BNkrD%V?~WS5S1WA%LbubHV[", + }, + thumbnail: null, + banner: null, + externalId: { + themoviedatabase: { + serieId: "72636", + season: 1, + link: "https://www.themoviedb.org/tv/72636/season/1", + }, + }, + createdAt: "2023-11-29T11:12:13.008151Z", + nextRefresh: "2025-01-07T11:37:50.151836Z", + }, + { + id: "135af9ae-a8eb-4110-a4e4-05eee49e2d76", + slug: "made-in-abyss-s2", + seasonNumber: 2, + name: "The Golden City of the Scorching Sun", + description: + "Set directly after the events of Made in Abyss: Dawn of the Deep Soul, the fifth installment of Made in Abyss covers the adventure of Reg, Riko and Nanachi in the Sixth Layer, The Capital of the Unreturned.", + startAir: "2022-07-06", + endAir: "2022-09-28", + poster: { + id: "a03c57d7-4032-7d97-083a-9a6e51d5f1e7", + source: + "https://image.tmdb.org/t/p/original/clC2erfUqIezhET67Gz9fcKD1L2.jpg", + blurhash: "LpNTRGx]s9oz~WbJRPoft7RjV@a|", + }, + thumbnail: null, + banner: null, + externalId: { + themoviedatabase: { + serieId: "72636", + season: 2, + link: "https://www.themoviedb.org/tv/72636/season/2", + }, + }, + createdAt: "2023-11-29T11:12:13.630306Z", + nextRefresh: "2025-01-07T11:09:19.552971Z", + }, + ], + entries: [ + { + kind: "episode", + id: "ab912364-61c8-4752-ac93-5802212467d8", + slug: "made-in-abyss-s1e13", + order: 13, + seasonNumber: 1, + episodeNumber: 13, + name: "The Challengers", + description: + "Nanachi and Mitty's past is revealed. How did they become what they are and who is responsible for it? Meanwhile, Riko is on the mend after her injuries.", + runtime: 47, + airDate: "2017-09-29", + thumbnail: { + id: "c2bfd626-bfdb-dee8-caa6-b6a7e7cb74ad", + source: + "https://image.tmdb.org/t/p/original/j9t1quh24suXxBetV7Q77YngID6.jpg", + blurhash: "L370#nD*^jEN}r$$$%J8i_-URkNc", + }, + externalId: { + themoviedatabase: { + serieId: "72636", + season: 1, + episode: 13, + link: "https://www.themoviedb.org/tv/72636/season/1/episode/13", + }, + }, + createdAt: "2024-10-06T20:09:09.28103Z", + nextRefresh: "2024-12-06T20:08:42.366583Z", + videos: [ + { + id: "0905bddd-8b93-403c-9b9c-db472e55d6cc", + slug: "made-in-abyss-s1e13", + path: "/video/Made in Abyss/Made in Abyss S01E13.mkv", + rendering: + "e27f226fe5e8d87cd396d0c3d24e1b1135aa563fcfca081bf68c6a71b44de107", + part: null, + version: 1, + createdAt: "2024-10-06T20:09:09.28103Z", + }, + ], + }, + { + kind: "special", + id: "1a83288a-3089-447f-9710-94297d614c51", + slug: "made-in-abyss-ova3", + // beween s1e13 & movie (which has 13.5 for the `order field`) + order: 13.25, + number: 3, + name: "Maruruk's Everday 3 - Cleaning", + description: + "Short played before Made in Abyss Movie 3: Dawn of the Deep Soul in Japan's theatrical screenings before the main movie from 2020-01-17 to 2020-01-23.", + runtime: 3, + airDate: "2020-01-31", + thumbnail: { + id: "f4ac4b0a-c857-ea95-4042-601314a26e71", + source: + "https://image.tmdb.org/t/p/original/4cMeg2ihvACsGVaSUcQJJZd96Je.jpg", + blurhash: "LAD,Pg%dc}tPDQfk.7kBo|ayR7WC", + }, + externalId: { + themoviedatabase: { + serieId: "72636", + season: 0, + episode: 3, + link: "https://www.themoviedb.org/tv/72636/season/0/episode/3", + }, + }, + createdAt: "2024-10-06T20:09:17.551272Z", + nextRefresh: "2024-12-06T20:08:29.463394Z", + videos: [ + { + id: "9153f7dc-b635-4a04-a2db-9c08ea205ec3", + slug: "made-in-abyss-ova3", + path: "/video/Made in Abyss/Made in Abyss S00E03.mkv", + rendering: + "0391acf2268983de705f65381d252f1b0cd3c3563209303dc50cf71ab400ebf4", + part: null, + version: 1, + createdAt: "2024-10-06T20:09:17.551272Z", + }, + ], + }, + { + kind: "movie", + id: "59312db0-df8c-446e-be26-2b2107d0cbde", + slug: "made-in-abyss-dawn-of-the-deep-soul", + order: 13.5, + name: "Made in Abyss: Dawn of the Deep Soul", + tagline: "Defy the darkness", + description: + "A continuation of the epic adventure of plucky Riko and Reg who are joined by their new friend Nanachi. Together they descend into the Abyss' treacherous fifth layer, the Sea of Corpses, and encounter the mysterious Bondrewd, a legendary White Whistle whose shadow looms over Nanachi's troubled past. Bondrewd is ingratiatingly hospitable, but the brave adventurers know things are not always as they seem in the enigmatic Abyss.", + runtime: 105, + airDate: "2020-01-17", + poster: { + id: "f4ac4b0a-c857-ea95-4042-601314a26e71", + source: + "https://image.tmdb.org/t/p/original/4cMeg2ihvACsGVaSUcQJJZd96Je.jpg", + blurhash: "LAD,Pg%dc}tPDQfk.7kBo|ayR7WC", + }, + externalId: { + themoviedatabase: { + dataId: "72636", + link: "https://www.themoviedb.org/tv/72636/season/0/episode/3", + }, + }, + createdAt: "2024-10-06T20:09:17.551272Z", + nextRefresh: "2024-12-06T20:08:29.463394Z", + videos: [ + { + id: "d3cedfc5-23f4-4aab-b4d3-98bef2954442", + slug: "made-in-abyss-dawn-of-the-deep-soul", + path: "/video/Made in Abyss/Made in Abyss Dawn of the Deep Soul.mkv", + rendering: + "a59ba5d88a4935d900db312422eec6f16827ce2572cc8c0eb6c8fffc5e235d6d", + part: null, + version: 1, + createdAt: "2024-10-06T20:09:17.551272Z", + }, + ], + }, + { + kind: "episode", + id: "bd155be3-39d0-4253-bb29-a60bedb62943", + slug: "made-in-abyss-s2e1", + order: 14, + seasonNumber: 2, + episodeNumber: 1, + name: "The Compass Pointed to the Darkness", + description: + "An old man speaks of a golden city that lies within a devouring abyss somewhere in uncharted waters. One explorer may be the key to finding both.", + runtime: 23, + airDate: "2022-07-06", + thumbnail: { + id: "072da617-f349-4a68-eb27-d097624b373c", + source: + "https://image.tmdb.org/t/p/original/Tgu6E3aMf7sFHFbEIMEjetnpMi.jpg", + blurhash: "LOI#x]yE01xtE2D*kWt7NGjENGM|", + }, + externalId: { + themoviedatabase: { + serieId: "72636", + season: 2, + episode: 1, + link: "https://www.themoviedb.org/tv/72636/season/2/episode/1", + }, + }, + createdAt: "2024-10-06T20:09:05.651996Z", + nextRefresh: "2024-12-06T20:08:22.854073Z", + videos: [ + { + id: "3cbcc337-f1da-486a-93bd-c705a58545eb", + slug: "made-in-abyss-s2e1-p1", + path: "/video/Made in Abyss/Made In Abyss S02E01 Part 1.mkv", + rendering: + "6239d558696fd1cbcd70a67346e748382fe141bbe7ea01a5d702cdcc02aa996f", + part: 1, + version: 1, + createdAt: "2024-10-06T20:09:05.651996Z", + }, + { + id: "67b37a00-7459-4287-9bbf-e058675850b5", + slug: "made-in-abyss-s2e1-p2", + path: "/video/Made in Abyss/Made In Abyss S02E01 Part 2.mkv", + rendering: + "6239d558696fd1cbcd70a67346e748382fe141bbe7ea01a5d702cdcc02aa996f", + part: 2, + version: 1, + createdAt: "2024-10-06T20:09:05.651996Z", + }, + ], + }, + ], + extras: [ + { + kind: "behind-the-scenes", + id: "a9b27fcc-9423-44ad-b875-d35a7a25b613", + slug: "made-in-abyss-the-making-of-01", + name: "The Making of MADE IN ABYSS 01", + description: null, + runtime: 17, + airDate: "2017-10-25", + thumbnail: null, + externalId: { + themoviedatabase: { + serieId: "72636", + season: 0, + episode: 13, + link: "https://thetvdb.com/series/made-in-abyss/episodes/8835068", + }, + }, + createdAt: "2024-10-06T20:09:05.651996Z", + nextRefresh: "2024-12-06T20:08:22.854073Z", + video: { + id: "ee3f58eb-0f72-423e-b247-0695cfabfa88", + slug: "made-in-abyss-s2e1-p2", + path: "/video/Made in Abyss/Made In Abyss S02E01 Part 2.mkv", + rendering: + "6239d558696fd1cbcd70a67346e748382fe141bbe7ea01a5d702cdcc02aa996f", + part: 2, + version: 1, + createdAt: "2024-10-06T20:09:05.651996Z", + }, + }, + ], }; diff --git a/api/src/models/movie.ts b/api/src/models/movie.ts index f6920840..263fa27f 100644 --- a/api/src/models/movie.ts +++ b/api/src/models/movie.ts @@ -49,4 +49,4 @@ export const Movie = t.Object({ export type Movie = typeof Movie.static; -registerExamples(Movie, bubble.movie); +registerExamples(Movie, bubble); diff --git a/api/src/models/season.ts b/api/src/models/season.ts index b40f2e7f..2b09c565 100644 --- a/api/src/models/season.ts +++ b/api/src/models/season.ts @@ -1,6 +1,7 @@ import { t } from "elysia"; import { Image } from "./utils/image"; import { SeasonId } from "./utils/external-id"; +import { madeInAbyss, registerExamples } from "./examples"; export const Season = t.Object({ id: t.String({ format: "uuid" }), @@ -9,11 +10,12 @@ export const Season = t.Object({ name: t.Nullable(t.String()), description: t.Nullable(t.String()), + startAir: t.Nullable(t.String({ format: "date" })), + endAir: t.Nullable(t.String({ format: "date" })), + poster: t.Nullable(Image), thumbnail: t.Nullable(Image), banner: t.Nullable(Image), - logo: t.Nullable(Image), - trailerUrl: t.Nullable(t.String()), createdAt: t.String({ format: "date-time" }), nextRefresh: t.String({ format: "date-time" }), @@ -21,3 +23,5 @@ export const Season = t.Object({ externalId: SeasonId, }); export type Season = typeof Season.static; + +registerExamples(Season, ...madeInAbyss.seasons); diff --git a/api/src/models/video.ts b/api/src/models/video.ts index 794c2a94..bb6f9cb6 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -36,21 +36,4 @@ export const Video = t.Object({ export type Video = typeof Video.static; -registerExamples(Video, bubble); - -export const CompleteVideo = t.Intersect([ - Video, - t.Union([ - t.Object({ - movie: Movie, - episodes: t.Optional(t.Never()), - }), - t.Object({ - // TODO: implement that - episodes: t.Array(t.Object({})), - movie: t.Optional(t.Never()), - }), - ]), -]); - -export type CompleteVideo = typeof CompleteVideo.static; +registerExamples(Video, ...bubble.videos);