mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 20:24:27 -04:00
192 lines
5.2 KiB
TypeScript
192 lines
5.2 KiB
TypeScript
import { and, eq, exists, inArray, not, sql } from "drizzle-orm";
|
|
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 { bubbleVideo } from "~/models/examples";
|
|
import { Page } from "~/models/utils";
|
|
import { SeedVideo, Video } from "~/models/video";
|
|
import { comment } from "~/utils";
|
|
import { computeVideoSlug } from "./seed/insert/entries";
|
|
import { updateAvailableCount } from "./seed/insert/shows";
|
|
|
|
const CreatedVideo = t.Object({
|
|
id: t.String({ format: "uuid" }),
|
|
path: t.String({ examples: [bubbleVideo.path] }),
|
|
// entries: t.Array(
|
|
// t.Object({
|
|
// slug: t.String({ format: "slug", examples: ["bubble-v2"] }),
|
|
// }),
|
|
// ),
|
|
});
|
|
|
|
export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|
.model({
|
|
video: Video,
|
|
"created-videos": t.Array(CreatedVideo),
|
|
error: t.Object({}),
|
|
})
|
|
.post(
|
|
"",
|
|
async ({ body, error }) => {
|
|
const oldRet = await db
|
|
.insert(videos)
|
|
.values(body)
|
|
.onConflictDoNothing()
|
|
.returning({
|
|
pk: videos.pk,
|
|
id: videos.id,
|
|
path: videos.path,
|
|
guess: videos.guess,
|
|
});
|
|
return error(201, oldRet);
|
|
|
|
// TODO: this is a huge untested wip
|
|
// const vidsI = db.$with("vidsI").as(
|
|
// db.insert(videos).values(body).onConflictDoNothing().returning({
|
|
// pk: videos.pk,
|
|
// id: videos.id,
|
|
// path: videos.path,
|
|
// guess: videos.guess,
|
|
// }),
|
|
// );
|
|
//
|
|
// const findEntriesQ = db
|
|
// .select({
|
|
// guess: videos.guess,
|
|
// entryPk: entries.pk,
|
|
// showSlug: shows.slug,
|
|
// // TODO: handle extras here
|
|
// // guessit can't know if an episode is a special or not. treat specials like a normal episode.
|
|
// kind: sql`
|
|
// case when ${entries.kind} = 'movie' then 'movie' else 'episode' end
|
|
// `.as("kind"),
|
|
// season: entries.seasonNumber,
|
|
// episode: entries.episodeNumber,
|
|
// })
|
|
// .from(entries)
|
|
// .leftJoin(entryVideoJoin, eq(entryVideoJoin.entry, entries.pk))
|
|
// .leftJoin(videos, eq(videos.pk, entryVideoJoin.video))
|
|
// .leftJoin(shows, eq(shows.pk, entries.showPk))
|
|
// .as("find_entries");
|
|
//
|
|
// const hasRenderingQ = db
|
|
// .select()
|
|
// .from(entryVideoJoin)
|
|
// .where(eq(entryVideoJoin.entry, findEntriesQ.entryPk));
|
|
//
|
|
// const ret = await db
|
|
// .with(vidsI)
|
|
// .insert(entryVideoJoin)
|
|
// .select(
|
|
// db
|
|
// .select({
|
|
// entry: findEntriesQ.entryPk,
|
|
// video: vidsI.pk,
|
|
// slug: computeVideoSlug(
|
|
// findEntriesQ.showSlug,
|
|
// sql`exists(${hasRenderingQ})`,
|
|
// ),
|
|
// })
|
|
// .from(vidsI)
|
|
// .leftJoin(
|
|
// findEntriesQ,
|
|
// and(
|
|
// eq(
|
|
// sql`${findEntriesQ.guess}->'title'`,
|
|
// sql`${vidsI.guess}->'title'`,
|
|
// ),
|
|
// // TODO: find if @> with a jsonb created on the fly is
|
|
// // better than multiples checks
|
|
// sql`${vidsI.guess} @> {"kind": }::jsonb`,
|
|
// inArray(findEntriesQ.kind, sql`${vidsI.guess}->'type'`),
|
|
// inArray(findEntriesQ.episode, sql`${vidsI.guess}->'episode'`),
|
|
// inArray(findEntriesQ.season, sql`${vidsI.guess}->'season'`),
|
|
// ),
|
|
// ),
|
|
// )
|
|
// .onConflictDoNothing()
|
|
// .returning({
|
|
// slug: entryVideoJoin.slug,
|
|
// entryPk: entryVideoJoin.entry,
|
|
// id: vidsI.id,
|
|
// path: vidsI.path,
|
|
// });
|
|
// return error(201, ret as any);
|
|
},
|
|
{
|
|
detail: {
|
|
description: comment`
|
|
Create videos in bulk.
|
|
Duplicated videos will simply be ignored.
|
|
|
|
If a videos has a \`guess\` field, it will be used to automatically register the video under an existing
|
|
movie or entry.
|
|
`,
|
|
},
|
|
body: t.Array(SeedVideo),
|
|
response: { 201: t.Array(CreatedVideo) },
|
|
},
|
|
)
|
|
.delete(
|
|
"",
|
|
async ({ body }) => {
|
|
await db.transaction(async (tx) => {
|
|
const vids = tx.$with("vids").as(
|
|
tx
|
|
.delete(videos)
|
|
.where(eq(videos.path, sql`any(${body})`))
|
|
.returning({ pk: videos.pk }),
|
|
);
|
|
const evj = alias(entryVideoJoin, "evj");
|
|
const delEntries = tx.$with("del_entries").as(
|
|
tx
|
|
.with(vids)
|
|
.select({ entry: entryVideoJoin.entryPk })
|
|
.from(entryVideoJoin)
|
|
.where(
|
|
and(
|
|
inArray(entryVideoJoin.videoPk, tx.select().from(vids)),
|
|
not(
|
|
exists(
|
|
tx
|
|
.select()
|
|
.from(evj)
|
|
.where(
|
|
and(
|
|
eq(evj.entryPk, entryVideoJoin.entryPk),
|
|
not(inArray(evj.videoPk, db.select().from(vids))),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
const delShows = await tx
|
|
.with(delEntries)
|
|
.update(entries)
|
|
.set({ availableSince: null })
|
|
.where(inArray(entries.pk, db.select().from(delEntries)))
|
|
.returning({ show: entries.showPk });
|
|
|
|
await updateAvailableCount(
|
|
tx,
|
|
delShows.map((x) => x.show),
|
|
false,
|
|
);
|
|
});
|
|
},
|
|
{
|
|
detail: { description: "Delete videos in bulk." },
|
|
body: t.Array(
|
|
t.String({
|
|
description: "Path of the video to delete",
|
|
examples: [bubbleVideo.path],
|
|
}),
|
|
),
|
|
response: { 204: t.Void() },
|
|
},
|
|
);
|