refactor: media character module

This commit is contained in:
2026-07-01 10:52:40 +07:00
parent 502e7b10c6
commit 107582dd32
4 changed files with 133 additions and 19 deletions

View File

@ -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

View File

@ -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
);
}
};

View File

@ -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) {

View File

@ -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[];
}