From 4424e9b40a6525c09f3b85c06bc8ba946c321a0b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Thu, 30 Jan 2025 18:54:30 +0100 Subject: [PATCH] Handle extra seeding --- api/src/controllers/seed/insert/entries.ts | 72 +++++++++++++++++----- api/src/controllers/seed/series.ts | 15 ++++- api/src/models/entry/extra.ts | 3 +- api/src/models/examples/made-in-abyss.ts | 2 + api/tests/series/seed-serie.test.ts | 6 +- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/api/src/controllers/seed/insert/entries.ts b/api/src/controllers/seed/insert/entries.ts index 45c9a352..eecb5e2d 100644 --- a/api/src/controllers/seed/insert/entries.ts +++ b/api/src/controllers/seed/insert/entries.ts @@ -7,13 +7,26 @@ import { videos, } from "~/db/schema"; import { conflictUpdateAllExcept, values } from "~/db/utils"; -import type { SeedEntry } from "~/models/entry"; +import type { SeedEntry as SEntry, SeedExtra as SExtra } from "~/models/entry"; import { processOptImage } from "../images"; import { guessNextRefresh } from "../refresh"; +type SeedEntry = SEntry & { + video?: undefined; +}; +type SeedExtra = Omit & { + videos?: undefined; + translations?: undefined; + kind: "extra"; + extraKind: SExtra["kind"]; +}; + type EntryI = typeof entries.$inferInsert; -const generateSlug = (showSlug: string, entry: SeedEntry): string => { +const generateSlug = ( + showSlug: string, + entry: SeedEntry | SeedExtra, +): string => { switch (entry.kind) { case "episode": return `${showSlug}-s${entry.seasonNumber}e${entry.episodeNumber}`; @@ -22,22 +35,29 @@ const generateSlug = (showSlug: string, entry: SeedEntry): string => { case "movie": if (entry.slug) return entry.slug; return entry.order === 1 ? showSlug : `${showSlug}-${entry.order}`; + case "extra": + return entry.slug; } }; export const insertEntries = async ( show: { pk: number; slug: string }, - items: SeedEntry[], + items: (SeedEntry | SeedExtra)[], ) => { + if (!items) return []; + const retEntries = await db.transaction(async (tx) => { const vals: EntryI[] = items.map((seed) => { - const { translations, videos, ...entry } = seed; + const { translations, videos, video, ...entry } = seed; return { ...entry, showPk: show.pk, slug: generateSlug(show.slug, seed), thumbnail: processOptImage(seed.thumbnail), - nextRefresh: guessNextRefresh(entry.airDate ?? new Date()), + nextRefresh: + entry.kind !== "extra" + ? guessNextRefresh(entry.airDate ?? new Date()) + : guessNextRefresh(new Date()), episodeNumber: entry.kind === "episode" ? entry.episodeNumber @@ -61,14 +81,25 @@ export const insertEntries = async ( }) .returning({ pk: entries.pk, id: entries.id, slug: entries.slug }); - const trans = items.flatMap((seed, i) => - Object.entries(seed.translations).map(([lang, tr]) => ({ + const trans = items.flatMap((seed, i) => { + if (seed.kind === "extra") { + return { + pk: ret[i].pk, + // yeah we hardcode the language to extra because if we want to support + // translations one day it won't be awkward + language: "extra", + name: seed.name, + description: null, + }; + } + + return Object.entries(seed.translations).map(([lang, tr]) => ({ // assumes ret is ordered like items. pk: ret[i].pk, language: lang, ...tr, - })), - ); + })); + }); await tx .insert(entryTranslations) .values(trans) @@ -80,15 +111,22 @@ export const insertEntries = async ( return ret; }); - const vids = items.flatMap( - (seed, i) => - seed.videos?.map((x, j) => ({ - videoId: x, + const vids = items.flatMap((seed, i) => { + if (seed.kind === "extra") { + return { + videoId: seed.video, entryPk: retEntries[i].pk, - // The first video should not have a rendering. - needRendering: j && seed.videos!.length > 1, - })) ?? [], - ); + needRendering: false, + }; + } + if (!seed.videos) return []; + return seed.videos.map((x, j) => ({ + videoId: x, + entryPk: retEntries[i].pk, + // The first video should not have a rendering. + needRendering: j && seed.videos!.length > 1, + })); + }); if (vids.length === 0) return retEntries.map((x) => ({ id: x.id, slug: x.slug, videos: [] })); diff --git a/api/src/controllers/seed/series.ts b/api/src/controllers/seed/series.ts index 349a7101..c8c2667d 100644 --- a/api/src/controllers/seed/series.ts +++ b/api/src/controllers/seed/series.ts @@ -2,9 +2,9 @@ import { t } from "elysia"; import type { SeedSerie } from "~/models/serie"; import { getYear } from "~/utils"; import { insertEntries } from "./insert/entries"; +import { insertSeasons } from "./insert/seasons"; import { insertShow } from "./insert/shows"; import { guessNextRefresh } from "./refresh"; -import { insertSeasons } from "./insert/seasons"; export const SeedSerieResponse = t.Object({ id: t.String({ format: "uuid" }), @@ -29,6 +29,12 @@ export const SeedSerieResponse = t.Object({ ), }), ), + extras: t.Array( + t.Object({ + id: t.String({ format: "uuid" }), + slug: t.String({ format: "slug", examples: ["made-in-abyss-s1e1"] }), + }), + ), }); export type SeedSerieResponse = typeof SeedSerieResponse.static; @@ -49,7 +55,7 @@ export const seedSerie = async ( seed.slug = `random-${getYear(seed.startAir)}`; } - const { translations, seasons, entries, ...serie } = seed; + const { translations, seasons, entries, extras, ...serie } = seed; const nextRefresh = guessNextRefresh(serie.startAir ?? new Date()); const show = await insertShow( @@ -64,6 +70,10 @@ export const seedSerie = async ( const retSeasons = await insertSeasons(show, seasons); const retEntries = await insertEntries(show, entries); + const retExtras = await insertEntries( + show, + (extras ?? []).map((x) => ({ ...x, kind: "extra", extraKind: x.kind })), + ); return { updated: show.updated, @@ -71,5 +81,6 @@ export const seedSerie = async ( slug: show.slug, seasons: retSeasons, entries: retEntries, + extras: retExtras, }; }; diff --git a/api/src/models/entry/extra.ts b/api/src/models/entry/extra.ts index 3d4c5b2f..5d002976 100644 --- a/api/src/models/entry/extra.ts +++ b/api/src/models/entry/extra.ts @@ -36,8 +36,9 @@ export type Extra = typeof Extra.static; export const SeedExtra = t.Intersect([ t.Omit(BaseExtra, ["thumbnail", "createdAt"]), t.Object({ + slug: t.String({ format: "slug" }), thumbnail: t.Nullable(SeedImage), - videos: t.Optional(t.Array(t.String({ format: "uuid" }))), + video: t.String({ format: "uuid" }), }), ]); export type SeedExtra = typeof SeedExtra.static; diff --git a/api/src/models/examples/made-in-abyss.ts b/api/src/models/examples/made-in-abyss.ts index 129008df..37e679d9 100644 --- a/api/src/models/examples/made-in-abyss.ts +++ b/api/src/models/examples/made-in-abyss.ts @@ -237,9 +237,11 @@ export const madeInAbyss = { extras: [ { kind: "behind-the-scene", + slug: "made-in-abyss-making-of", name: "The Making of MADE IN ABYSS 01", runtime: 17, thumbnail: null, + video: "3cd436ee-01ff-4f45-ba98-654282531234", }, ], } satisfies SeedSerie; diff --git a/api/tests/series/seed-serie.test.ts b/api/tests/series/seed-serie.test.ts index 976b57bf..37614b69 100644 --- a/api/tests/series/seed-serie.test.ts +++ b/api/tests/series/seed-serie.test.ts @@ -28,8 +28,8 @@ describe("Serie seeding", () => { expect(ret!.seasons).toBeArrayOfSize(2); expect(ret!.seasons[0].slug).toBe("made-in-abyss-s1"); expect(ret!.seasons[1].slug).toBe("made-in-abyss-s2"); - // expect(ret!.entries).toBeArrayOfSize( - // madeInAbyss.entries.length + madeInAbyss.extras.length, - // ); + expect(ret!.entries).toBeArrayOfSize( + madeInAbyss.entries.length + madeInAbyss.extras.length, + ); }); });