From d9140462881aa3cf5190e97ceae7b7372f7a4c6c Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Thu, 2 Jul 2026 11:34:13 +0700 Subject: [PATCH] refactor: bulk insert module --- prisma/dbml/schema.dbml | 32 ++++++---- .../bulkInsertEpisodes.repository.ts | 64 ++++++++++++------- .../findMediaWithMalId.repository.ts | 14 ++++ .../http/bulkInsertEpisode.service.ts | 62 +++++++++--------- 4 files changed, 109 insertions(+), 63 deletions(-) create mode 100644 src/modules/internal/repositories/findMediaWithMalId.repository.ts diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index 0aad866..ff9e59a 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -169,10 +169,11 @@ Table user_watch_histories { episode episodes [not null] updated_at DateTime [not null] user_id String [not null] - episode_id String [not null] + episode_number Int [not null] + media_id String [not null] indexes { - (user_id, episode_id) [pk] + (user_id, episode_number, media_id) [pk] } } @@ -254,6 +255,7 @@ Table medias { related_media media_relations [not null] updated_by_id String deleted_by_id String + episodes episodes [not null] home_media_banners home_media_banners [not null] saved_to_collections media_collections [not null] @@ -507,9 +509,7 @@ Table staff { } Table episodes { - id String [pk] - media_id String [not null] - episode Int [not null] + episode_number Int [not null] mal_url String forum_url String title String [not null] @@ -528,12 +528,18 @@ Table episodes { created_by_id String [not null] comments comments [not null] watch_histories user_watch_histories [not null] + media medias [not null] + media_id String [not null] + + indexes { + (media_id, episode_number) [pk] + } } Table videos { id String [pk] service video_services [not null] - Episode episodes [not null] + episode episodes [not null] video_code String [not null] short_code String thumbnail_code String @@ -541,7 +547,8 @@ Table videos { created_at DateTime [default: `now()`, not null] deleted_at DateTime updated_at DateTime [not null] - episode_id String [not null] + episode_number Int [not null] + media_id String [not null] created_by_id String [not null] video_submission video_submissions } @@ -603,7 +610,8 @@ Table comments { updated_at DateTime [not null] deleted_at DateTime user_id String [not null] - episode_id String [not null] + episode_number Int [not null] + media_id String [not null] likes comment_likes [not null] audit_logs comment_audit_logs [not null] reports comment_reports [not null] @@ -795,7 +803,7 @@ Ref: user_follows.following_id > users.id Ref: user_watch_histories.user_id > users.id -Ref: user_watch_histories.episode_id > episodes.id +Ref: user_watch_histories.(episode_number, media_id) > episodes.(episode_number, media_id) Ref: collection_members.collection_id > collections.id @@ -877,7 +885,9 @@ Ref: voice_actors.(media_id, character_id) > media_characters.(media_id, charact Ref: episodes.created_by_id > users.id -Ref: videos.episode_id > episodes.id +Ref: episodes.media_id > medias.id + +Ref: videos.(episode_number, media_id) > episodes.(episode_number, media_id) Ref: video_submissions.created_by_id > users.id @@ -893,7 +903,7 @@ Ref: video_service_submissions.video_service_id - video_services.id Ref: comments.user_id > users.id -Ref: comments.episode_id > episodes.id +Ref: comments.(episode_number, media_id) > episodes.(episode_number, media_id) Ref: comment_likes.user_id > users.id diff --git a/src/modules/internal/repositories/bulkInsertEpisodes.repository.ts b/src/modules/internal/repositories/bulkInsertEpisodes.repository.ts index bfd7c58..7f6795a 100644 --- a/src/modules/internal/repositories/bulkInsertEpisodes.repository.ts +++ b/src/modules/internal/repositories/bulkInsertEpisodes.repository.ts @@ -1,26 +1,46 @@ -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 {AppError} from "../../../helpers/error/instances/app"; +import {prisma} from "../../../utils/databases/prisma/connection"; +import {SystemAccountId} from "../../../config/account/system"; + + +export interface BulkInsertEpisodesPayload { + media_id: string + episode_number: number + title: string + title_romanji: string + title_origin: string + aired_at: Date + score: number + filler: boolean + recap: boolean + forum_url: string + created_by_id: string +} export const bulkInsertEpisodesRepository = async ( - payload: Omit, + payload: BulkInsertEpisodesPayload[] ) => { - try { - return await prisma.episode.upsert({ - where: { - mediaId_episode: { - mediaId: payload.mediaId as string, - episode: payload.episode as number, - }, - }, - update: payload, - create: { - id: generateUUIDv7(), - ...payload, - }, - }); - } catch (err) { - throw new AppError(500, "Failed to bulk insert episodes", err); - } + try { + await prisma.$transaction(async (tx) => { + await Promise.all( + payload.map(async (episode) => + await tx.episode.upsert({ + where: { + media_id_episode_number: { + media_id: episode.media_id, + episode_number: episode.episode_number + } + }, + update: episode, + create: { + ...episode, + created_by_id: SystemAccountId + } + }) + ) + ) + }) + } catch (err) { + throw new AppError(500, "Failed to bulk insert episodes", err); + } }; diff --git a/src/modules/internal/repositories/findMediaWithMalId.repository.ts b/src/modules/internal/repositories/findMediaWithMalId.repository.ts new file mode 100644 index 0000000..4180d28 --- /dev/null +++ b/src/modules/internal/repositories/findMediaWithMalId.repository.ts @@ -0,0 +1,14 @@ +import {AppError} from "../../../helpers/error/instances/app"; +import {prisma} from "../../../utils/databases/prisma/connection"; + +export const findMediaWithMalIdRepository = async (malId: number) => { + try { + return await prisma.media.findUnique({ + where: { + mal_id: malId + } + }); + } catch (error) { + throw new AppError(500, "Failed to find media with malId", error) + } +}; \ No newline at end of file diff --git a/src/modules/internal/services/http/bulkInsertEpisode.service.ts b/src/modules/internal/services/http/bulkInsertEpisode.service.ts index ed8d820..6854e5f 100644 --- a/src/modules/internal/services/http/bulkInsertEpisode.service.ts +++ b/src/modules/internal/services/http/bulkInsertEpisode.service.ts @@ -1,35 +1,37 @@ -import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; -import { MediaEpisodeInfoResponse } from "../../types/mediaEpisodeInfo.type"; -import { AppError } from "../../../../helpers/error/instances/app"; -import { SystemAccountId } from "../../../../config/account/system"; -import { bulkInsertEpisodesRepository } from "../../repositories/bulkInsertEpisodes.repository"; -import { getEpisodeReferenceAPI } from "../../../../config/apis/jikan/episode.reference"; -import { selectMediaByMalIdRepository } from "../../../media/repositories/SELECT/selectMediaByMalId.repository"; +import {MediaEpisodeInfoResponse} from "../../types/mediaEpisodeInfo.type"; +import {AppError} from "../../../../helpers/error/instances/app"; +import {SystemAccountId} from "../../../../config/account/system"; +import {ErrorForwarder} from "../../../../helpers/error/instances/forwarder"; +import {bulkInsertEpisodesRepository} from "../../repositories/bulkInsertEpisodes.repository"; +import {getEpisodeReferenceAPI} from "../../../../config/apis/jikan/episode.reference"; +import {findMediaWithMalIdRepository} from "../../repositories/findMediaWithMalId.repository"; export const bulkInsertEpisodeService = async (mal_id: number, page: number = 1) => { - try { - const episodeAPI = getEpisodeReferenceAPI(mal_id); - const episodeData: MediaEpisodeInfoResponse = await fetch( - `${episodeAPI.baseURL}${episodeAPI.getEpisodeList}?page=${page}`, - ).then((res) => res.json()); + try { + const episodeAPI = getEpisodeReferenceAPI(mal_id); + const episodeData: MediaEpisodeInfoResponse = await fetch( + `${episodeAPI.baseURL}${episodeAPI.getEpisodeList}?page=${page}`, + ).then((res) => res.json()) as MediaEpisodeInfoResponse; - const mediaData = await selectMediaByMalIdRepository(mal_id); - if (!mediaData) throw new AppError(404, `Media with Mal ID ${mal_id} not found in database`); + const mediaData = await findMediaWithMalIdRepository(mal_id) + if (!mediaData) throw new AppError(404, "Media not found"); - const insertedEpisodeData = []; - episodeData.data.forEach(async (episode) => { - insertedEpisodeData.push( - await bulkInsertEpisodesRepository({ - mediaId: mediaData.id!, - episode: episode.mal_id, - name: episode.title, - score: episode.score, - uploadedBy: SystemAccountId, - }), - ); - }); - return episodeData; - } catch (err) { - ErrorForwarder(err); - } + const constructedInput = episodeData.data.map(c => ({ + media_id: mediaData.id, + episode_number: c.mal_id, + title: c.title, + title_romanji: c.title_romanji, + title_origin: c.title_japanese, + aired_at: c.aired, + score: c.score, + filler: c.filler, + recap: c.recap, + forum_url: c.forum_url, + created_by_id: SystemAccountId + })) + const insertedEpisodes = await bulkInsertEpisodesRepository(constructedInput) + return episodeData; + } catch (err) { + ErrorForwarder(err); + } };