🚧 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 age_rating_id String
country_id String country_id String
related_media media_relations [not null] related_media media_relations [not null]
approver_id String
created_by_id String
updated_by_id String updated_by_id String
deleted_by_id String deleted_by_id String
home_media_banners home_media_banners [not null] home_media_banners home_media_banners [not null]
@ -296,7 +294,7 @@ Table media_collections {
Table media_trailers { Table media_trailers {
media_id String [pk] media_id String [pk]
url String url String
embed_url String embed_url String [unique]
small_image_url String small_image_url String
large_image_url String large_image_url String
maximum_image_url String maximum_image_url String
@ -661,7 +659,7 @@ Table countries {
id String [pk] id String [pk]
name String [not null] name String [not null]
slug String [not null] slug String [not null]
code String [not null] code String [unique, not null]
flag String flag String
banner String banner String
UserCountry users [not null] 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 age_rating_id String? @db.Uuid
country_id String? @db.Uuid country_id String? @db.Uuid
related_media MediaRelation[] @relation("MediaRelationRelatedMedia") related_media MediaRelation[] @relation("MediaRelationRelatedMedia")
approver_id String? @db.Uuid
created_by_id String? @db.Uuid
updated_by_id String? @db.Uuid updated_by_id String? @db.Uuid
deleted_by_id String? @db.Uuid deleted_by_id String? @db.Uuid
home_media_banners HomeMediaBanner[] home_media_banners HomeMediaBanner[]
@ -407,7 +405,7 @@ model MediaCollection {
model MediaTrailer { model MediaTrailer {
media_id String @id @db.Uuid media_id String @id @db.Uuid
url String? @db.VarChar(255) 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) small_image_url String? @db.VarChar(255)
large_image_url String? @db.VarChar(255) large_image_url String? @db.VarChar(255)
maximum_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)) id String @id @db.Uuid @default(uuid(7))
name String @db.VarChar(155) name String @db.VarChar(155)
slug String @db.VarChar(165) slug String @db.VarChar(165)
code String @db.VarChar(3) code String @db.VarChar(3) @unique
flag String? @db.VarChar(255) flag String? @db.VarChar(255)
banner 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 { AppError } from "../../../helpers/error/instances/app";
import { prisma } from "../../../utils/databases/prisma/connection"; import { prisma } from "../../../utils/databases/prisma/connection";
import { generateUUIDv7 } from "../../../helpers/databases/uuidv7"; import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
import { MediaFullInfoResponse } from "../types/mediaFullInfo.type";
import { SystemAccountId } from "../../../config/account/system";
/** /**
* Media Payload Construction and Upsert * 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. * @param data - The full media data for constructing the media payload.
* @returns The inserted or updated media record. * @returns The inserted or updated media record.
*/ */
export const InsertMediaRepository = async ({ export const InsertMediaRepository = async ({ payload }: { payload: MediaFullInfoResponse["data"] }) => {
malId,
payload,
}: {
malId: number;
payload: Omit<Prisma.MediaUncheckedCreateInput, "id">;
}) => {
try { try {
return await prisma.media.upsert({ const constructMediaPayload: Prisma.MediaUpsertArgs["create"] = {
where: { malId }, mal_id: payload.mal_id,
update: payload, title: payload.title,
create: { title_secondary: payload.title_english,
id: generateUUIDv7(), title_original: payload.title_japanese,
...payload, 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) { } catch (error) {
throw new AppError(500, "Failed to insert media", 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 { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { bulkInsertGenresRepository } from "../../repositories/bulkInsertGenres.repository";
import { InsertMediaRepository } from "../../repositories/bulkinsertMedia.repository"; import { InsertMediaRepository } from "../../repositories/bulkinsertMedia.repository";
import { bulkInsertStudiosRepository } from "../../repositories/bulkInsertStudios.repository";
import { MediaFullInfoResponse } from "../../types/mediaFullInfo.type"; 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"; import { getContentReferenceAPI } from "../../../../config/apis/jikan/media.reference";
export const bulkInsertAnimeService = async (malId: number) => { export const bulkInsertAnimeService = async (malId: number) => {
try { try {
const { baseURL, getMediaFullInfo } = getContentReferenceAPI(malId); const { baseURL, getMediaFullInfo } = getContentReferenceAPI(malId);
const mediaFullInfo = (await fetch(baseURL + getMediaFullInfo).then((res) => const mediaFullInfo = (await fetch(baseURL + getMediaFullInfo).then((res) => res.json())) as MediaFullInfoResponse;
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({ const insertedMedia = await InsertMediaRepository({
malId: mediaFullInfo.data.mal_id, payload: mediaFullInfo.data,
payload: constructMediaPayload,
}); });
return insertedMedia; 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 { export interface MediaFullInfoResponse {
data: Data; data: Data;
} }
@ -14,7 +14,7 @@ interface Data {
title_english: string; title_english: string;
title_japanese: string; title_japanese: string;
title_synonyms: string[]; title_synonyms: string[];
type: MediaType; type: string;
source: string; source: string;
episodes: number; episodes: number;
status: string; status: string;
@ -30,7 +30,7 @@ interface Data {
favorites: number; favorites: number;
synopsis: string; synopsis: string;
background: string; background: string;
season: string; season: media_season;
year: number; year: number;
broadcast: Broadcast; broadcast: Broadcast;
producers: Genre[]; producers: Genre[];