diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index 230d00c..0aad866 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -454,13 +454,16 @@ Table media_external_links { } Table media_characters { - id String [pk] media medias [not null] character characters [not null] voice_actors voice_actors [not null] role character_role [not null] media_id String [not null] character_id String [not null] + + indexes { + (character_id, media_id) [pk] + } } Table characters { @@ -480,11 +483,16 @@ Table characters { Table voice_actors { id String [pk] - media_character_id String [not null] language String [not null] actor_staff staff [not null] staff_id String [not null] + media_id String [not null] + character_id String [not null] media_character media_characters [not null] + + indexes { + (media_id, character_id, staff_id, language) [unique] + } } Table staff { @@ -865,7 +873,7 @@ Ref: media_characters.character_id > characters.id Ref: voice_actors.staff_id > staff.id -Ref: voice_actors.media_character_id > media_characters.id +Ref: voice_actors.(media_id, character_id) > media_characters.(media_id, character_id) Ref: episodes.created_by_id > users.id diff --git a/src/modules/internal/repositories/bulkInsertMediaCharacter.repository.ts b/src/modules/internal/repositories/bulkInsertMediaCharacter.repository.ts index fd6402a..8a07dd5 100644 --- a/src/modules/internal/repositories/bulkInsertMediaCharacter.repository.ts +++ b/src/modules/internal/repositories/bulkInsertMediaCharacter.repository.ts @@ -1,10 +1,123 @@ import {AppError} from "../../../helpers/error/instances/app"; import {MediaChar} from "../types/mediaCharacters"; +import {prisma} from "../../../utils/databases/prisma/connection"; +import {character_role} from "@prisma/client"; -export const bulkInsertMediaCharacterRepository = async (animeMalId: number, characters: MediaChar[]) => { +export const bulkInsertMediaCharacterRepository = async ( + mediaId: string, + characters: MediaChar[] +) => { try { - return characters[0].character.name; + const chars = characters.map(c => ({ + mal_id: c.character.mal_id, + name: c.character.name, + image: c.character.images.webp.image_url, + small_image: c.character.images.webp.small_image_url, + fanpage_url: c.character.url + })); + + const staffs = characters.flatMap(c => + c.voice_actors.map(v => ({ + mal_id: v.person.mal_id, + name: v.person.name, + image: v.person.images.jpg.image_url + })) + ); + + await prisma.$transaction(async (tx) => { + + // Insert Character + await tx.character.createMany({ + data: chars, + skipDuplicates: true + }); + + // Insert Staff + await tx.staff.createMany({ + data: staffs, + skipDuplicates: true + }); + + // Get inserted characters + const insertedChar = await tx.character.findMany({ + where: { + mal_id: { + in: chars.map(c => c.mal_id) + } + }, + select: { + id: true, + mal_id: true + } + }); + + // Get inserted staffs + const insertedStaff = await tx.staff.findMany({ + where: { + mal_id: { + in: staffs.map(s => s.mal_id) + } + }, + select: { + id: true, + mal_id: true + } + }); + + // Build lookup map + const characterMap = new Map( + insertedChar.map(c => [c.mal_id!, c.id]) + ); + const staffMap = new Map( + insertedStaff.map(s => [s.mal_id!, s.id]) + ); + + // Connect media with characters + const mediaCharacters = characters.map(c => { + const characterId = characterMap.get(c.character.mal_id); + if (!characterId) + throw new AppError(500, `Character ${c.character.mal_id} not found`); + + return { + media_id: mediaId, + character_id: characterId, + role: c.role.toLowerCase() as character_role + }; + }); + await tx.mediaCharacter.createMany({ + data: mediaCharacters, + skipDuplicates: true + }); + + // Insert all voice actor of characters + const voiceActors = characters.flatMap(c => { + const characterId = characterMap.get(c.character.mal_id); + if (!characterId) + throw new AppError(500, `Character ${c.character.mal_id} not found`); + + return c.voice_actors.map(v => { + const staffId = staffMap.get(v.person.mal_id); + if (!staffId) throw new AppError(500, `Staff ${v.person.mal_id} not found`); + + return { + media_id: mediaId, + character_id: characterId, + staff_id: staffId, + language: v.language + }; + }); + }); + await tx.voiceActor.createMany({ + data: voiceActors, + skipDuplicates: true + }); + }); + } catch (error) { - throw new AppError(500, "Failed to bulk insert media characters", error); + throw new AppError( + 500, + "Failed to bulk insert media characters", + error + ); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/modules/internal/services/http/bulkInsertAnime.service.ts b/src/modules/internal/services/http/bulkInsertAnime.service.ts index b185ff6..518be7b 100644 --- a/src/modules/internal/services/http/bulkInsertAnime.service.ts +++ b/src/modules/internal/services/http/bulkInsertAnime.service.ts @@ -17,7 +17,7 @@ export const bulkInsertAnimeService = async (malId: number) => { // await bulkInsertMediaCharacterRepository(insertedMedia.mal_id) const mediaChar = await fetch(baseURL + getMediaCharacters).then((res) => res.json()) as MediaCharacters; - await bulkInsertMediaCharacterRepository(insertedMedia.mal_id, mediaChar.data); + return await bulkInsertMediaCharacterRepository(insertedMedia.id, mediaChar.data); return insertedMedia.id; } catch (error) { diff --git a/src/modules/internal/types/mediaCharacters.ts b/src/modules/internal/types/mediaCharacters.ts index 63927cb..0db6546 100644 --- a/src/modules/internal/types/mediaCharacters.ts +++ b/src/modules/internal/types/mediaCharacters.ts @@ -1,13 +1,12 @@ +import {character_role} from "@prisma/client"; + interface StaffVA { mal_id: number; url: string; + name: string; images: { jpg: { image_url: string; - }, - webp: { - image_url: string; - small_image_url: string; } } } @@ -32,15 +31,9 @@ interface Character { } } -enum Role { - Main = "Main", - Supporting = "Supporting", - Background = "Background", -} - export interface MediaChar { character: Character; - role: Role; + role: character_role; favorites: number; voice_actors: voiceActor[]; }