mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-05-22 07:02:27 -04:00
Add put routes for videos
This commit is contained in:
@@ -28,6 +28,91 @@ const CreatedVideo = t.Object({
|
||||
),
|
||||
});
|
||||
|
||||
async function createVideos(body: SeedVideo[], clearLinks: boolean) {
|
||||
if (body.length === 0) {
|
||||
return { status: 422, message: "No videos" } as const;
|
||||
}
|
||||
return await db.transaction(async (tx) => {
|
||||
let vids: { pk: number; id: string; path: string; guess: Guess }[] = [];
|
||||
try {
|
||||
vids = await tx
|
||||
.insert(videos)
|
||||
.select(unnestValues(body, videos))
|
||||
.onConflictDoUpdate({
|
||||
target: [videos.path],
|
||||
set: conflictUpdateAllExcept(videos, ["pk", "id", "createdAt"]),
|
||||
})
|
||||
.returning({
|
||||
pk: videos.pk,
|
||||
id: videos.id,
|
||||
path: videos.path,
|
||||
guess: videos.guess,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!isUniqueConstraint(e)) throw e;
|
||||
return {
|
||||
status: 409,
|
||||
message: comment`
|
||||
Invalid rendering. A video with the same (rendering, part, version) combo
|
||||
(but with a different path) already exists in db.
|
||||
|
||||
rendering should be computed by the sha of your path (excluding only the version & part numbers)
|
||||
`,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const vidEntries = body.flatMap((x) => {
|
||||
if (!x.for) return [];
|
||||
return x.for.map((e) => ({
|
||||
video: vids.find((v) => v.path === x.path)!.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,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
if (!vidEntries.length) {
|
||||
return vids.map((x) => ({
|
||||
id: x.id,
|
||||
path: x.path,
|
||||
guess: x.guess,
|
||||
entries: [],
|
||||
}));
|
||||
}
|
||||
|
||||
if (clearLinks) {
|
||||
await tx
|
||||
.delete(entryVideoJoin)
|
||||
.where(
|
||||
eq(
|
||||
entryVideoJoin.videoPk,
|
||||
sql`any(${sqlarr(vids.map((x) => x.pk))})`,
|
||||
),
|
||||
);
|
||||
}
|
||||
const links = await linkVideos(tx, vidEntries);
|
||||
|
||||
return vids.map((x) => ({
|
||||
id: x.id,
|
||||
path: x.path,
|
||||
guess: x.guess,
|
||||
entries: links[x.pk] ?? [],
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export const videosWriteH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
||||
.model({
|
||||
video: Video,
|
||||
@@ -38,84 +123,9 @@ export const videosWriteH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
||||
.post(
|
||||
"",
|
||||
async ({ body, status }) => {
|
||||
if (body.length === 0) {
|
||||
return status(422, { status: 422, message: "No videos" });
|
||||
}
|
||||
return await db.transaction(async (tx) => {
|
||||
let vids: { pk: number; id: string; path: string; guess: Guess }[] = [];
|
||||
try {
|
||||
vids = await tx
|
||||
.insert(videos)
|
||||
.select(unnestValues(body, videos))
|
||||
.onConflictDoUpdate({
|
||||
target: [videos.path],
|
||||
set: conflictUpdateAllExcept(videos, ["pk", "id", "createdAt"]),
|
||||
})
|
||||
.returning({
|
||||
pk: videos.pk,
|
||||
id: videos.id,
|
||||
path: videos.path,
|
||||
guess: videos.guess,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!isUniqueConstraint(e)) throw e;
|
||||
return status(409, {
|
||||
status: 409,
|
||||
message: comment`
|
||||
Invalid rendering. A video with the same (rendering, part, version) combo
|
||||
(but with a different path) already exists in db.
|
||||
|
||||
rendering should be computed by the sha of your path (excluding only the version & part numbers)
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
const vidEntries = body.flatMap((x) => {
|
||||
if (!x.for) return [];
|
||||
return x.for.map((e) => ({
|
||||
video: vids.find((v) => v.path === x.path)!.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,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
if (!vidEntries.length) {
|
||||
return status(
|
||||
201,
|
||||
vids.map((x) => ({
|
||||
id: x.id,
|
||||
path: x.path,
|
||||
guess: x.guess,
|
||||
entries: [],
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const links = await linkVideos(tx, vidEntries);
|
||||
|
||||
return status(
|
||||
201,
|
||||
vids.map((x) => ({
|
||||
id: x.id,
|
||||
path: x.path,
|
||||
guess: x.guess,
|
||||
entries: links[x.pk] ?? [],
|
||||
})),
|
||||
);
|
||||
});
|
||||
const ret = await createVideos(body, false);
|
||||
if ("status" in ret) return status(ret.status, ret);
|
||||
return status(201, ret);
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
@@ -123,8 +133,39 @@ export const videosWriteH = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
||||
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.
|
||||
The \`for\` field of each video can be used to link the video to an existing entry.
|
||||
|
||||
If the video was already registered, links will be merged (existing and new ones will be kept).
|
||||
`,
|
||||
},
|
||||
body: t.Array(SeedVideo),
|
||||
response: {
|
||||
201: t.Array(CreatedVideo),
|
||||
409: {
|
||||
...KError,
|
||||
description:
|
||||
"Invalid rendering specified. (conflicts with an existing video)",
|
||||
},
|
||||
422: KError,
|
||||
},
|
||||
},
|
||||
)
|
||||
.put(
|
||||
"",
|
||||
async ({ body, status }) => {
|
||||
const ret = await createVideos(body, true);
|
||||
if ("status" in ret) return status(ret.status, ret);
|
||||
return status(201, ret);
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
description: comment`
|
||||
Create videos in bulk.
|
||||
Duplicated videos will simply be ignored.
|
||||
|
||||
The \`for\` field of each video can be used to link the video to an existing entry.
|
||||
|
||||
If the video was already registered, links will be overriden (existing will be removed and new ones will be created).
|
||||
`,
|
||||
},
|
||||
body: t.Array(SeedVideo),
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
or,
|
||||
type SQL,
|
||||
sql,
|
||||
inArray,
|
||||
WithSubquery,
|
||||
} from "drizzle-orm";
|
||||
import { alias } from "drizzle-orm/pg-core";
|
||||
import { Elysia, t } from "elysia";
|
||||
@@ -275,6 +277,7 @@ export async function getVideos({
|
||||
preferOriginal = false,
|
||||
relations = [],
|
||||
userId,
|
||||
cte = [],
|
||||
}: {
|
||||
after?: string;
|
||||
limit: number;
|
||||
@@ -285,8 +288,10 @@ export async function getVideos({
|
||||
preferOriginal?: boolean;
|
||||
relations?: (keyof typeof videoRelations)[];
|
||||
userId: string;
|
||||
cte?: WithSubquery[];
|
||||
}) {
|
||||
let ret = await db
|
||||
.with(...cte)
|
||||
.select({
|
||||
...getColumns(videos),
|
||||
...buildRelations(relations, videoRelations, {
|
||||
@@ -620,9 +625,27 @@ export const videosReadH = new Elysia({ tags: ["videos"] })
|
||||
});
|
||||
}
|
||||
|
||||
const titleGuess = db.$with("title_guess").as(
|
||||
db
|
||||
.selectDistinctOn([sql<string>`${videos.guess}->>'title'`], {
|
||||
title: sql<string>`${videos.guess}->>'title'`.as("title"),
|
||||
})
|
||||
.from(videos)
|
||||
.leftJoin(evJoin, eq(videos.pk, evJoin.videoPk))
|
||||
.leftJoin(entries, eq(entries.pk, evJoin.entryPk))
|
||||
.where(eq(entries.showPk, serie.pk)),
|
||||
);
|
||||
|
||||
const languages = processLanguages(langs);
|
||||
const items = await getVideos({
|
||||
filter: eq(entries.showPk, serie.pk),
|
||||
cte: [titleGuess],
|
||||
filter: or(
|
||||
eq(entries.showPk, serie.pk),
|
||||
inArray(
|
||||
sql<string>`${videos.guess}->>'title'`,
|
||||
db.select().from(titleGuess),
|
||||
),
|
||||
),
|
||||
limit,
|
||||
after,
|
||||
query,
|
||||
|
||||
Reference in New Issue
Block a user