diff --git a/api/src/controllers/videos.ts b/api/src/controllers/videos.ts index ade6c208..06ddcb73 100644 --- a/api/src/controllers/videos.ts +++ b/api/src/controllers/videos.ts @@ -3,10 +3,10 @@ import { alias } from "drizzle-orm/pg-core"; import { Elysia, t } from "elysia"; import { db } from "~/db"; import { entries, entryVideoJoin, shows, videos } from "~/db/schema"; -import { sqlarr } from "~/db/utils"; +import { jsonbBuildObject, jsonbObjectAgg, sqlarr } from "~/db/utils"; import { bubbleVideo } from "~/models/examples"; import { Page } from "~/models/utils"; -import { SeedVideo, Video } from "~/models/video"; +import { Guesses, SeedVideo, Video } from "~/models/video"; import { comment } from "~/utils"; import { computeVideoSlug } from "./seed/insert/entries"; import { updateAvailableCount } from "./seed/insert/shows"; @@ -27,6 +27,56 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] }) "created-videos": t.Array(CreatedVideo), error: t.Object({}), }) + .get( + "", + async () => { + const years = db.$with("years").as( + db + .select({ + guess: sql`${videos.guess}->>'title'`.as("guess"), + year: sql`coalesce(year, 'unknown')`.as("year"), + id: shows.id, + slug: shows.slug, + }) + .from(videos) + .leftJoin( + sql`jsonb_array_elements_text(${videos.guess}->'year') as year`, + sql`true`, + ) + .innerJoin(entryVideoJoin, eq(entryVideoJoin.videoPk, videos.pk)) + .innerJoin(entries, eq(entries.pk, entryVideoJoin.entryPk)) + .innerJoin(shows, eq(shows.pk, entries.showPk)), + ); + + const guess = db.$with("guess").as( + db + .select({ + guess: years.guess, + years: jsonbObjectAgg( + years.year, + jsonbBuildObject({ id: years.id, slug: years.slug }), + ).as("years"), + }) + .from(years) + .groupBy(years.guess), + ); + + const [{ guesses }] = await db + .with(years, guess) + .select({ guesses: jsonbObjectAgg(guess.guess, guess.years) }) + .from(guess); + + const paths = await db.select({ path: videos.path }).from(videos); + + return { paths: paths.map((x) => x.path), guesses }; + }, + { + detail: { description: "Get all video registered & guessed made" }, + response: { + 200: Guesses, + }, + }, + ) .post( "", async ({ body, error }) => { diff --git a/api/src/db/utils.ts b/api/src/db/utils.ts index c8f6c940..6c2fcccc 100644 --- a/api/src/db/utils.ts +++ b/api/src/db/utils.ts @@ -103,7 +103,7 @@ export const nullif = (val: SQL | Column, eq: SQL) => { return sql`nullif(${val}, ${eq})`; }; -export const jsonbObjectAgg = (key: SQLWrapper, value: SQL) => { +export const jsonbObjectAgg = (key: SQLWrapper, value: SQL | SQLWrapper) => { return sql< Record >`jsonb_object_agg(${sql.join([key, value], sql.raw(","))})`; diff --git a/api/src/models/video.ts b/api/src/models/video.ts index 012a6e70..09ad86f6 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -124,3 +124,34 @@ export const EmbeddedVideo = t.Omit(Video, ["guess", "createdAt", "updatedAt"]); export type EmbeddedVideo = Prettify; registerExamples(Video, bubbleVideo); + +export const Guesses = t.Object({ + paths: t.Array(t.String()), + guesses: t.Record( + t.String(), + t.Record( + t.Union([t.Literal("unknown"), t.String({ pattern: "[1-9][0-9]*" })]), + Resource(), + ), + ), +}); +export type Guesses = typeof Guesses.static; + +registerExamples(Guesses, { + paths: [ + "/videos/Evangelion S01E02.mkv", + "/videos/Evangelion (1995) S01E26.mkv", + ], + guesses: { + Evangelion: { + unknown: { + id: "43b742f5-9ce6-467d-ad29-74460624020a", + slug: "evangelion", + }, + 1995: { + id: "43b742f5-9ce6-467d-ad29-74460624020a", + slug: "evangelion", + }, + }, + }, +});