diff --git a/api/src/controllers/series.ts b/api/src/controllers/series.ts new file mode 100644 index 00000000..2879abad --- /dev/null +++ b/api/src/controllers/series.ts @@ -0,0 +1,11 @@ +import { Elysia, t } from "elysia"; +import { Serie } from "../models/serie"; + +export const series = new Elysia({ prefix: "/series" }) + .model({ + serie: Serie, + error: t.Object({}), + }) + .get("/:id", () => "hello" as unknown as Serie, { + response: { 200: "serie" }, + }); diff --git a/api/src/index.ts b/api/src/index.ts index 25976b32..7295c631 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -3,6 +3,7 @@ import { swagger } from "@elysiajs/swagger"; import { migrate } from "drizzle-orm/node-postgres/migrator"; import { Elysia } from "elysia"; import { movies } from "./controllers/movies"; +import { series } from "./controllers/series"; import { videos } from "./controllers/videos"; import { db } from "./db"; @@ -33,6 +34,7 @@ const app = new Elysia() .use(swagger()) .get("/", () => "Hello Elysia") .use(movies) + .use(series) .use(videos) .listen(3000); diff --git a/api/src/models/examples.ts b/api/src/models/examples/bubble.ts similarity index 78% rename from api/src/models/examples.ts rename to api/src/models/examples/bubble.ts index 757c7169..58acab3f 100644 --- a/api/src/models/examples.ts +++ b/api/src/models/examples/bubble.ts @@ -1,22 +1,4 @@ -import type { TSchema } from "elysia"; -import type { CompleteVideo } from "./video"; - -export const registerExamples = ( - schema: T, - ...examples: (T["static"] | undefined)[] -) => { - for (const example of examples) { - if (!example) continue; - for (const [key, val] of Object.entries(example)) { - const prop = schema.properties[ - key as keyof typeof schema.properties - ] as TSchema; - if (!prop) continue; - prop.examples ??= []; - prop.examples.push(val); - } - } -}; +import type { CompleteVideo } from "../video"; export const bubble: CompleteVideo = { id: "0934da28-4a49-404e-920b-a150404a3b6d", diff --git a/api/src/models/examples/index.ts b/api/src/models/examples/index.ts new file mode 100644 index 00000000..8a2e2f3d --- /dev/null +++ b/api/src/models/examples/index.ts @@ -0,0 +1,27 @@ +import type { TSchema } from "elysia"; + +export const registerExamples = ( + schema: T, + ...examples: (T["static"] | undefined)[] +) => { + if ("anyOf" in schema) { + for (const union of schema.anyOf) { + registerExamples(union, examples); + } + return; + } + for (const example of examples) { + if (!example) continue; + for (const [key, val] of Object.entries(example)) { + const prop = schema.properties[ + key as keyof typeof schema.properties + ] as TSchema; + if (!prop) continue; + prop.examples ??= []; + prop.examples.push(val); + } + } +}; + +export { bubble } from "./bubble"; +export { madeInAbyss } from "./made-in-abyss"; diff --git a/api/src/models/examples/made-in-abyss.ts b/api/src/models/examples/made-in-abyss.ts new file mode 100644 index 00000000..b4390358 --- /dev/null +++ b/api/src/models/examples/made-in-abyss.ts @@ -0,0 +1,79 @@ +import type { Serie } from "../serie"; + +export const madeInAbyss: Serie = { + id: "04bcf2ac-3c09-42f6-8357-b003798f9562", + slug: "made-in-abyss", + name: "Made in Abyss", + tagline: "How far would you go… for the ones you love?", + aliases: [ + "Made in Abyss: The Golden City of the Scorching Sun", + "Meidoinabisu", + "Meidoinabisu: Retsujitsu no ôgonkyô", + ], + description: + "Located in the center of a remote island, the Abyss is the last unexplored region, a huge and treacherous fathomless hole inhabited by strange creatures where only the bravest adventurers descend in search of ancient relics. In the upper levels of the Abyss, Riko, a girl who dreams of becoming an explorer, stumbles upon a mysterious little boy.", + tags: [ + "android", + "amnesia", + "post-apocalyptic future", + "exploration", + "friendship", + "mecha", + "survival", + "curse", + "tragedy", + "orphan", + "based on manga", + "robot", + "dark fantasy", + "seinen", + "anime", + "drastic change of life", + "fantasy", + "adventure", + ], + genres: [ + "animation", + "drama", + "action", + "adventure", + "science-fiction", + "fantasy", + ], + status: "finished", + rating: 84, + runtime: 24, + originalLanguage: "ja", + startAir: "2017-07-07", + endAir: "2022-09-28", + poster: { + id: "8205a20e-d91f-804c-3a84-4e4dc6202d66", + source: + "https://image.tmdb.org/t/p/original/4Bh9qzB1Kau4RDaVQXVFdoJ0HcE.jpg", + blurhash: "LZGlS3XTD%jE~Wf,SeV@%2o|WERj", + }, + thumbnail: { + id: "819d816c-88f6-9f3a-b5e7-ce3daaffbac4", + source: + "https://image.tmdb.org/t/p/original/Df9XrvZFIeQfLKfu8evRmzvRsd.jpg", + blurhash: "LmJtk{kq~q%2bbWCxaV@.8RixuNG", + }, + logo: { + id: "23cb7b06-8406-2288-8e40-08bfc16180b5", + source: + "https://image.tmdb.org/t/p/original/7hY3Q4GhkiYPBfn4UoVg0AO4Zgk.png", + blurhash: "LKGaa%M{0zbI#7$%bbofGGw^wcw{", + }, + banner: null, + trailerUrl: "https://www.youtube.com/watch?v=ePOyy6Wlk4s", + externalId: { + themoviedatabase: { + dataId: "72636", + link: "https://www.themoviedb.org/tv/72636", + }, + imdb: { dataId: "tt7222086", link: "https://www.imdb.com/title/tt7222086" }, + tvdb: { dataId: "326109", link: null }, + }, + createdAt: "2023-11-29T11:12:11.949503Z", + nextRefresh: "2025-01-07T11:42:50.948248Z", +}; diff --git a/api/src/models/movie.ts b/api/src/models/movie.ts index 3555a5e4..7a0d2271 100644 --- a/api/src/models/movie.ts +++ b/api/src/models/movie.ts @@ -1,10 +1,13 @@ import { t } from "elysia"; -import { Genre, ShowStatus } from "./show"; +import { Genre } from "./show"; import { Image } from "./image"; import { ExternalId } from "./external-id"; import { bubble, registerExamples } from "./examples"; import { comment } from "../utils"; +export const MovieStatus = t.UnionEnum(["unknown", "finished", "planned"]); +export type MovieStatus = typeof MovieStatus.static; + export const Movie = t.Object({ id: t.String({ format: "uuid" }), slug: t.String(), @@ -16,8 +19,10 @@ export const Movie = t.Object({ genres: t.Array(Genre), rating: t.Nullable(t.Number({ minimum: 0, maximum: 100 })), - status: ShowStatus, - runtime: t.Nullable(t.Number({ minimum: 0 })), + status: MovieStatus, + runtime: t.Nullable( + t.Number({ minimum: 0, description: "Runtime of the movie in minutes." }), + ), airDate: t.Nullable(t.String({ format: "date" })), originalLanguage: t.Nullable( diff --git a/api/src/models/serie.ts b/api/src/models/serie.ts new file mode 100644 index 00000000..cc0850cf --- /dev/null +++ b/api/src/models/serie.ts @@ -0,0 +1,61 @@ +import { t } from "elysia"; +import { Genre } from "./show"; +import { Image } from "./image"; +import { ExternalId } from "./external-id"; +import { madeInAbyss , registerExamples } from "./examples"; +import { comment } from "../utils"; + +export const SerieStatus = t.UnionEnum([ + "unknown", + "finished", + "airing", + "planned", +]); +export type SerieStatus = typeof SerieStatus.static; + +export const Serie = t.Object({ + id: t.String({ format: "uuid" }), + slug: t.String(), + name: t.String(), + description: t.Nullable(t.String()), + tagline: t.Nullable(t.String()), + aliases: t.Array(t.String()), + tags: t.Array(t.String()), + + genres: t.Array(Genre), + rating: t.Nullable(t.Number({ minimum: 0, maximum: 100 })), + status: SerieStatus, + runtime: t.Nullable( + t.Number({ + minimum: 0, + description: "Average runtime of all episodes (in minutes.)", + }), + ), + + startAir: t.Nullable(t.String({ format: "date" })), + endAir: t.Nullable(t.String({ format: "date" })), + originalLanguage: t.Nullable( + t.String({ + description: comment` + The language code this movie was made in. + This is a BCP 47 language code (the IETF Best Current Practices on Tags for Identifying Languages). + BCP 47 is also known as RFC 5646. It subsumes ISO 639 and is backward compatible with it. + `, + }), + ), + + 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" }), + + externalId: ExternalId, +}); + +export type Serie = typeof Serie.static; + +registerExamples(Serie, madeInAbyss); diff --git a/api/src/models/show.ts b/api/src/models/show.ts index 9f1c7e63..dbac93b6 100644 --- a/api/src/models/show.ts +++ b/api/src/models/show.ts @@ -1,13 +1,5 @@ import { t } from "elysia"; -export const ShowStatus = t.UnionEnum([ - "unknown", - "finished", - "airing", - "planned", -]); -export type ShowStatus = typeof ShowStatus.static; - export const Genre = t.UnionEnum([ "action", "adventure", diff --git a/api/src/seed.ts b/api/src/seed.ts new file mode 100644 index 00000000..24aa2b17 --- /dev/null +++ b/api/src/seed.ts @@ -0,0 +1,7 @@ +import { db } from "./db"; +import { videos } from "./db/schema/videos"; +import { Video } from "./models/video"; + +const seed = async () =>{ + db.insert(videos).values(Video.examples) +};