mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 21:54:49 -04:00
Extract movie insert to a show insert method
This commit is contained in:
parent
b9c022f614
commit
ec3d48ac79
89
api/src/controllers/seed/insert/shows.ts
Normal file
89
api/src/controllers/seed/insert/shows.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { eq, sql } from "drizzle-orm";
|
||||||
|
import { db } from "~/db";
|
||||||
|
import { showTranslations, shows } from "~/db/schema";
|
||||||
|
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||||
|
import type { SeedMovie } from "~/models/movie";
|
||||||
|
import { getYear } from "~/utils";
|
||||||
|
import { processOptImage } from "../images";
|
||||||
|
|
||||||
|
type Show = typeof shows.$inferInsert;
|
||||||
|
type ShowTrans = typeof showTranslations.$inferInsert;
|
||||||
|
|
||||||
|
export const insertShow = async (
|
||||||
|
show: Show,
|
||||||
|
translations: SeedMovie["translations"],
|
||||||
|
) => {
|
||||||
|
return await db.transaction(async (tx) => {
|
||||||
|
const ret = await insertBaseShow(tx, show);
|
||||||
|
if ("status" in ret) return ret;
|
||||||
|
|
||||||
|
const trans: ShowTrans[] = await Promise.all(
|
||||||
|
Object.entries(translations).map(async ([lang, tr]) => ({
|
||||||
|
pk: ret.pk,
|
||||||
|
language: lang,
|
||||||
|
...tr,
|
||||||
|
poster: await processOptImage(tr.poster),
|
||||||
|
thumbnail: await processOptImage(tr.thumbnail),
|
||||||
|
logo: await processOptImage(tr.logo),
|
||||||
|
banner: await processOptImage(tr.banner),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
await tx
|
||||||
|
.insert(showTranslations)
|
||||||
|
.values(trans)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: [showTranslations.pk, showTranslations.language],
|
||||||
|
set: conflictUpdateAllExcept(showTranslations, ["pk", "language"]),
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async function insertBaseShow(
|
||||||
|
tx: Parameters<Parameters<typeof db.transaction>[0]>[0],
|
||||||
|
show: Show,
|
||||||
|
) {
|
||||||
|
function insert() {
|
||||||
|
return tx
|
||||||
|
.insert(shows)
|
||||||
|
.values(show)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: shows.slug,
|
||||||
|
set: conflictUpdateAllExcept(shows, ["pk", "id", "slug", "createdAt"]),
|
||||||
|
// if year is different, this is not an update but a conflict (ex: dune-1984 vs dune-2021)
|
||||||
|
setWhere: sql`date_part('year', ${shows.startAir}) = date_part('year', excluded."start_air")`,
|
||||||
|
})
|
||||||
|
.returning({
|
||||||
|
pk: shows.pk,
|
||||||
|
id: shows.id,
|
||||||
|
slug: shows.slug,
|
||||||
|
// https://stackoverflow.com/questions/39058213/differentiate-inserted-and-updated-rows-in-upsert-using-system-columns/39204667#39204667
|
||||||
|
updated: sql<boolean>`(xmax <> 0)`.as("updated"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let [ret] = await insert();
|
||||||
|
if (ret) return ret;
|
||||||
|
|
||||||
|
// ret is undefined when the conflict's where return false (meaning we have
|
||||||
|
// a conflicting slug but a different air year.
|
||||||
|
// try to insert adding the year at the end of the slug.
|
||||||
|
if (show.startAir && !show.slug.endsWith(`${getYear(show.startAir)}`)) {
|
||||||
|
show.slug = `${show.slug}-${getYear(show.startAir)}`;
|
||||||
|
[ret] = await insert();
|
||||||
|
if (ret) return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if at this point ret is still undefined, we could not reconciliate.
|
||||||
|
// simply bail and let the caller handle this.
|
||||||
|
const [{ id }] = await db
|
||||||
|
.select({ id: shows.id })
|
||||||
|
.from(shows)
|
||||||
|
.where(eq(shows.slug, show.slug))
|
||||||
|
.limit(1);
|
||||||
|
return {
|
||||||
|
status: 409 as const,
|
||||||
|
id,
|
||||||
|
slug: show.slug,
|
||||||
|
};
|
||||||
|
}
|
@ -1,23 +1,19 @@
|
|||||||
import { eq, inArray, sql } from "drizzle-orm";
|
import { inArray, sql } from "drizzle-orm";
|
||||||
import { t } from "elysia";
|
import { t } from "elysia";
|
||||||
import { db } from "~/db";
|
import { db } from "~/db";
|
||||||
import {
|
import {
|
||||||
entries,
|
type entries,
|
||||||
entryTranslations,
|
entryTranslations,
|
||||||
entryVideoJointure as evj,
|
entryVideoJointure as evj,
|
||||||
showTranslations,
|
|
||||||
shows,
|
|
||||||
videos,
|
videos,
|
||||||
} from "~/db/schema";
|
} from "~/db/schema";
|
||||||
import { conflictUpdateAllExcept } from "~/db/utils";
|
import { conflictUpdateAllExcept } from "~/db/utils";
|
||||||
import type { SeedMovie } from "~/models/movie";
|
import type { SeedMovie } from "~/models/movie";
|
||||||
import { processOptImage } from "./images";
|
import { getYear } from "~/utils";
|
||||||
|
import { insertEntries } from "./insert/entries";
|
||||||
|
import { insertShow } from "./insert/shows";
|
||||||
import { guessNextRefresh } from "./refresh";
|
import { guessNextRefresh } from "./refresh";
|
||||||
|
|
||||||
type Show = typeof shows.$inferInsert;
|
|
||||||
type ShowTrans = typeof showTranslations.$inferInsert;
|
|
||||||
type Entry = typeof entries.$inferInsert;
|
|
||||||
|
|
||||||
export const SeedMovieResponse = t.Object({
|
export const SeedMovieResponse = t.Object({
|
||||||
id: t.String({ format: "uuid" }),
|
id: t.String({ format: "uuid" }),
|
||||||
slug: t.String({ format: "slug", examples: ["bubble"] }),
|
slug: t.String({ format: "slug", examples: ["bubble"] }),
|
||||||
@ -30,7 +26,8 @@ export type SeedMovieResponse = typeof SeedMovieResponse.static;
|
|||||||
export const seedMovie = async (
|
export const seedMovie = async (
|
||||||
seed: SeedMovie,
|
seed: SeedMovie,
|
||||||
): Promise<
|
): Promise<
|
||||||
| (SeedMovieResponse & { status: "Created" | "OK" | "Conflict" })
|
| (SeedMovieResponse & { updated: boolean })
|
||||||
|
| { status: 409; id: string; slug: string }
|
||||||
| { status: 422; message: string }
|
| { status: 422; message: string }
|
||||||
> => {
|
> => {
|
||||||
if (seed.slug === "random") {
|
if (seed.slug === "random") {
|
||||||
@ -44,116 +41,27 @@ export const seedMovie = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { translations, videos: vids, ...bMovie } = seed;
|
const { translations, videos: vids, ...bMovie } = seed;
|
||||||
|
const nextRefresh = guessNextRefresh(bMovie.airDate ?? new Date());
|
||||||
|
|
||||||
const ret = await db.transaction(async (tx) => {
|
const ret = await insertShow(
|
||||||
const movie: Show = {
|
{
|
||||||
kind: "movie",
|
kind: "movie",
|
||||||
startAir: bMovie.airDate,
|
startAir: bMovie.airDate,
|
||||||
nextRefresh: guessNextRefresh(bMovie.airDate ?? new Date()),
|
nextRefresh,
|
||||||
...bMovie,
|
...bMovie,
|
||||||
};
|
},
|
||||||
|
translations,
|
||||||
|
);
|
||||||
|
if ("status" in ret) return ret;
|
||||||
|
|
||||||
const insert = () =>
|
// even if never shown to the user, a movie still has an entry.
|
||||||
tx
|
const [entry] = await insertEntries(ret.pk, [
|
||||||
.insert(shows)
|
{
|
||||||
.values(movie)
|
kind: "movie",
|
||||||
.onConflictDoUpdate({
|
nextRefresh,
|
||||||
target: shows.slug,
|
...bMovie,
|
||||||
set: conflictUpdateAllExcept(shows, [
|
},
|
||||||
"pk",
|
]);
|
||||||
"id",
|
|
||||||
"slug",
|
|
||||||
"createdAt",
|
|
||||||
]),
|
|
||||||
// if year is different, this is not an update but a conflict (ex: dune-1984 vs dune-2021)
|
|
||||||
setWhere: sql`date_part('year', ${shows.startAir}) = date_part('year', excluded."start_air")`,
|
|
||||||
})
|
|
||||||
.returning({
|
|
||||||
pk: shows.pk,
|
|
||||||
id: shows.id,
|
|
||||||
slug: shows.slug,
|
|
||||||
// https://stackoverflow.com/questions/39058213/differentiate-inserted-and-updated-rows-in-upsert-using-system-columns/39204667#39204667
|
|
||||||
updated: sql<boolean>`(xmax <> 0)`.as("updated"),
|
|
||||||
});
|
|
||||||
let [ret] = await insert();
|
|
||||||
if (!ret) {
|
|
||||||
// ret is undefined when the conflict's where return false (meaning we have
|
|
||||||
// a conflicting slug but a different air year.
|
|
||||||
// try to insert adding the year at the end of the slug.
|
|
||||||
if (
|
|
||||||
movie.startAir &&
|
|
||||||
!movie.slug.endsWith(`${getYear(movie.startAir)}`)
|
|
||||||
) {
|
|
||||||
movie.slug = `${movie.slug}-${getYear(movie.startAir)}`;
|
|
||||||
[ret] = await insert();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if at this point ret is still undefined, we could not reconciliate.
|
|
||||||
// simply bail and let the caller handle this.
|
|
||||||
if (!ret) {
|
|
||||||
const [{ id }] = await db
|
|
||||||
.select({ id: shows.id })
|
|
||||||
.from(shows)
|
|
||||||
.where(eq(shows.slug, movie.slug))
|
|
||||||
.limit(1);
|
|
||||||
return {
|
|
||||||
status: "Conflict" as const,
|
|
||||||
id,
|
|
||||||
slug: movie.slug,
|
|
||||||
videos: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// even if never shown to the user, a movie still has an entry.
|
|
||||||
const movieEntry: Entry = { type: "movie", ...bMovie };
|
|
||||||
const [entry] = await tx
|
|
||||||
.insert(entries)
|
|
||||||
.values(movieEntry)
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: entries.slug,
|
|
||||||
set: conflictUpdateAllExcept(entries, [
|
|
||||||
"pk",
|
|
||||||
"id",
|
|
||||||
"slug",
|
|
||||||
"createdAt",
|
|
||||||
]),
|
|
||||||
})
|
|
||||||
.returning({ pk: entries.pk });
|
|
||||||
|
|
||||||
const trans: ShowTrans[] = await Promise.all(
|
|
||||||
Object.entries(translations).map(async ([lang, tr]) => ({
|
|
||||||
pk: ret.pk,
|
|
||||||
// TODO: normalize lang or error if invalid
|
|
||||||
language: lang,
|
|
||||||
...tr,
|
|
||||||
poster: await processOptImage(tr.poster),
|
|
||||||
thumbnail: await processOptImage(tr.thumbnail),
|
|
||||||
logo: await processOptImage(tr.logo),
|
|
||||||
banner: await processOptImage(tr.banner),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
await tx
|
|
||||||
.insert(showTranslations)
|
|
||||||
.values(trans)
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: [showTranslations.pk, showTranslations.language],
|
|
||||||
set: conflictUpdateAllExcept(showTranslations, ["pk", "language"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
const entryTrans = trans.map((x) => ({ ...x, pk: entry.pk }));
|
|
||||||
await tx
|
|
||||||
.insert(entryTranslations)
|
|
||||||
.values(entryTrans)
|
|
||||||
.onConflictDoUpdate({
|
|
||||||
target: [entryTranslations.pk, entryTranslations.language],
|
|
||||||
set: conflictUpdateAllExcept(entryTranslations, ["pk", "language"]),
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...ret, entry: entry.pk };
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ret.status === "Conflict") return ret;
|
|
||||||
|
|
||||||
let retVideos: { slug: string }[] = [];
|
let retVideos: { slug: string }[] = [];
|
||||||
if (vids) {
|
if (vids) {
|
||||||
@ -182,13 +90,9 @@ export const seedMovie = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: ret.updated ? "OK" : "Created",
|
updated: ret.updated,
|
||||||
id: ret.id,
|
id: ret.id,
|
||||||
slug: ret.slug,
|
slug: ret.slug,
|
||||||
videos: retVideos,
|
videos: retVideos,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function getYear(date: string) {
|
|
||||||
return new Date(date).getUTCFullYear();
|
|
||||||
}
|
|
||||||
|
@ -20,7 +20,7 @@ export const videos = new Elysia({ prefix: "/videos", tags: ["videos"] })
|
|||||||
response: { 200: "video" },
|
response: { 200: "video" },
|
||||||
})
|
})
|
||||||
.post(
|
.post(
|
||||||
"/",
|
"",
|
||||||
async ({ body }) => {
|
async ({ body }) => {
|
||||||
return await db
|
return await db
|
||||||
.insert(videosT)
|
.insert(videosT)
|
||||||
|
@ -12,7 +12,7 @@ export const Entry = t.Union([Episode, MovieEntry, Special]);
|
|||||||
export type Entry = typeof Entry.static;
|
export type Entry = typeof Entry.static;
|
||||||
|
|
||||||
export const SeedEntry = t.Union([SeedEpisode, SeedMovieEntry, SeedSpecial]);
|
export const SeedEntry = t.Union([SeedEpisode, SeedMovieEntry, SeedSpecial]);
|
||||||
export type SeedEntry = typeof Entry.static;
|
export type SeedEntry = typeof SeedEntry.static;
|
||||||
|
|
||||||
export * from "./episode";
|
export * from "./episode";
|
||||||
export * from "./movie-entry";
|
export * from "./movie-entry";
|
||||||
|
@ -6,3 +6,7 @@ export const comment = (str: TemplateStringsArray, ...values: any[]) =>
|
|||||||
.replace(/^[ \t]+/gm, "") // leading spaces
|
.replace(/^[ \t]+/gm, "") // leading spaces
|
||||||
.replace(/([^\n])\n([^\n])/g, "$1 $2") // two lines to space separated line
|
.replace(/([^\n])\n([^\n])/g, "$1 $2") // two lines to space separated line
|
||||||
.replace(/\n{2}/g, "\n"); // keep newline if there's an empty line
|
.replace(/\n{2}/g, "\n"); // keep newline if there's an empty line
|
||||||
|
|
||||||
|
export function getYear(date: string) {
|
||||||
|
return new Date(date).getUTCFullYear();
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user