Rework POST /videos

This commit is contained in:
Zoe Roux 2025-04-28 23:21:33 +02:00
parent 621c9cec82
commit 1369da1845
No known key found for this signature in database
2 changed files with 147 additions and 103 deletions

View File

@ -1,11 +1,17 @@
import { and, eq, exists, inArray, not, sql } from "drizzle-orm"; import { and, eq, exists, inArray, not, or, sql } from "drizzle-orm";
import { alias } from "drizzle-orm/pg-core"; import { alias } from "drizzle-orm/pg-core";
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { db } from "~/db"; import { db } from "~/db";
import { entries, entryVideoJoin, shows, videos } from "~/db/schema"; import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
import { jsonbBuildObject, jsonbObjectAgg, sqlarr } from "~/db/utils"; import {
conflictUpdateAllExcept,
jsonbBuildObject,
jsonbObjectAgg,
sqlarr,
values,
} from "~/db/utils";
import { bubbleVideo } from "~/models/examples"; import { bubbleVideo } from "~/models/examples";
import { Page } from "~/models/utils"; import { Page, isUuid } from "~/models/utils";
import { Guesses, SeedVideo, Video } from "~/models/video"; import { Guesses, SeedVideo, Video } from "~/models/video";
import { comment } from "~/utils"; import { comment } from "~/utils";
import { computeVideoSlug } from "./seed/insert/entries"; import { computeVideoSlug } from "./seed/insert/entries";
@ -14,11 +20,11 @@ import { updateAvailableCount } from "./seed/insert/shows";
const CreatedVideo = t.Object({ const CreatedVideo = t.Object({
id: t.String({ format: "uuid" }), id: t.String({ format: "uuid" }),
path: t.String({ examples: [bubbleVideo.path] }), path: t.String({ examples: [bubbleVideo.path] }),
// entries: t.Array( entries: t.Array(
// t.Object({ t.Object({
// slug: t.String({ format: "slug", examples: ["bubble-v2"] }), slug: t.String({ format: "slug", examples: ["bubble-v2"] }),
// }), }),
// ), ),
}); });
export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] }) export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
@ -63,7 +69,9 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
const [{ guesses }] = await db const [{ guesses }] = await db
.with(years, guess) .with(years, guess)
.select({ guesses: jsonbObjectAgg<Guesses["guesses"]>(guess.guess, guess.years) }) .select({
guesses: jsonbObjectAgg<Guesses["guesses"]>(guess.guess, guess.years),
})
.from(guess); .from(guess);
const paths = await db.select({ path: videos.path }).from(videos); const paths = await db.select({ path: videos.path }).from(videos);
@ -80,90 +88,128 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
.post( .post(
"", "",
async ({ body, error }) => { async ({ body, error }) => {
const oldRet = await db const vidsI = db.$with("vidsI").as(
db
.insert(videos) .insert(videos)
.values(body) .values(body)
.onConflictDoNothing() .onConflictDoUpdate({
target: [videos.path],
set: conflictUpdateAllExcept(videos, ["pk", "id", "createdAt"]),
})
.returning({ .returning({
pk: videos.pk, pk: videos.pk,
id: videos.id, id: videos.id,
path: videos.path, path: videos.path,
guess: videos.guess, }),
}); );
return error(201, oldRet);
// TODO: this is a huge untested wip const entriesQ = db
// const vidsI = db.$with("vidsI").as( .select({
// db.insert(videos).values(body).onConflictDoNothing().returning({ pk: entries.pk,
// pk: videos.pk, id: entries.id,
// id: videos.id, slug: entries.slug,
// path: videos.path, seasonNumber: entries.seasonNumber,
// guess: videos.guess, episodeNumber: entries.episodeNumber,
// }), order: entries.order,
// ); showId: shows.id,
// showSlug: shows.slug,
// const findEntriesQ = db })
// .select({ .from(entries)
// guess: videos.guess, .innerJoin(shows, eq(entries.showPk, shows.pk))
// entryPk: entries.pk, .as("entriesQ");
// showSlug: shows.slug,
// // TODO: handle extras here const hasRenderingQ = db
// // guessit can't know if an episode is a special or not. treat specials like a normal episode. .select()
// kind: sql` .from(entryVideoJoin)
// case when ${entries.kind} = 'movie' then 'movie' else 'episode' end .where(eq(entryVideoJoin.entryPk, entriesQ.pk));
// `.as("kind"),
// season: entries.seasonNumber, const ret = await db
// episode: entries.episodeNumber, .with(vidsI)
// }) .insert(entryVideoJoin)
// .from(entries) .select(
// .leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk)) db
// .leftJoin(videos, eq(videos.pk, entryVideoJoin.video)) .select({
// .leftJoin(shows, eq(shows.pk, entries.showPk)) entry: entries.pk,
// .as("find_entries"); video: vidsI.pk,
// slug: computeVideoSlug(
// const hasRenderingQ = db entriesQ.showSlug,
// .select() sql`j.needRendering::boolean || exists(${hasRenderingQ})`,
// .from(entryVideoJoin) ),
// .where(eq(entryVideoJoin.entry, findEntriesQ.entryPk)); })
// .from(
// const ret = await db values(
// .with(vidsI) body.flatMap((x) =>
// .insert(entryVideoJoin) x.for.map((e) => ({
// .select( path: x.path,
// db needRendering: x.for.length > 1,
// .select({ entry: {
// entry: findEntriesQ.entryPk, ...e,
// video: vidsI.pk, movie:
// slug: computeVideoSlug( "movie" in e
// findEntriesQ.showSlug, ? isUuid(e.movie)
// sql`exists(${hasRenderingQ})`, ? { id: e.movie }
// ), : { slug: e.movie }
// }) : undefined,
// .from(vidsI) serie:
// .leftJoin( "serie" in e
// findEntriesQ, ? isUuid(e.serie)
// and( ? { id: e.serie }
// eq( : { slug: e.serie }
// sql`${findEntriesQ.guess}->'title'`, : undefined,
// sql`${vidsI.guess}->'title'`, },
// ), })),
// // TODO: find if @> with a jsonb created on the fly is ),
// // better than multiples checks ).as("j"),
// sql`${vidsI.guess} @> {"kind": }::jsonb`, )
// inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`), .innerJoin(vidsI, eq(vidsI.path, sql`j.path`))
// inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`), .innerJoin(
// inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`), entriesQ,
// ), or(
// ), and(
// ) sql`j.entry ? 'slug'`,
// .onConflictDoNothing() eq(entriesQ.slug, sql`j.entry->'slug'`),
// .returning({ ),
// slug: entryVideoJoin.slug, and(
// entryPk: entryVideoJoin.entry, sql`j.entry ? 'movie'`,
// id: vidsI.id, or(
// path: vidsI.path, eq(entriesQ.showId, sql`j.entry #> '{movie, id}'`),
// }); eq(entriesQ.showSlug, sql`j.entry #> '{movie, slug}'`),
// return error(201, ret as any); ),
),
and(
sql`j.entry ? 'serie'`,
or(
eq(entriesQ.showId, sql`j.entry #> '{serie, id}'`),
eq(entriesQ.showSlug, sql`j.entry #> '{serie, slug}'`),
),
or(
and(
sql`j.entry ?& array['season', 'episode']`,
eq(entriesQ.seasonNumber, sql`j.entry->'season'`),
eq(entriesQ.episodeNumber, sql`j.entry->'episode'`),
),
and(
sql`j.entry ? 'order'`,
eq(entriesQ.order, sql`j.entry->'order'`),
),
and(
sql`j.entry ? 'special'`,
eq(entriesQ.episodeNumber, sql`j.entry->'special'`),
),
),
),
),
),
)
.onConflictDoNothing()
.returning({
slug: entryVideoJoin.slug,
entryPk: entryVideoJoin.entryPk,
id: vidsI.id,
path: vidsI.path,
});
return error(201, ret);
// return error(201, ret.map(x => ({ id: x.id, slug: x.})));
}, },
{ {
detail: { detail: {
@ -176,7 +222,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
`, `,
}, },
body: t.Array(SeedVideo), body: t.Array(SeedVideo),
response: { 201: t.Array(CreatedVideo) }, // response: { 201: t.Array(CreatedVideo) },
}, },
) )
.delete( .delete(

View File

@ -70,12 +70,20 @@ export const SeedVideo = t.Object({
for: t.Array( for: t.Array(
t.Union([ t.Union([
t.Object({
slug: t.String({
format: "slug",
examples: ["made-in-abyss-dawn-of-the-deep-soul"],
}),
}),
t.Object({
externalId: t.Optional(t.Union([EpisodeId, ExternalId()])),
}),
t.Object({ t.Object({
movie: t.Union([ movie: t.Union([
t.String({ format: "uuid" }), t.String({ format: "uuid" }),
t.String({ format: "slug", examples: ["bubble"] }), t.String({ format: "slug", examples: ["bubble"] }),
]), ]),
externalId: t.Optional(ExternalId()),
}), }),
t.Intersect([ t.Intersect([
t.Object({ t.Object({
@ -88,22 +96,12 @@ export const SeedVideo = t.Object({
t.Object({ t.Object({
season: t.Integer({ minimum: 1 }), season: t.Integer({ minimum: 1 }),
episode: t.Integer(), episode: t.Integer(),
externalId: t.Optional(EpisodeId),
}), }),
t.Object({ t.Object({
absolute: t.Integer(), order: t.Number(),
externalId: t.Optional(t.Union([EpisodeId, ExternalId()])),
}), }),
t.Object({ t.Object({
special: t.Integer(), special: t.Integer(),
externalId: t.Optional(EpisodeId),
}),
t.Object({
slug: t.String({
format: "slug",
examples: ["made-in-abyss-dawn-of-the-deep-soul"],
}),
externalId: t.Optional(t.Union([EpisodeId, ExternalId()])),
}), }),
]), ]),
]), ]),