diff --git a/api/Dockerfile b/api/Dockerfile index f3e7e177..3e434495 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -2,6 +2,7 @@ FROM oven/bun AS builder WORKDIR /app COPY package.json bun.lock . +COPY patches patches RUN bun install --production COPY src src diff --git a/api/bun.lock b/api/bun.lock index cc9e31d5..e5837399 100644 --- a/api/bun.lock +++ b/api/bun.lock @@ -17,6 +17,9 @@ }, }, }, + "patchedDependencies": { + "drizzle-orm@0.39.0": "patches/drizzle-orm@0.39.0.patch", + }, "packages": { "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], diff --git a/api/package.json b/api/package.json index 945aa120..da151870 100644 --- a/api/package.json +++ b/api/package.json @@ -21,5 +21,8 @@ "@types/pg": "^8.11.11", "bun-types": "^1.2.4" }, - "module": "src/index.js" + "module": "src/index.js", + "patchedDependencies": { + "drizzle-orm@0.39.0": "patches/drizzle-orm@0.39.0.patch" + } } diff --git a/api/patches/drizzle-orm@0.39.0.patch b/api/patches/drizzle-orm@0.39.0.patch new file mode 100644 index 00000000..236d1c90 --- /dev/null +++ b/api/patches/drizzle-orm@0.39.0.patch @@ -0,0 +1,111 @@ +diff --git a/node_modules/drizzle-orm/.bun-tag-9fae835e61d5cc75 b/.bun-tag-9fae835e61d5cc75 +new file mode 100644 +index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +diff --git a/pg-core/query-builders/select.d.cts b/pg-core/query-builders/select.d.cts +index b968ebb3f563f37c8c36221dd17cc6f3603270ec..3fda6d0a97997f6bd07ec6a0c83397c0fdd2e97e 100644 +--- a/pg-core/query-builders/select.d.cts ++++ b/pg-core/query-builders/select.d.cts +@@ -98,7 +98,16 @@ export declare abstract class PgSelectQueryBuilderBase; ++ leftJoin: PgSelectJoinFn; ++ /** ++ * For each row of the table, include ++ * values from a matching row of the joined ++ * subquery, if there is a matching row. If not, ++ * all of the columns of the joined subquery ++ * will be set to null. The lateral keyword allows ++ * access to columns after the FROM statement. ++ */ ++ leftJoinLateral: PgSelectJoinFn; + /** + * Executes a `right join` operation by adding another table to the current query. + * +@@ -126,7 +135,7 @@ export declare abstract class PgSelectQueryBuilderBase; ++ rightJoin: PgSelectJoinFn; + /** + * Executes an `inner join` operation, creating a new table by combining rows from two tables that have matching values. + * +@@ -154,7 +163,14 @@ export declare abstract class PgSelectQueryBuilderBase; ++ innerJoin: PgSelectJoinFn; ++ /** ++ * For each row of the table, the joined subquery ++ * needs to have a matching row, or it will ++ * be excluded from results. The lateral keyword allows ++ * access to columns after the FROM statement. ++ */ ++ innerJoinLateral: PgSelectJoinFn; + /** + * Executes a `full join` operation by combining rows from two tables into a new table. + * +@@ -182,7 +198,7 @@ export declare abstract class PgSelectQueryBuilderBase; ++ fullJoin: PgSelectJoinFn; + private createSetOperator; + /** + * Adds `union` set operator to the query. +diff --git a/pg-core/query-builders/select.js b/pg-core/query-builders/select.js +index e54406fcaf68ccfdaf32c8945d4d432212c4cf3f..0441be1e483a7ec02430978b5fac5bf6d863ffc7 100644 +--- a/pg-core/query-builders/select.js ++++ b/pg-core/query-builders/select.js +@@ -98,7 +98,7 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder { + this.tableName = getTableLikeName(table); + this.joinsNotNullableMap = typeof this.tableName === "string" ? { [this.tableName]: true } : {}; + } +- createJoin(joinType) { ++ createJoin(joinType, lateral = false) { + return (table, on) => { + const baseTableName = this.tableName; + const tableName = getTableLikeName(table); +@@ -127,7 +127,7 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder { + if (!this.config.joins) { + this.config.joins = []; + } +- this.config.joins.push({ on, table, joinType, alias: tableName }); ++ this.config.joins.push({ on, table, joinType, alias: tableName, lateral }); + if (typeof tableName === "string") { + switch (joinType) { + case "left": { +@@ -185,6 +185,15 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder { + * ``` + */ + leftJoin = this.createJoin("left"); ++ /** ++ * For each row of the table, include ++ * values from a matching row of the joined ++ * subquery, if there is a matching row. If not, ++ * all of the columns of the joined subquery ++ * will be set to null. The lateral keyword allows ++ * access to columns after the FROM statement. ++ */ ++ leftJoinLateral = this.createJoin("left", true); + /** + * Executes a `right join` operation by adding another table to the current query. + * +@@ -241,6 +250,13 @@ class PgSelectQueryBuilderBase extends TypedQueryBuilder { + * ``` + */ + innerJoin = this.createJoin("inner"); ++ /** ++ * For each row of the table, the joined subquery ++ * needs to have a matching row, or it will ++ * be excluded from results. The lateral keyword allows ++ * access to columns after the FROM statement. ++ */ ++ innerJoinLateral = this.createJoin("inner", true); + /** + * Executes a `full join` operation by combining rows from two tables into a new table. + * diff --git a/api/src/controllers/entries.ts b/api/src/controllers/entries.ts index f43b0751..c18a9787 100644 --- a/api/src/controllers/entries.ts +++ b/api/src/controllers/entries.ts @@ -2,7 +2,13 @@ import type { StaticDecode } from "@sinclair/typebox"; import { type SQL, and, eq, ne, sql } from "drizzle-orm"; import { Elysia, t } from "elysia"; import { db } from "~/db"; -import { entries, entryTranslations, shows } from "~/db/schema"; +import { + entries, + entryTranslations, + entryVideoJoin, + shows, + videos, +} from "~/db/schema"; import { getColumns, sqlarr } from "~/db/utils"; import { Entry, @@ -99,6 +105,20 @@ async function getEntries({ .as("t"); const { pk, name, ...transCol } = getColumns(transQ); + const { guess, createdAt, updatedAt, ...videosCol } = getColumns(videos); + const videosQ = db + .select({ slug: entryVideoJoin.slug, ...videosCol }) + .from(entryVideoJoin) + .where(eq(entryVideoJoin.entryPk, entries.pk)) + .leftJoin(videos, eq(videos.pk, entryVideoJoin.videoPk)) + .as("videos"); + const videosJ = db + .select({ + videos: sql`coalesce(json_agg("videos"), '[]'::json)`.as("videos"), + }) + .from(videosQ) + .as("videos_json"); + const { kind, externalId, @@ -112,24 +132,25 @@ async function getEntries({ .select({ ...entryCol, ...transCol, + videos: videosJ.videos, // specials don't have an `episodeNumber` but a `number` field. - number: sql`${episodeNumber}`.as("order"), + number: episodeNumber, // merge `extraKind` into `kind` kind: sql`case when ${kind} = 'extra' then ${extraKind} else ${kind}::text end`.as( "kind", ), - isExtra: sql`${kind} = 'extra'`.as("isExtra"), // assign more restrained types to make typescript happy. - externalId: sql`${externalId}`.as("externalId"), - order: sql`${order}`.as("order"), - seasonNumber: sql`${seasonNumber}`.as("order"), - episodeNumber: sql`${episodeNumber}`.as("order"), - name: sql`${name}`.as("name"), + externalId: sql`${externalId}`, + order: sql`${order}`, + seasonNumber: sql`${seasonNumber}`, + episodeNumber: sql`${episodeNumber}`, + name: sql`${name}`, }) .from(entries) .innerJoin(transQ, eq(entries.pk, transQ.pk)) + .leftJoinLateral(videosJ, sql`true`) .where( and( filter, diff --git a/api/src/controllers/seed/insert/entries.ts b/api/src/controllers/seed/insert/entries.ts index a3100cbf..126e2562 100644 --- a/api/src/controllers/seed/insert/entries.ts +++ b/api/src/controllers/seed/insert/entries.ts @@ -121,6 +121,7 @@ export const insertEntries = async ( return { videoId: seed.video, entryPk: retEntries[i].pk, + entrySlug: retEntries[i].slug, needRendering: false, }; } @@ -128,8 +129,9 @@ export const insertEntries = async ( return seed.videos.map((x, j) => ({ videoId: x, entryPk: retEntries[i].pk, + entrySlug: retEntries[i].slug, // The first video should not have a rendering. - needRendering: j && seed.videos!.length > 1, + needRendering: j !== 0 && seed.videos!.length > 1, })); }); @@ -142,9 +144,9 @@ export const insertEntries = async ( db .select({ entryPk: sql`vids.entryPk::integer`.as("entry"), - videoPk: sql`${videos.pk}`.as("video"), + videoPk: videos.pk, slug: computeVideoSlug( - sql`${show.slug}::text`, + sql`vids.entrySlug::text`, sql`vids.needRendering::boolean`, ), }) diff --git a/api/src/controllers/seed/insert/studios.ts b/api/src/controllers/seed/insert/studios.ts index 291801d7..f53f93a2 100644 --- a/api/src/controllers/seed/insert/studios.ts +++ b/api/src/controllers/seed/insert/studios.ts @@ -7,8 +7,11 @@ import { processOptImage } from "../images"; type StudioI = typeof studios.$inferInsert; type StudioTransI = typeof studioTranslations.$inferInsert; -export const insertStudios = async (seed: SeedStudio[], showPk: number) => { - if (!seed.length) return []; +export const insertStudios = async ( + seed: SeedStudio[] | undefined, + showPk: number, +) => { + if (!seed?.length) return []; return await db.transaction(async (tx) => { const vals: StudioI[] = seed.map((x) => { diff --git a/api/src/controllers/shows/movies.ts b/api/src/controllers/shows/movies.ts index 44e0c0f4..4d3eca69 100644 --- a/api/src/controllers/shows/movies.ts +++ b/api/src/controllers/shows/movies.ts @@ -39,7 +39,7 @@ export const movies = new Elysia({ prefix: "/movies", tags: ["movies"] }) if (!ret) { return error(404, { status: 404, - message: "Movie not found", + message: `No movie found with id or slug: '${id}'.`, }); } if (!ret.language) { diff --git a/api/src/elysia.ts b/api/src/elysia.ts index 1df911cf..561b23a9 100644 --- a/api/src/elysia.ts +++ b/api/src/elysia.ts @@ -18,6 +18,10 @@ export const base = new Elysia({ name: "base" }) const { code, ...ret } = details; return ret; } + details.errors = details.errors.map((x: any) => { + const { schema, ...err } = x; + return err; + }); return { status: error.status, message: `Validation error on ${details.on}.`, diff --git a/api/src/models/entry/episode.ts b/api/src/models/entry/episode.ts index 3c87b28e..05478465 100644 --- a/api/src/models/entry/episode.ts +++ b/api/src/models/entry/episode.ts @@ -8,6 +8,7 @@ import { SeedImage, TranslationRecord, } from "../utils"; +import { EmbeddedVideo } from "../video"; import { BaseEntry, EntryTranslation } from "./base-entry"; export const BaseEpisode = t.Intersect([ @@ -25,6 +26,9 @@ export const Episode = t.Intersect([ Resource(), EntryTranslation(), BaseEpisode, + t.Object({ + videos: t.Optional(t.Array(EmbeddedVideo)), + }), DbMetadata, ]); export type Episode = Prettify; @@ -34,7 +38,7 @@ export const SeedEpisode = t.Intersect([ t.Object({ thumbnail: t.Nullable(SeedImage), translations: TranslationRecord(EntryTranslation()), - videos: t.Optional(t.Array(t.String({ format: "uuid" }))), + videos: t.Optional(t.Array(t.String({ format: "uuid" }), { default: [] })), }), ]); export type SeedEpisode = Prettify; @@ -45,4 +49,5 @@ registerExamples(Episode, { ...ep.translations.en, ...bubbleImages, slug: `${madeInAbyss.slug}-s${ep.seasonNumber}-e${ep.episodeNumber}`, + videos: [], }); diff --git a/api/src/models/entry/movie-entry.ts b/api/src/models/entry/movie-entry.ts index 95fcc501..ceab00c4 100644 --- a/api/src/models/entry/movie-entry.ts +++ b/api/src/models/entry/movie-entry.ts @@ -9,6 +9,7 @@ import { SeedImage, TranslationRecord, } from "../utils"; +import { EmbeddedVideo } from "../video"; import { BaseEntry, EntryTranslation } from "./base-entry"; export const BaseMovieEntry = t.Intersect( @@ -43,6 +44,9 @@ export const MovieEntry = t.Intersect([ Resource(), MovieEntryTranslation, BaseMovieEntry, + t.Object({ + videos: t.Optional(t.Array(EmbeddedVideo)), + }), DbMetadata, ]); export type MovieEntry = Prettify; @@ -58,7 +62,7 @@ export const SeedMovieEntry = t.Intersect([ t.Object({ poster: t.Nullable(SeedImage) }), ]), ), - videos: t.Optional(t.Array(t.String({ format: "uuid" }))), + videos: t.Optional(t.Array(t.String({ format: "uuid" }), { default: [] })), }), ]); export type SeedMovieEntry = Prettify; diff --git a/api/src/models/entry/special.ts b/api/src/models/entry/special.ts index 248ff9d7..c062f6d4 100644 --- a/api/src/models/entry/special.ts +++ b/api/src/models/entry/special.ts @@ -8,6 +8,7 @@ import { SeedImage, TranslationRecord, } from "../utils"; +import { EmbeddedVideo } from "../video"; import { BaseEntry, EntryTranslation } from "./base-entry"; export const BaseSpecial = t.Intersect( @@ -35,6 +36,9 @@ export const Special = t.Intersect([ Resource(), EntryTranslation(), BaseSpecial, + t.Object({ + videos: t.Optional(t.Array(EmbeddedVideo)), + }), DbMetadata, ]); export type Special = Prettify; @@ -44,7 +48,7 @@ export const SeedSpecial = t.Intersect([ t.Object({ thumbnail: t.Nullable(SeedImage), translations: TranslationRecord(EntryTranslation()), - videos: t.Optional(t.Array(t.String({ format: "uuid" }))), + videos: t.Optional(t.Array(t.String({ format: "uuid" }), { default: [] })), }), ]); export type SeedSpecial = Prettify; diff --git a/api/src/models/examples/made-in-abyss.ts b/api/src/models/examples/made-in-abyss.ts index 6fd213e1..f6dfb4ca 100644 --- a/api/src/models/examples/made-in-abyss.ts +++ b/api/src/models/examples/made-in-abyss.ts @@ -3,15 +3,15 @@ import type { Video } from "~/models/video"; export const madeInAbyssVideo: Video = { id: "3cd436ee-01ff-4f45-ba98-654282531234", - slug: "made-in-abyss-s1e1", - path: "/video/Made in abyss S01E01.mkv", + slug: "made-in-abyss-s1e13", + path: "/video/Made in abyss S01E13.mkv", rendering: "459429fa062adeebedcc2bb04b9965de0262bfa453369783132d261be79021bd", part: null, version: 1, guess: { title: "Made in abyss", season: [1], - episode: [1], + episode: [13], type: "episode", from: "guessit", }, @@ -156,6 +156,7 @@ export const madeInAbyss = { link: "https://www.themoviedb.org/tv/72636/season/1/episode/13", }, }, + videos: [madeInAbyssVideo.id], }, { kind: "special", @@ -240,7 +241,7 @@ export const madeInAbyss = { name: "The Making of MADE IN ABYSS 01", runtime: 17, thumbnail: null, - video: "3cd436ee-01ff-4f45-ba98-654282531234", + video: madeInAbyssVideo.id, }, ], studios: [ diff --git a/api/src/models/movie.ts b/api/src/models/movie.ts index 4f3f7089..5285d374 100644 --- a/api/src/models/movie.ts +++ b/api/src/models/movie.ts @@ -88,9 +88,9 @@ export const SeedMovie = t.Intersect([ }), ]), ), - videos: t.Optional(t.Array(t.String({ format: "uuid" }))), + videos: t.Optional(t.Array(t.String({ format: "uuid" }), { default: [] })), collection: t.Optional(SeedCollection), - studios: t.Array(SeedStudio), + studios: t.Optional(t.Array(SeedStudio, { default: [] })), }), ]); export type SeedMovie = Prettify; diff --git a/api/src/models/serie.ts b/api/src/models/serie.ts index a20c8795..9be6f76f 100644 --- a/api/src/models/serie.ts +++ b/api/src/models/serie.ts @@ -98,9 +98,9 @@ export const SeedSerie = t.Intersect([ ), seasons: t.Array(SeedSeason), entries: t.Array(SeedEntry), - extras: t.Optional(t.Array(SeedExtra)), + extras: t.Optional(t.Array(SeedExtra, { default: [] })), collection: t.Optional(SeedCollection), - studios: t.Array(SeedStudio), + studios: t.Optional(t.Array(SeedStudio, { default: [] })), }), ]); export type SeedSerie = typeof SeedSerie.static; diff --git a/api/src/models/video.ts b/api/src/models/video.ts index 7f8f6681..6822cbb2 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -38,7 +38,7 @@ export const SeedVideo = t.Object({ season: t.Optional(t.Array(t.Integer(), { default: [] })), episode: t.Optional(t.Array(t.Integer(), { default: [] })), // TODO: maybe replace "extra" with the `extraKind` value (aka behind-the-scene, trailer, etc) - type: t.Optional(t.UnionEnum(["episode", "movie", "extra"])), + kind: t.Optional(t.UnionEnum(["episode", "movie", "extra"])), from: t.String({ description: "Name of the tool that made the guess", @@ -71,4 +71,8 @@ export type SeedVideo = typeof SeedVideo.static; export const Video = t.Intersect([Resource(), SeedVideo, DbMetadata]); export type Video = Prettify; +// type used in entry responses +export const EmbeddedVideo = t.Omit(Video, ["createdAt", "updatedAt"]); +export type EmbeddedVideo = Prettify; + registerExamples(Video, bubbleVideo); diff --git a/api/tests/manual.ts b/api/tests/manual.ts new file mode 100644 index 00000000..0fcd9d2f --- /dev/null +++ b/api/tests/manual.ts @@ -0,0 +1,15 @@ +import { db, migrate } from "~/db"; +import { shows, videos } from "~/db/schema"; +import { madeInAbyss, madeInAbyssVideo } from "~/models/examples"; +import { createSerie, createVideo } from "./helpers"; + +// test file used to run manually using `bun tests/manual.ts` + +await migrate(); +await db.delete(shows); +await db.delete(videos); + +const [_, vid] = await createVideo(madeInAbyssVideo); +console.log(vid); +const [__, ser] = await createSerie(madeInAbyss); +console.log(ser); diff --git a/api/tests/movies/get-movie.test.ts b/api/tests/movies/get-movie.test.ts index 2d9c8e54..b282c882 100644 --- a/api/tests/movies/get-movie.test.ts +++ b/api/tests/movies/get-movie.test.ts @@ -21,7 +21,7 @@ describe("Get movie", () => { expectStatus(resp, body).toBe(404); expect(body).toMatchObject({ status: 404, - message: "Movie not found", + message: expect.any(String), }); }); it("Retrive by slug", async () => { diff --git a/api/tests/series/get-entries.test.ts b/api/tests/series/get-entries.test.ts index 1078aea8..ca5a692e 100644 --- a/api/tests/series/get-entries.test.ts +++ b/api/tests/series/get-entries.test.ts @@ -1,14 +1,22 @@ import { beforeAll, describe, expect, it } from "bun:test"; -import { getEntries, getExtras, getUnknowns } from "tests/helpers"; +import { createSerie, createVideo, getEntries, getExtras } from "tests/helpers"; import { expectStatus } from "tests/utils"; -import { seedSerie } from "~/controllers/seed/series"; -import { madeInAbyss } from "~/models/examples"; +import { db } from "~/db"; +import { shows, videos } from "~/db/schema"; +import { madeInAbyss as base, madeInAbyssVideo } from "~/models/examples"; -let miaId = ""; +// make a copy so we can mutate it. +const madeInAbyss = JSON.parse(JSON.stringify(base)) as typeof base; beforeAll(async () => { - const ret = await seedSerie(madeInAbyss); - if (!("status" in ret)) miaId = ret.id; + await db.delete(shows); + await db.delete(videos); + const [_, vid] = await createVideo(madeInAbyssVideo); + for (const entry of madeInAbyss.entries.filter((x) => x.videos?.length)) + entry.videos = [vid[0].id]; + for (const entry of madeInAbyss.extras.filter((x) => x.video)) + entry.video = vid[0].id; + await createSerie(madeInAbyss); }); describe("Get entries", () => { @@ -27,6 +35,19 @@ describe("Get entries", () => { expectStatus(resp, body).toBe(200); expect(body.items).toBeArrayOfSize(madeInAbyss.entries.length); }); + it("With videos", async () => { + const [resp, body] = await getEntries(madeInAbyss.slug, { langs: "en" }); + + expectStatus(resp, body).toBe(200); + expect(body.items[0].videos).toBeArrayOfSize(1); + expect(body.items[0].videos[0]).toMatchObject({ + path: madeInAbyssVideo.path, + slug: madeInAbyssVideo.slug, + version: madeInAbyssVideo.version, + rendering: madeInAbyssVideo.rendering, + part: madeInAbyssVideo.part, + }); + }); }); describe("Get extra", () => { diff --git a/api/tests/series/get-seasons.test.ts b/api/tests/series/get-seasons.test.ts index d49d751f..8aa29284 100644 --- a/api/tests/series/get-seasons.test.ts +++ b/api/tests/series/get-seasons.test.ts @@ -1,14 +1,10 @@ import { beforeAll, describe, expect, it } from "bun:test"; -import { getSeasons } from "tests/helpers"; +import { createSerie, getSeasons } from "tests/helpers"; import { expectStatus } from "tests/utils"; -import { seedSerie } from "~/controllers/seed/series"; import { madeInAbyss } from "~/models/examples"; -let miaId = ""; - beforeAll(async () => { - const ret = await seedSerie(madeInAbyss); - if (!("status" in ret)) miaId = ret.id; + await createSerie(madeInAbyss); }); describe("Get seasons", () => { diff --git a/api/tests/series/seed-serie.test.ts b/api/tests/series/seed-serie.test.ts index 2d5baf56..d6a0111f 100644 --- a/api/tests/series/seed-serie.test.ts +++ b/api/tests/series/seed-serie.test.ts @@ -25,7 +25,12 @@ describe("Serie seeding", () => { where: eq(shows.id, body.id), with: { seasons: { orderBy: seasons.seasonNumber }, - entries: { with: { translations: true } }, + entries: { + with: { + translations: true, + evj: { with: { video: true } }, + }, + }, }, }); @@ -37,7 +42,9 @@ describe("Serie seeding", () => { madeInAbyss.entries.length + madeInAbyss.extras.length, ); - const ep13 = madeInAbyss.entries.find((x) => x.order === 13)!; + const { videos: _, ...ep13 } = madeInAbyss.entries.find( + (x) => x.order === 13, + )!; expect(ret!.entries.find((x) => x.order === 13)).toMatchObject({ ...ep13, slug: "made-in-abyss-s1e13", @@ -48,6 +55,12 @@ describe("Serie seeding", () => { ...ep13.translations.en, }, ], + evj: [ + expect.objectContaining({ + slug: madeInAbyssVideo.slug, + video: expect.objectContaining({ path: madeInAbyssVideo.path }), + }), + ], }); const { number, ...special } = madeInAbyss.entries.find( diff --git a/api/tests/series/studios.test.ts b/api/tests/series/studios.test.ts index a23f9494..772139ef 100644 --- a/api/tests/series/studios.test.ts +++ b/api/tests/series/studios.test.ts @@ -1,11 +1,15 @@ import { beforeAll, describe, expect, it } from "bun:test"; -import { getSerie, getShowsByStudio, getStudio } from "tests/helpers"; +import { + createSerie, + getSerie, + getShowsByStudio, + getStudio, +} from "tests/helpers"; import { expectStatus } from "tests/utils"; -import { seedSerie } from "~/controllers/seed/series"; import { madeInAbyss } from "~/models/examples"; beforeAll(async () => { - await seedSerie(madeInAbyss); + await createSerie(madeInAbyss); }); describe("Get by studio", () => {