🚧 wip: update media bulk insert for new schema

This commit is contained in:
2026-05-30 21:00:00 +07:00
parent 2faaeabf92
commit 1694035dc4
9 changed files with 157 additions and 72 deletions

View File

@ -252,8 +252,6 @@ Table medias {
age_rating_id String
country_id String
related_media media_relations [not null]
approver_id String
created_by_id String
updated_by_id String
deleted_by_id String
home_media_banners home_media_banners [not null]
@ -296,7 +294,7 @@ Table media_collections {
Table media_trailers {
media_id String [pk]
url String
embed_url String
embed_url String [unique]
small_image_url String
large_image_url String
maximum_image_url String
@ -661,7 +659,7 @@ Table countries {
id String [pk]
name String [not null]
slug String [not null]
code String [not null]
code String [unique, not null]
flag String
banner String
UserCountry users [not null]

View File

@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[embed_url]` on the table `media_trailers` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "media_trailers_embed_url_key" ON "media_trailers"("embed_url");

View File

@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[code]` on the table `countries` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "countries_code_key" ON "countries"("code");

View File

@ -0,0 +1,10 @@
/*
Warnings:
- You are about to drop the column `approver_id` on the `medias` table. All the data in the column will be lost.
- You are about to drop the column `created_by_id` on the `medias` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "medias" DROP COLUMN "approver_id",
DROP COLUMN "created_by_id";

View File

@ -366,8 +366,6 @@ model Media {
age_rating_id String? @db.Uuid
country_id String? @db.Uuid
related_media MediaRelation[] @relation("MediaRelationRelatedMedia")
approver_id String? @db.Uuid
created_by_id String? @db.Uuid
updated_by_id String? @db.Uuid
deleted_by_id String? @db.Uuid
home_media_banners HomeMediaBanner[]
@ -407,7 +405,7 @@ model MediaCollection {
model MediaTrailer {
media_id String @id @db.Uuid
url String? @db.VarChar(255)
embed_url String? @db.VarChar(255)
embed_url String? @db.VarChar(255) @unique
small_image_url String? @db.VarChar(255)
large_image_url String? @db.VarChar(255)
maximum_image_url String? @db.VarChar(255)
@ -802,7 +800,7 @@ model Country {
id String @id @db.Uuid @default(uuid(7))
name String @db.VarChar(155)
slug String @db.VarChar(165)
code String @db.VarChar(3)
code String @db.VarChar(3) @unique
flag String? @db.VarChar(255)
banner String? @db.VarChar(255)

View File

@ -2,6 +2,8 @@ import { Prisma } from "@prisma/client";
import { AppError } from "../../../helpers/error/instances/app";
import { prisma } from "../../../utils/databases/prisma/connection";
import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
import { MediaFullInfoResponse } from "../types/mediaFullInfo.type";
import { SystemAccountId } from "../../../config/account/system";
/**
* Media Payload Construction and Upsert
@ -17,21 +19,100 @@ import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
* @param data - The full media data for constructing the media payload.
* @returns The inserted or updated media record.
*/
export const InsertMediaRepository = async ({
malId,
payload,
}: {
malId: number;
payload: Omit<Prisma.MediaUncheckedCreateInput, "id">;
}) => {
export const InsertMediaRepository = async ({ payload }: { payload: MediaFullInfoResponse["data"] }) => {
try {
return await prisma.media.upsert({
where: { malId },
update: payload,
create: {
id: generateUUIDv7(),
...payload,
const constructMediaPayload: Prisma.MediaUpsertArgs["create"] = {
mal_id: payload.mal_id,
title: payload.title,
title_secondary: payload.title_english,
title_original: payload.title_japanese,
title_synonyms: payload.title_synonyms,
trailer: {
connectOrCreate: {
where: {
embed_url: payload.trailer.embed_url,
},
create: {
embed_url: payload.trailer.embed_url,
url: payload.trailer.url,
small_image_url: payload.trailer.images.small_image_url,
large_image_url: payload.trailer.images.large_image_url,
maximum_image_url: payload.trailer.images.maximum_image_url,
},
},
},
synopsis: payload.synopsis,
small_image_url: payload.images.jpg.small_image_url,
medium_image_url: payload.images.jpg.image_url,
large_image_url: payload.images.jpg.large_image_url,
type: {
connectOrCreate: {
where: {
name: payload.type.toLowerCase(),
},
create: {
name: payload.type.toLowerCase(),
},
},
},
source: {
connectOrCreate: {
where: {
name: payload.source.toLowerCase(),
},
create: {
name: payload.source.toLowerCase(),
},
},
},
status: {
connectOrCreate: {
where: {
name: payload.status.toLowerCase(),
},
create: {
name: payload.status.toLowerCase(),
},
},
},
airing: payload.airing,
start_airing: payload.aired.from,
end_airing: payload.aired.to,
age_rating: {
connectOrCreate: {
where: {
name: payload.rating.toLowerCase(),
},
create: {
name: payload.rating.toLowerCase(),
min_age: 0, // Placeholder, as the actual age rating details may require additional mapping
},
},
},
score_total: 0,
score_count: 0,
background: payload.background,
season: payload.season,
year: payload.year,
country: {
connectOrCreate: {
where: {
code: "jpn",
},
create: {
name: "japan",
slug: "japan",
code: "jpn",
},
},
},
broadcast_day: payload.broadcast.day,
};
return await prisma.media.upsert({
where: { mal_id: payload.mal_id },
create: constructMediaPayload,
update: constructMediaPayload,
});
} catch (error) {
throw new AppError(500, "Failed to insert media", error);

View File

@ -1,61 +1,15 @@
import { Prisma } from "@prisma/client";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { bulkInsertGenresRepository } from "../../repositories/bulkInsertGenres.repository";
import { InsertMediaRepository } from "../../repositories/bulkinsertMedia.repository";
import { bulkInsertStudiosRepository } from "../../repositories/bulkInsertStudios.repository";
import { MediaFullInfoResponse } from "../../types/mediaFullInfo.type";
import { generateSlug } from "../../../../helpers/characters/generateSlug";
import { bulkInsertCharWithVAService } from "../internal/bulkInsertCharWithVA.service";
import { generateUUIDv7 } from "../../../../helpers/databases/uuidv7";
import { SystemAccountId } from "../../../../config/account/system";
import { getContentReferenceAPI } from "../../../../config/apis/jikan/media.reference";
export const bulkInsertAnimeService = async (malId: number) => {
try {
const { baseURL, getMediaFullInfo } = getContentReferenceAPI(malId);
const mediaFullInfo = (await fetch(baseURL + getMediaFullInfo).then((res) =>
res.json(),
)) as MediaFullInfoResponse;
const mediaFullInfo = (await fetch(baseURL + getMediaFullInfo).then((res) => res.json())) as MediaFullInfoResponse;
const insertedGenres = await bulkInsertGenresRepository(mediaFullInfo);
const insertedStudios = await bulkInsertStudiosRepository(mediaFullInfo);
const insertedCharacters = await bulkInsertCharWithVAService(malId);
const constructMediaPayload: Prisma.MediaUpsertArgs["create"] = {
id: generateUUIDv7(),
title: mediaFullInfo.data.title,
titleAlternative: mediaFullInfo.data
.titles as unknown as Prisma.InputJsonValue,
slug: await generateSlug(mediaFullInfo.data.title, {
model: "media",
target: "slug",
}),
malId: mediaFullInfo.data.mal_id,
genres: {
connect: insertedGenres.map((id) => ({ id })),
},
studios: {
connect: insertedStudios.map((id) => ({ id })),
},
characters: {
connect: insertedCharacters.map(({ id }) => ({ id })),
},
score: mediaFullInfo.data.score,
pictureMedium: mediaFullInfo.data.images.webp.image_url,
pictureLarge: mediaFullInfo.data.images.webp.large_image_url,
status: mediaFullInfo.data.status,
startAiring: mediaFullInfo.data.aired.from,
endAiring: mediaFullInfo.data.aired.to,
synopsis: mediaFullInfo.data.synopsis,
ageRating: mediaFullInfo.data.rating,
mediaType: mediaFullInfo.data.type,
source: mediaFullInfo.data.source,
onDraft: false,
uploadedBy: SystemAccountId,
};
const insertedMedia = await InsertMediaRepository({
malId: mediaFullInfo.data.mal_id,
payload: constructMediaPayload,
payload: mediaFullInfo.data,
});
return insertedMedia;

View File

@ -0,0 +1,28 @@
import { Prisma } from "@prisma/client";
import { MediaFullInfoResponse } from "../../types/mediaFullInfo.type";
import { prisma } from "../../../../utils/databases/prisma/connection";
interface InsertedProducer {
producer: string[];
licensor: string[];
studio: string[];
}
export const bulkInsertProducerService = async (payload: MediaFullInfoResponse, systemAccountId: string) => {
const insertedPayload: InsertedProducer = {
producer: [],
licensor: [],
studio: [],
};
const insertingMainProducer = await prisma.producer.createMany({
data: payload.data.producers.map((producer) => ({
mal_id: producer.mal_id,
type: producer.type,
name: producer.name,
url: producer.url,
created_by_id: systemAccountId,
})),
skipDuplicates: true,
});
};

View File

@ -1,4 +1,4 @@
import { MediaType } from "@prisma/client";
import { media_season } from "@prisma/client";
export interface MediaFullInfoResponse {
data: Data;
}
@ -14,7 +14,7 @@ interface Data {
title_english: string;
title_japanese: string;
title_synonyms: string[];
type: MediaType;
type: string;
source: string;
episodes: number;
status: string;
@ -30,7 +30,7 @@ interface Data {
favorites: number;
synopsis: string;
background: string;
season: string;
season: media_season;
year: number;
broadcast: Broadcast;
producers: Genre[];