From 956ab51e1bbc4400d2215ba1f12d6b670102ad02 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 17 Jan 2025 23:10:56 +0100 Subject: [PATCH] Add a flag to retrieve all translations --- api/src/controllers/movies.ts | 56 ++++++++++++++++++----------------- api/src/db/schema/shows.ts | 8 +++++ api/src/models/error.ts | 15 ++++++---- api/src/models/movie.ts | 10 +++++++ 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/api/src/controllers/movies.ts b/api/src/controllers/movies.ts index c714d52f..d8a3122e 100644 --- a/api/src/controllers/movies.ts +++ b/api/src/controllers/movies.ts @@ -11,7 +11,12 @@ import { } from "~/db/schema"; import { getColumns, sqlarr } from "~/db/schema/utils"; import { bubble } from "~/models/examples"; -import { Movie, MovieStatus, MovieTranslation } from "~/models/movie"; +import { + FullMovie, + Movie, + MovieStatus, + MovieTranslation, +} from "~/models/movie"; import { Filter, type Image, @@ -49,7 +54,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) async ({ params: { id }, headers: { "accept-language": languages }, - query: { preferOriginal }, + query: { preferOriginal, with: relations }, error, set, }) => { @@ -86,7 +91,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) isUuid(id) ? eq(shows.id, id) : eq(shows.slug, id), ), with: { - translations: { + selectedTranslation: { columns: { pk: false, }, @@ -113,6 +118,13 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) ), }, }, + ...(relations.includes("translations") && { + translations: { + columns: { + pk: false, + }, + }, + }), }, }); @@ -122,7 +134,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) message: "Movie not found", }); } - const translation = ret.translations[0]; + const translation = ret.selectedTranslation[0]; if (!translation) { return error(422, { status: 422, @@ -140,6 +152,14 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) ...(ot.banner && { banner: ot.banner }), ...(ot.logo && { logo: ot.logo }), }), + ...(ret.translations && { + translations: Object.fromEntries( + ret.translations.map( + ({ language, ...translation }) => + [language, translation] as const, + ), + ), + }), }; }, { @@ -162,6 +182,10 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) `, }), ), + with: t.Array(t.UnionEnum(["translations", "videos"]), { + default: [], + description: "Include related resources in the response.", + }), }), headers: t.Object({ "accept-language": t.String({ @@ -174,13 +198,10 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) }), }), response: { - 200: { ...Movie, description: "Found" }, + 200: { ...FullMovie, description: "Found" }, 404: { ...KError, description: "No movie found with the given id or slug.", - examples: [ - { status: 404, message: "Movie not found", details: undefined }, - ], }, 422: { ...KError, @@ -189,12 +210,6 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) unavailable.) Try with another languages or add * to the list of languages to fallback to any language. `, - examples: [ - { - status: 422, - message: "Accept-Language header could not be satisfied.", - }, - ], }, }, }, @@ -227,9 +242,6 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) 404: { ...KError, description: "No movie found with the given id or slug.", - examples: [ - { status: 404, message: "Movie not found", details: undefined }, - ], }, }, }, @@ -362,16 +374,6 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) 422: { ...KError, description: "Invalid query parameters.", - examples: [ - { - status: 422, - message: - "Invalid property: slug. Expected one of genres, rating, status, runtime, airDate, originalLanguage.", - details: { - in: "slug eq bubble", - }, - }, - ], }, }, }, diff --git a/api/src/db/schema/shows.ts b/api/src/db/schema/shows.ts index 4ea29f6e..302223ac 100644 --- a/api/src/db/schema/shows.ts +++ b/api/src/db/schema/shows.ts @@ -110,6 +110,9 @@ export const showTranslations = schema.table( ); export const showsRelations = relations(shows, ({ many, one }) => ({ + selectedTranslation: many(showTranslations, { + relationName: "selectedTranslation", + }), translations: many(showTranslations, { relationName: "showTranslations" }), originalTranslation: one(showTranslations, { relationName: "originalTranslation", @@ -123,6 +126,11 @@ export const showsTrRelations = relations(showTranslations, ({ one }) => ({ fields: [showTranslations.pk], references: [shows.pk], }), + selectedTranslation: one(shows, { + relationName: "selectedTranslation", + fields: [showTranslations.pk], + references: [shows.pk], + }), originalTranslation: one(shows, { relationName: "originalTranslation", fields: [showTranslations.pk, showTranslations.language], diff --git a/api/src/models/error.ts b/api/src/models/error.ts index 4be8a968..9d2229d7 100644 --- a/api/src/models/error.ts +++ b/api/src/models/error.ts @@ -1,10 +1,15 @@ import { t } from "elysia"; -export const KError = t.Object({ - status: t.Integer(), - message: t.String(), - details: t.Optional(t.Any()), -}); +export const KError = t.Object( + { + status: t.Integer(), + message: t.String(), + details: t.Optional(t.Any()), + }, + { + examples: [{ status: 404, message: "Movie not found" }], + }, +); export type KError = typeof KError.static; export class KErrorT extends Error { diff --git a/api/src/models/movie.ts b/api/src/models/movie.ts index 2b259336..1e8dc722 100644 --- a/api/src/models/movie.ts +++ b/api/src/models/movie.ts @@ -9,6 +9,7 @@ import { } from "./utils"; import { bubble, registerExamples } from "./examples"; import { bubbleImages } from "./examples/bubble"; +import { Video } from "./video"; export const MovieStatus = t.UnionEnum(["unknown", "finished", "planned"]); export type MovieStatus = typeof MovieStatus.static; @@ -58,6 +59,15 @@ export const Movie = t.Intersect([ ]); export type Movie = typeof Movie.static; +export const FullMovie = t.Intersect([ + Movie, + t.Object({ + translations: t.Optional(TranslationRecord(MovieTranslation)), + videos: t.Optional(t.Array(Video)), + }), +]); +export type FullMovie = typeof FullMovie.static; + export const SeedMovie = t.Intersect([ t.Omit(BaseMovie, ["id", "createdAt", "nextRefresh"]), t.Object({