diff --git a/api/src/controllers/seed/images.ts b/api/src/controllers/seed/images.ts new file mode 100644 index 00000000..b13dfbcb --- /dev/null +++ b/api/src/controllers/seed/images.ts @@ -0,0 +1,19 @@ +import type { Image } from "~/models/utils"; + +export const processImage = async (url: string): Promise => { + const hasher = new Bun.CryptoHasher("sha256"); + hasher.update(url); + + // TODO: download source, save it in multiples qualities & process blurhash + + return { + id: hasher.digest().toString(), + source: url, + blurhash: "", + }; +}; + +export const processOptImage = (url: string | null): Promise => { + if (!url) return Promise.resolve(null); + return processImage(url); +}; diff --git a/api/src/controllers/seed/index.ts b/api/src/controllers/seed/index.ts new file mode 100644 index 00000000..142e1bd2 --- /dev/null +++ b/api/src/controllers/seed/index.ts @@ -0,0 +1,79 @@ +import Elysia, { t } from "elysia"; +import { Movie, SeedMovie } from "~/models/movie"; +import { db } from "~/db"; +import { + shows, + showTranslations, + entries, + entryTranslations, +} from "~/db/schema"; +import { guessNextRefresh } from "./refresh"; +import { processOptImage } from "./images"; + +type Show = typeof shows.$inferInsert; +type ShowTrans = typeof showTranslations.$inferInsert; +type Entry = typeof entries.$inferInsert; + +export const seed = new Elysia() + .model({ + movie: Movie, + "seed-movie": SeedMovie, + error: t.String(), + }) + .post( + "/movies", + async ({ body }) => { + const { translations, videos, ...bMovie } = body; + + const ret = await db.transaction(async (tx) => { + const movie: Show = { + kind: "movie", + startAir: bMovie.airDate, + nextRefresh: guessNextRefresh(bMovie.airDate ?? new Date()), + ...bMovie, + }; + const [ret] = await tx + .insert(shows) + .values(movie) + .returning({ pk: shows.pk, id: shows.id }); + + // even if never shown to the user, a movie still has an entry. + const movieEntry: Entry = { type: "movie", ...bMovie }; + const [entry] = await tx + .insert(entries) + .values(movieEntry) + .returning({ pk: entries.pk }); + + const trans: ShowTrans[] = await Promise.all( + Object.entries(translations).map(async ([lang, tr]) => ({ + pk: ret.pk, + // TODO: normalize lang or error if invalid + language: lang, + ...tr, + poster: await processOptImage(tr.poster), + thumbnail: await processOptImage(tr.thumbnail), + logo: await processOptImage(tr.logo), + banner: await processOptImage(tr.banner), + })), + ); + await tx.insert(showTranslations).values(trans); + + const entryTrans = trans.map((x) => ({ ...x, pk: entry.pk })); + await tx.insert(entryTranslations).values(entryTrans); + + return { ...ret, entry: entry.pk }; + }); + + // TODO: insert entry-video links + // await db.transaction(async tx => { + // await tx.insert(videos).values(videos); + // }); + + return ret.id; + }, + { + body: "seed-movie", + response: { 200: "movie", 400: "error" }, + tags: ["movies"], + }, + ); diff --git a/api/src/controllers/seed/refresh.ts b/api/src/controllers/seed/refresh.ts new file mode 100644 index 00000000..2f142eaa --- /dev/null +++ b/api/src/controllers/seed/refresh.ts @@ -0,0 +1,12 @@ +// oh i hate js dates so much. +export const guessNextRefresh = (airDate: Date | string) => { + if (typeof airDate === "string") airDate = new Date(airDate); + const diff = new Date().getTime() - airDate.getTime(); + const days = diff / (24 * 60 * 60 * 1000); + + const ret = new Date(); + if (days <= 4) ret.setDate(ret.getDate() + 4); + else if (days <= 21) ret.setDate(ret.getDate() + 14); + else ret.setMonth(ret.getMonth() + 2); + return ret.toISOString().substring(0, 10); +}; diff --git a/api/src/models/utils/image.ts b/api/src/models/utils/image.ts index c1883d60..6d79a4c3 100644 --- a/api/src/models/utils/image.ts +++ b/api/src/models/utils/image.ts @@ -1,7 +1,7 @@ import { t } from "elysia"; export const Image = t.Object({ - id: t.String({ format: "uuid" }), + id: t.String(), source: t.String({ format: "uri" }), blurhash: t.String(), }); diff --git a/api/tsconfig.json b/api/tsconfig.json index ec10ebde..b2e97422 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -3,11 +3,19 @@ "target": "ES2021", "module": "ES2022", "moduleResolution": "node", - "types": ["bun-types"], + "types": [ + "bun-types" + ], "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, - "noErrorTruncation": true + "noErrorTruncation": true, + "baseUrl": ".", + "paths": { + "~/*": [ + "./src/*" + ] + } } }