mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-23 15:30:34 -04:00
Add link video route
This commit is contained in:
parent
3e69ea9c8b
commit
6e2743a4be
@ -1,6 +1,6 @@
|
|||||||
import { and, eq, notExists, or, sql } from "drizzle-orm";
|
import { and, eq, notExists, or, sql } from "drizzle-orm";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { db } from "~/db";
|
import { type Transaction, db } from "~/db";
|
||||||
import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
|
import { entries, entryVideoJoin, shows, videos } from "~/db/schema";
|
||||||
import {
|
import {
|
||||||
conflictUpdateAllExcept,
|
conflictUpdateAllExcept,
|
||||||
@ -30,6 +30,140 @@ import {
|
|||||||
updateAvailableSince,
|
updateAvailableSince,
|
||||||
} from "./seed/insert/shows";
|
} from "./seed/insert/shows";
|
||||||
|
|
||||||
|
async function linkVideos(
|
||||||
|
tx: Transaction,
|
||||||
|
links: {
|
||||||
|
video: number;
|
||||||
|
entry: Omit<SeedVideo["for"], "movie" | "serie"> & {
|
||||||
|
movie?: { id?: string; slug?: string };
|
||||||
|
serie?: { id?: string; slug?: string };
|
||||||
|
};
|
||||||
|
}[],
|
||||||
|
) {
|
||||||
|
if (!links.length) return {};
|
||||||
|
|
||||||
|
const entriesQ = tx
|
||||||
|
.select({
|
||||||
|
pk: entries.pk,
|
||||||
|
id: entries.id,
|
||||||
|
slug: entries.slug,
|
||||||
|
kind: entries.kind,
|
||||||
|
seasonNumber: entries.seasonNumber,
|
||||||
|
episodeNumber: entries.episodeNumber,
|
||||||
|
order: entries.order,
|
||||||
|
showId: sql`${shows.id}`.as("showId"),
|
||||||
|
showSlug: sql`${shows.slug}`.as("showSlug"),
|
||||||
|
externalId: entries.externalId,
|
||||||
|
})
|
||||||
|
.from(entries)
|
||||||
|
.innerJoin(shows, eq(entries.showPk, shows.pk))
|
||||||
|
.as("entriesQ");
|
||||||
|
|
||||||
|
const hasRenderingQ = tx
|
||||||
|
.select()
|
||||||
|
.from(entryVideoJoin)
|
||||||
|
.where(eq(entryVideoJoin.entryPk, entriesQ.pk));
|
||||||
|
|
||||||
|
const ret = await tx
|
||||||
|
.insert(entryVideoJoin)
|
||||||
|
.select(
|
||||||
|
tx
|
||||||
|
.selectDistinctOn([entriesQ.pk, videos.pk], {
|
||||||
|
entryPk: entriesQ.pk,
|
||||||
|
videoPk: videos.pk,
|
||||||
|
slug: computeVideoSlug(entriesQ.slug, sql`exists(${hasRenderingQ})`),
|
||||||
|
})
|
||||||
|
.from(
|
||||||
|
values(links, {
|
||||||
|
video: "integer",
|
||||||
|
entry: "jsonb",
|
||||||
|
}).as("j"),
|
||||||
|
)
|
||||||
|
.innerJoin(videos, eq(videos.pk, sql`j.video`))
|
||||||
|
.innerJoin(
|
||||||
|
entriesQ,
|
||||||
|
or(
|
||||||
|
and(
|
||||||
|
sql`j.entry ? 'slug'`,
|
||||||
|
eq(entriesQ.slug, sql`j.entry->>'slug'`),
|
||||||
|
),
|
||||||
|
and(
|
||||||
|
sql`j.entry ? 'movie'`,
|
||||||
|
or(
|
||||||
|
eq(entriesQ.showId, sql`(j.entry #>> '{movie, id}')::uuid`),
|
||||||
|
eq(entriesQ.showSlug, sql`j.entry #>> '{movie, slug}'`),
|
||||||
|
),
|
||||||
|
eq(entriesQ.kind, "movie"),
|
||||||
|
),
|
||||||
|
and(
|
||||||
|
sql`j.entry ? 'serie'`,
|
||||||
|
or(
|
||||||
|
eq(entriesQ.showId, sql`(j.entry #>> '{serie, id}')::uuid`),
|
||||||
|
eq(entriesQ.showSlug, sql`j.entry #>> '{serie, slug}'`),
|
||||||
|
),
|
||||||
|
or(
|
||||||
|
and(
|
||||||
|
sql`j.entry ?& array['season', 'episode']`,
|
||||||
|
eq(entriesQ.seasonNumber, sql`(j.entry->>'season')::integer`),
|
||||||
|
eq(
|
||||||
|
entriesQ.episodeNumber,
|
||||||
|
sql`(j.entry->>'episode')::integer`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
and(
|
||||||
|
sql`j.entry ? 'order'`,
|
||||||
|
eq(entriesQ.order, sql`(j.entry->>'order')::float`),
|
||||||
|
),
|
||||||
|
and(
|
||||||
|
sql`j.entry ? 'special'`,
|
||||||
|
eq(
|
||||||
|
entriesQ.episodeNumber,
|
||||||
|
sql`(j.entry->>'special')::integer`,
|
||||||
|
),
|
||||||
|
eq(entriesQ.kind, "special"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
and(
|
||||||
|
sql`j.entry ? 'externalId'`,
|
||||||
|
sql`j.entry->'externalId' <@ ${entriesQ.externalId}`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: [entryVideoJoin.entryPk, entryVideoJoin.videoPk],
|
||||||
|
// this is basically a `.onConflictDoNothing()` but we want `returning` to give us the existing data
|
||||||
|
set: { entryPk: sql`excluded.entry_pk` },
|
||||||
|
})
|
||||||
|
.returning({
|
||||||
|
slug: entryVideoJoin.slug,
|
||||||
|
entryPk: entryVideoJoin.entryPk,
|
||||||
|
videoPk: entryVideoJoin.videoPk,
|
||||||
|
});
|
||||||
|
|
||||||
|
const entr = ret.reduce(
|
||||||
|
(acc, x) => {
|
||||||
|
acc[x.videoPk] ??= [];
|
||||||
|
acc[x.videoPk].push({ slug: x.slug });
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<number, { slug: string }[]>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const entriesPk = [...new Set(ret.map((x) => x.entryPk))];
|
||||||
|
await updateAvailableCount(
|
||||||
|
tx,
|
||||||
|
tx
|
||||||
|
.selectDistinct({ pk: entries.showPk })
|
||||||
|
.from(entries)
|
||||||
|
.where(eq(entries.pk, sql`any(${sqlarr(entriesPk)})`)),
|
||||||
|
);
|
||||||
|
await updateAvailableSince(tx, entriesPk);
|
||||||
|
|
||||||
|
return entr;
|
||||||
|
}
|
||||||
|
|
||||||
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] }),
|
||||||
@ -203,7 +337,6 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
if (!x.for) return [];
|
if (!x.for) return [];
|
||||||
return x.for.map((e) => ({
|
return x.for.map((e) => ({
|
||||||
video: vids.find((v) => v.path === x.path)!.pk,
|
video: vids.find((v) => v.path === x.path)!.pk,
|
||||||
path: x.path,
|
|
||||||
entry: {
|
entry: {
|
||||||
...e,
|
...e,
|
||||||
movie:
|
movie:
|
||||||
@ -234,135 +367,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entriesQ = tx
|
const links = await linkVideos(tx, vidEntries);
|
||||||
.select({
|
|
||||||
pk: entries.pk,
|
|
||||||
id: entries.id,
|
|
||||||
slug: entries.slug,
|
|
||||||
kind: entries.kind,
|
|
||||||
seasonNumber: entries.seasonNumber,
|
|
||||||
episodeNumber: entries.episodeNumber,
|
|
||||||
order: entries.order,
|
|
||||||
showId: sql`${shows.id}`.as("showId"),
|
|
||||||
showSlug: sql`${shows.slug}`.as("showSlug"),
|
|
||||||
externalId: entries.externalId,
|
|
||||||
})
|
|
||||||
.from(entries)
|
|
||||||
.innerJoin(shows, eq(entries.showPk, shows.pk))
|
|
||||||
.as("entriesQ");
|
|
||||||
|
|
||||||
const hasRenderingQ = tx
|
|
||||||
.select()
|
|
||||||
.from(entryVideoJoin)
|
|
||||||
.where(eq(entryVideoJoin.entryPk, entriesQ.pk));
|
|
||||||
|
|
||||||
const ret = await tx
|
|
||||||
.insert(entryVideoJoin)
|
|
||||||
.select(
|
|
||||||
tx
|
|
||||||
.selectDistinctOn([entriesQ.pk, videos.pk], {
|
|
||||||
entryPk: entriesQ.pk,
|
|
||||||
videoPk: videos.pk,
|
|
||||||
slug: computeVideoSlug(
|
|
||||||
entriesQ.slug,
|
|
||||||
sql`exists(${hasRenderingQ})`,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.from(
|
|
||||||
values(vidEntries, {
|
|
||||||
video: "integer",
|
|
||||||
entry: "jsonb",
|
|
||||||
}).as("j"),
|
|
||||||
)
|
|
||||||
.innerJoin(videos, eq(videos.pk, sql`j.video`))
|
|
||||||
.innerJoin(
|
|
||||||
entriesQ,
|
|
||||||
or(
|
|
||||||
and(
|
|
||||||
sql`j.entry ? 'slug'`,
|
|
||||||
eq(entriesQ.slug, sql`j.entry->>'slug'`),
|
|
||||||
),
|
|
||||||
and(
|
|
||||||
sql`j.entry ? 'movie'`,
|
|
||||||
or(
|
|
||||||
eq(
|
|
||||||
entriesQ.showId,
|
|
||||||
sql`(j.entry #>> '{movie, id}')::uuid`,
|
|
||||||
),
|
|
||||||
eq(entriesQ.showSlug, sql`j.entry #>> '{movie, slug}'`),
|
|
||||||
),
|
|
||||||
eq(entriesQ.kind, "movie"),
|
|
||||||
),
|
|
||||||
and(
|
|
||||||
sql`j.entry ? 'serie'`,
|
|
||||||
or(
|
|
||||||
eq(
|
|
||||||
entriesQ.showId,
|
|
||||||
sql`(j.entry #>> '{serie, id}')::uuid`,
|
|
||||||
),
|
|
||||||
eq(entriesQ.showSlug, sql`j.entry #>> '{serie, slug}'`),
|
|
||||||
),
|
|
||||||
or(
|
|
||||||
and(
|
|
||||||
sql`j.entry ?& array['season', 'episode']`,
|
|
||||||
eq(
|
|
||||||
entriesQ.seasonNumber,
|
|
||||||
sql`(j.entry->>'season')::integer`,
|
|
||||||
),
|
|
||||||
eq(
|
|
||||||
entriesQ.episodeNumber,
|
|
||||||
sql`(j.entry->>'episode')::integer`,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
and(
|
|
||||||
sql`j.entry ? 'order'`,
|
|
||||||
eq(entriesQ.order, sql`(j.entry->>'order')::float`),
|
|
||||||
),
|
|
||||||
and(
|
|
||||||
sql`j.entry ? 'special'`,
|
|
||||||
eq(
|
|
||||||
entriesQ.episodeNumber,
|
|
||||||
sql`(j.entry->>'special')::integer`,
|
|
||||||
),
|
|
||||||
eq(entriesQ.kind, "special"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
and(
|
|
||||||
sql`j.entry ? 'externalId'`,
|
|
||||||
sql`j.entry->'externalId' <@ ${entriesQ.externalId}`,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: [entryVideoJoin.entryPk, entryVideoJoin.videoPk],
|
|
||||||
// this is basically a `.onConflictDoNothing()` but we want `returning` to give us the existing data
|
|
||||||
set: { entryPk: sql`excluded.entry_pk` },
|
|
||||||
})
|
|
||||||
.returning({
|
|
||||||
slug: entryVideoJoin.slug,
|
|
||||||
entryPk: entryVideoJoin.entryPk,
|
|
||||||
videoPk: entryVideoJoin.videoPk,
|
|
||||||
});
|
|
||||||
const entr = ret.reduce(
|
|
||||||
(acc, x) => {
|
|
||||||
acc[x.videoPk] ??= [];
|
|
||||||
acc[x.videoPk].push({ slug: x.slug });
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<number, { slug: string }[]>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const entriesPk = [...new Set(ret.map((x) => x.entryPk))];
|
|
||||||
await updateAvailableCount(
|
|
||||||
tx,
|
|
||||||
tx
|
|
||||||
.selectDistinct({ pk: entries.showPk })
|
|
||||||
.from(entries)
|
|
||||||
.where(eq(entries.pk, sql`any(${sqlarr(entriesPk)})`)),
|
|
||||||
);
|
|
||||||
await updateAvailableSince(tx, entriesPk);
|
|
||||||
|
|
||||||
return status(
|
return status(
|
||||||
201,
|
201,
|
||||||
@ -370,7 +375,7 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
id: x.id,
|
id: x.id,
|
||||||
path: x.path,
|
path: x.path,
|
||||||
guess: x.guess,
|
guess: x.guess,
|
||||||
entries: entr[x.pk] ?? [],
|
entries: links[x.pk] ?? [],
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -453,4 +458,72 @@ export const videosH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
),
|
),
|
||||||
response: { 200: t.Array(t.String()) },
|
response: { 200: t.Array(t.String()) },
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
.post(
|
||||||
|
"/link",
|
||||||
|
async ({ body, status }) => {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const vids = await tx
|
||||||
|
.select({ pk: videos.pk, id: videos.id, path: videos.path })
|
||||||
|
.from(videos)
|
||||||
|
.where(eq(videos.id, sql`any(${sqlarr(body.map((x) => x.id))})`));
|
||||||
|
const lVids = body.flatMap((x) => {
|
||||||
|
return x.for.map((e) => ({
|
||||||
|
video: vids.find((v) => v.id === x.id)!.pk,
|
||||||
|
entry: {
|
||||||
|
...e,
|
||||||
|
movie:
|
||||||
|
"movie" in e
|
||||||
|
? isUuid(e.movie)
|
||||||
|
? { id: e.movie }
|
||||||
|
: { slug: e.movie }
|
||||||
|
: undefined,
|
||||||
|
serie:
|
||||||
|
"serie" in e
|
||||||
|
? isUuid(e.serie)
|
||||||
|
? { id: e.serie }
|
||||||
|
: { slug: e.serie }
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
const links = await linkVideos(tx, lVids);
|
||||||
|
return status(
|
||||||
|
201,
|
||||||
|
vids.map((x) => ({
|
||||||
|
id: x.id,
|
||||||
|
path: x.path,
|
||||||
|
entries: links[x.pk] ?? [],
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detail: {
|
||||||
|
description: "Link existing videos to existing entries",
|
||||||
|
},
|
||||||
|
body: t.Array(
|
||||||
|
t.Object({
|
||||||
|
id: t.String({
|
||||||
|
description: "Id of the video",
|
||||||
|
format: "uuid",
|
||||||
|
}),
|
||||||
|
for: t.Array(SeedVideo.properties.for.items),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
response: {
|
||||||
|
201: t.Array(
|
||||||
|
t.Object({
|
||||||
|
id: t.String({ format: "uuid" }),
|
||||||
|
path: t.String(),
|
||||||
|
entries: t.Array(
|
||||||
|
t.Object({
|
||||||
|
slug: t.String({ format: "slug" }),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
422: KError,
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user