Add staff seeding

This commit is contained in:
Zoe Roux 2025-03-10 01:31:51 +01:00
parent 46833ac06f
commit 43a128ebe8
No known key found for this signature in database
9 changed files with 121 additions and 29 deletions

View File

@ -0,0 +1,47 @@
import { eq } from "drizzle-orm";
import { db } from "~/db";
import { roles, staff } from "~/db/schema";
import { conflictUpdateAllExcept } from "~/db/utils";
import type { SeedStaff } from "~/models/staff";
import { processOptImage } from "../images";
export const insertStaff = async (
seed: SeedStaff[] | undefined,
showPk: number,
) => {
if (!seed?.length) return [];
return await db.transaction(async (tx) => {
const people = seed.map((x) => ({
...x.staff,
image: processOptImage(x.staff.image),
}));
const ret = await tx
.insert(staff)
.values(people)
.onConflictDoUpdate({
target: staff.slug,
set: conflictUpdateAllExcept(staff, ["pk", "id", "slug", "createdAt"]),
})
.returning({ pk: staff.pk, id: staff.id, slug: staff.slug });
const rval = seed.map((x, i) => ({
showPk,
staffPk: ret[i].pk,
kind: x.kind,
order: i,
character: {
...x.character,
image: processOptImage(x.character.image),
},
}));
// always replace all roles. this is because:
// - we want `order` to stay in sync (& without duplicates)
// - we don't have ways to identify a role so we can't onConflict
await tx.delete(roles).where(eq(roles.showPk, showPk));
await tx.insert(roles).values(rval);
return ret;
});
};

View File

@ -5,6 +5,7 @@ import { processOptImage } from "./images";
import { insertCollection } from "./insert/collection";
import { insertEntries } from "./insert/entries";
import { insertShow, updateAvailableCount } from "./insert/shows";
import { insertStaff } from "./insert/staff";
import { insertStudios } from "./insert/studios";
import { guessNextRefresh } from "./refresh";
@ -26,6 +27,12 @@ export const SeedMovieResponse = t.Object({
slug: t.String({ format: "slug", examples: ["disney"] }),
}),
),
staff: t.Array(
t.Object({
id: t.String({ format: "uuid" }),
slug: t.String({ format: "slug", examples: ["hiroyuki-sawano"] }),
}),
),
});
export type SeedMovieResponse = typeof SeedMovieResponse.static;
@ -46,7 +53,7 @@ export const seedMovie = async (
seed.slug = `random-${getYear(seed.airDate)}`;
}
const { translations, videos, collection, studios, ...movie } = seed;
const { translations, videos, collection, studios, staff, ...movie } = seed;
const nextRefresh = guessNextRefresh(movie.airDate ?? new Date());
const original = translations[movie.originalLanguage];
if (!original) {
@ -101,6 +108,7 @@ export const seedMovie = async (
await updateAvailableCount([show.pk], false);
const retStudios = await insertStudios(studios, show.pk);
const retStaff = await insertStaff(staff, show.pk);
return {
updated: show.updated,
@ -109,5 +117,6 @@ export const seedMovie = async (
videos: entry.videos,
collection: col,
studios: retStudios,
staff: retStaff,
};
};

View File

@ -6,6 +6,7 @@ import { insertCollection } from "./insert/collection";
import { insertEntries } from "./insert/entries";
import { insertSeasons } from "./insert/seasons";
import { insertShow, updateAvailableCount } from "./insert/shows";
import { insertStaff } from "./insert/staff";
import { insertStudios } from "./insert/studios";
import { guessNextRefresh } from "./refresh";
@ -53,6 +54,12 @@ export const SeedSerieResponse = t.Object({
slug: t.String({ format: "slug", examples: ["mappa"] }),
}),
),
staff: t.Array(
t.Object({
id: t.String({ format: "uuid" }),
slug: t.String({ format: "slug", examples: ["hiroyuki-sawano"] }),
}),
),
});
export type SeedSerieResponse = typeof SeedSerieResponse.static;
@ -80,6 +87,7 @@ export const seedSerie = async (
extras,
collection,
studios,
staff,
...serie
} = seed;
const nextRefresh = guessNextRefresh(serie.startAir ?? new Date());
@ -127,6 +135,7 @@ export const seedSerie = async (
await updateAvailableCount([show.pk]);
const retStudios = await insertStudios(studios, show.pk);
const retStaff = await insertStaff(staff, show.pk);
return {
updated: show.updated,
@ -137,5 +146,6 @@ export const seedSerie = async (
extras: retExtras,
collection: col,
studios: retStudios,
staff: retStaff,
};
};

View File

@ -7,7 +7,8 @@ import { roles, staff } from "~/db/schema/staff";
import { getColumns, sqlarr } from "~/db/utils";
import { KError } from "~/models/error";
import type { MovieStatus } from "~/models/movie";
import { Role, RoleWShow, RoleWStaff, Staff } from "~/models/staff";
import { Role, Staff } from "~/models/staff";
import { RoleWShow, RoleWStaff } from "~/models/staff-roles";
import {
Filter,
type FilterDef,
@ -42,11 +43,13 @@ const staffRoleSort = Sort(
name: { sql: staff.name, accessor: (x) => x.staff.name },
latinName: { sql: staff.latinName, accessor: (x) => x.staff.latinName },
characterName: {
sql: sql`${roles.character}->'name'`,
sql: sql`${roles.character}->>'name'`,
isNullable: true,
accessor: (x) => x.character.name,
},
characterLatinName: {
sql: sql`${roles.character}->'latinName'`,
sql: sql`${roles.character}->>'latinName'`,
isNullable: true,
accessor: (x) => x.character.latinName,
},
},

View File

@ -42,6 +42,7 @@ export const staff = schema.table("staff", {
export const roles = schema.table(
"roles",
{
pk: integer().primaryKey().generatedAlwaysAsIdentity(),
showPk: integer()
.notNull()
.references(() => shows.pk, { onDelete: "cascade" }),
@ -53,7 +54,6 @@ export const roles = schema.table(
character: jsonb().$type<Character>(),
},
(t) => [
primaryKey({ columns: [t.showPk, t.staffPk] }),
index("role_kind").using("hash", t.kind),
index("role_order").on(t.order),
],

View File

@ -2,6 +2,7 @@ import { t } from "elysia";
import type { Prettify } from "~/utils";
import { SeedCollection } from "./collections";
import { bubble, bubbleImages, registerExamples } from "./examples";
import { SeedStaff } from "./staff";
import { SeedStudio, Studio } from "./studio";
import {
DbMetadata,
@ -90,6 +91,7 @@ export const SeedMovie = t.Intersect([
videos: t.Optional(t.Array(t.String({ format: "uuid" }), { default: [] })),
collection: t.Optional(SeedCollection),
studios: t.Optional(t.Array(SeedStudio, { default: [] })),
staff: t.Optional(t.Array(SeedStaff, { default: [] })),
}),
]);
export type SeedMovie = Prettify<typeof SeedMovie.static>;

View File

@ -4,6 +4,7 @@ import { SeedCollection } from "./collections";
import { SeedEntry, SeedExtra } from "./entry";
import { bubbleImages, madeInAbyss, registerExamples } from "./examples";
import { SeedSeason } from "./season";
import { SeedStaff } from "./staff";
import { SeedStudio, Studio } from "./studio";
import {
DbMetadata,
@ -106,6 +107,7 @@ export const SeedSerie = t.Intersect([
extras: t.Optional(t.Array(SeedExtra, { default: [] })),
collection: t.Optional(SeedCollection),
studios: t.Optional(t.Array(SeedStudio, { default: [] })),
staff: t.Optional(t.Array(SeedStaff, { default: [] })),
}),
]);
export type SeedSerie = typeof SeedSerie.static;

View File

@ -0,0 +1,19 @@
import { t } from "elysia";
import { Show } from "./show";
import { Role, Staff } from "./staff";
export const RoleWShow = t.Intersect([
Role,
t.Object({
show: Show,
}),
]);
export type RoleWShow = typeof RoleWShow.static;
export const RoleWStaff = t.Intersect([
Role,
t.Object({
staff: Staff,
}),
]);
export type RoleWStaff = typeof RoleWStaff.static;

View File

@ -1,6 +1,5 @@
import { t } from "elysia";
import { Show } from "./show";
import { DbMetadata, ExternalId, Image, Resource } from "./utils";
import { DbMetadata, ExternalId, Image, Resource, SeedImage } from "./utils";
export const Character = t.Object({
name: t.String(),
@ -22,30 +21,31 @@ export const Role = t.Object({
});
export type Role = typeof Role.static;
export const Staff = t.Intersect([
Resource(),
t.Object({
name: t.String(),
latinName: t.Nullable(t.String()),
image: t.Nullable(Image),
externalId: ExternalId(),
}),
DbMetadata,
]);
const StaffData = t.Object({
name: t.String(),
latinName: t.Nullable(t.String()),
image: t.Nullable(Image),
externalId: ExternalId(),
});
export const Staff = t.Intersect([Resource(), StaffData, DbMetadata]);
export type Staff = typeof Staff.static;
export const RoleWShow = t.Intersect([
Role,
export const SeedStaff = t.Intersect([
t.Omit(Role, ["character"]),
t.Object({
show: Show,
character: t.Intersect([
t.Omit(Character, ["image"]),
t.Object({
image: t.Nullable(SeedImage),
}),
]),
staff: t.Intersect([
t.Object({
slug: t.String({ format: "slug" }),
image: t.Nullable(SeedImage),
}),
t.Omit(StaffData, ["image"]),
]),
}),
]);
export type RoleWShow = typeof RoleWShow.static;
export const RoleWStaff = t.Intersect([
Role,
t.Object({
staff: Staff
}),
]);
export type RoleWStaff = typeof RoleWStaff.static;
export type SeedStaff = typeof SeedStaff.static;