diff --git a/api/src/controllers/seed/index.ts b/api/src/controllers/seed/index.ts index 467462da..8a5dac11 100644 --- a/api/src/controllers/seed/index.ts +++ b/api/src/controllers/seed/index.ts @@ -1,7 +1,7 @@ import Elysia from "elysia"; import { Movie, SeedMovie } from "~/models/movie"; import { seedMovie, SeedMovieResponse } from "./movies"; -import { Resource } from "~/models/utils"; +import { Resource, validateTranslations } from "~/models/utils"; import { comment } from "~/utils"; import { KError } from "~/models/error"; @@ -14,6 +14,9 @@ export const seed = new Elysia() .post( "/movies", async ({ body, error }) => { + const err = validateTranslations(body.translations); + if (err) return error(400, err); + const { status, ...ret } = await seedMovie(body); return error(status, ret); }, diff --git a/api/src/db/schema/shows.ts b/api/src/db/schema/shows.ts index ce406f56..2f43e736 100644 --- a/api/src/db/schema/shows.ts +++ b/api/src/db/schema/shows.ts @@ -1,4 +1,4 @@ -import { sql } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; import { check, date, @@ -108,3 +108,14 @@ export const showTranslations = schema.table( }, (t) => [primaryKey({ columns: [t.pk, t.language] })], ); + +export const showsRelations = relations(shows, ({ many }) => ({ + translations: many(showTranslations, { relationName: "showTranslations" }), +})); +export const showsTrRelations = relations(showTranslations, ({ one }) => ({ + show: one(shows, { + relationName: "showTranslations", + fields: [showTranslations.pk], + references: [shows.pk], + }), +})); diff --git a/api/src/index.ts b/api/src/index.ts index 00f1b082..5fb5021f 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -12,6 +12,7 @@ import { videos } from "./controllers/videos"; import { db } from "./db"; import { Image } from "./models/utils"; import { comment } from "./utils"; +import { base } from "./base"; await migrate(db, { migrationsSchema: "kyoo", migrationsFolder: "./drizzle" }); @@ -37,6 +38,7 @@ if (!secret) { } const app = new Elysia() + .use(base) .use(jwt({ secret })) .use( swagger({ diff --git a/api/src/models/utils/language.ts b/api/src/models/utils/language.ts index 302e67f9..b843df0b 100644 --- a/api/src/models/utils/language.ts +++ b/api/src/models/utils/language.ts @@ -1,6 +1,28 @@ import { FormatRegistry } from "@sinclair/typebox"; import { t } from "elysia"; import { comment } from "../../utils"; +import type { KError } from "../error"; + +export const validateTranslations = ( + translations: Record, +): KError | null => { + for (const lang of Object.keys(translations)) { + try { + const valid = new Intl.Locale(lang).baseName; + if (lang !== valid) { + translations[valid] = translations[lang]; + delete translations[lang]; + } + } catch (e) { + return { + status: 400, + message: `Invalid translation name: '${lang}'.`, + details: null, + }; + } + } + return null; +}; FormatRegistry.Set("language", (lang) => { try { diff --git a/api/tests/seed-movies.test.ts b/api/tests/seed-movies.test.ts index 1173116c..b3e1aba4 100644 --- a/api/tests/seed-movies.test.ts +++ b/api/tests/seed-movies.test.ts @@ -1,13 +1,15 @@ import { afterAll, beforeAll, describe, expect, it, test } from "bun:test"; import { eq, inArray } from "drizzle-orm"; import Elysia from "elysia"; +import { base } from "~/base"; import { seed } from "~/controllers/seed"; import { db } from "~/db"; import { shows, showTranslations, videos } from "~/db/schema"; +import { bubble } from "~/models/examples"; import { dune, duneVideo } from "~/models/examples/dune-2021"; import type { SeedMovie } from "~/models/movie"; -const app = new Elysia().use(seed); +const app = new Elysia().use(base).use(seed); const createMovie = async (movie: SeedMovie) => { const resp = await app.handle( new Request("http://localhost/movies", { @@ -157,6 +159,65 @@ describe("Movie seeding", () => { expect(body.details).toBeObject(); // TODO: handle additional fields too }); + + it("Invalid translation name", async () => { + const [resp, body] = await createMovie({ + ...dune, + translations: { + ...dune.translations, + test: { + name: "foo", + description: "bar", + tags: [], + aliases: [], + tagline: "toto", + banner: null, + poster: null, + thumbnail: null, + logo: null, + trailerUrl: null, + }, + }, + }); + + expectStatus(resp, body).toBe(400); + expect(body.status).toBe(400); + expect(body.message).toBe("Invalid translation name: 'test'."); + }); + + it("Correct translations casing.", async () => { + const [resp, body] = await createMovie({ + ...bubble, + slug: "casing-test", + translations: { + "en-us": { + name: "foo", + description: "bar", + tags: [], + aliases: [], + tagline: "toto", + banner: null, + poster: null, + thumbnail: null, + logo: null, + trailerUrl: null, + }, + }, + }); + + expect(resp.status).toBeWithin(200, 299); + expect(body.id).toBeString(); + const ret = await db.query.shows.findFirst({ + where: eq(shows.id, body.id), + with: { translations: true }, + }); + expect(ret!.translations).toBeArrayOfSize(1); + expect(ret!.translations[0]).toMatchObject({ + language: "en-US", + name: "foo", + }); + }); + test.todo("Create correct video slug (version)", async () => {}); test.todo("Create correct video slug (part)", async () => {}); test.todo("Create correct video slug (rendering)", async () => {});