refactor: bulk insert module

This commit is contained in:
2026-07-02 11:34:13 +07:00
parent 7387386aee
commit d914046288
4 changed files with 109 additions and 63 deletions

View File

@ -169,10 +169,11 @@ Table user_watch_histories {
episode episodes [not null] episode episodes [not null]
updated_at DateTime [not null] updated_at DateTime [not null]
user_id String [not null] user_id String [not null]
episode_id String [not null] episode_number Int [not null]
media_id String [not null]
indexes { 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] related_media media_relations [not null]
updated_by_id String updated_by_id String
deleted_by_id String deleted_by_id String
episodes episodes [not null]
home_media_banners home_media_banners [not null] home_media_banners home_media_banners [not null]
saved_to_collections media_collections [not null] saved_to_collections media_collections [not null]
@ -507,9 +509,7 @@ Table staff {
} }
Table episodes { Table episodes {
id String [pk] episode_number Int [not null]
media_id String [not null]
episode Int [not null]
mal_url String mal_url String
forum_url String forum_url String
title String [not null] title String [not null]
@ -528,12 +528,18 @@ Table episodes {
created_by_id String [not null] created_by_id String [not null]
comments comments [not null] comments comments [not null]
watch_histories user_watch_histories [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 { Table videos {
id String [pk] id String [pk]
service video_services [not null] service video_services [not null]
Episode episodes [not null] episode episodes [not null]
video_code String [not null] video_code String [not null]
short_code String short_code String
thumbnail_code String thumbnail_code String
@ -541,7 +547,8 @@ Table videos {
created_at DateTime [default: `now()`, not null] created_at DateTime [default: `now()`, not null]
deleted_at DateTime deleted_at DateTime
updated_at DateTime [not null] 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] created_by_id String [not null]
video_submission video_submissions video_submission video_submissions
} }
@ -603,7 +610,8 @@ Table comments {
updated_at DateTime [not null] updated_at DateTime [not null]
deleted_at DateTime deleted_at DateTime
user_id String [not null] 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] likes comment_likes [not null]
audit_logs comment_audit_logs [not null] audit_logs comment_audit_logs [not null]
reports comment_reports [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.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 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: 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 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.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 Ref: comment_likes.user_id > users.id

View File

@ -1,25 +1,45 @@
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 {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 ( export const bulkInsertEpisodesRepository = async (
payload: Omit<Prisma.EpisodeUncheckedCreateInput, "id">, payload: BulkInsertEpisodesPayload[]
) => { ) => {
try { try {
return await prisma.episode.upsert({ await prisma.$transaction(async (tx) => {
await Promise.all(
payload.map(async (episode) =>
await tx.episode.upsert({
where: { where: {
mediaId_episode: { media_id_episode_number: {
mediaId: payload.mediaId as string, media_id: episode.media_id,
episode: payload.episode as number, episode_number: episode.episode_number
}
}, },
}, update: episode,
update: payload,
create: { create: {
id: generateUUIDv7(), ...episode,
...payload, created_by_id: SystemAccountId
}, }
}); })
)
)
})
} catch (err) { } catch (err) {
throw new AppError(500, "Failed to bulk insert episodes", err); throw new AppError(500, "Failed to bulk insert episodes", err);
} }

View File

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

View File

@ -1,33 +1,35 @@
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import {MediaEpisodeInfoResponse} from "../../types/mediaEpisodeInfo.type"; import {MediaEpisodeInfoResponse} from "../../types/mediaEpisodeInfo.type";
import {AppError} from "../../../../helpers/error/instances/app"; import {AppError} from "../../../../helpers/error/instances/app";
import {SystemAccountId} from "../../../../config/account/system"; import {SystemAccountId} from "../../../../config/account/system";
import {ErrorForwarder} from "../../../../helpers/error/instances/forwarder";
import {bulkInsertEpisodesRepository} from "../../repositories/bulkInsertEpisodes.repository"; import {bulkInsertEpisodesRepository} from "../../repositories/bulkInsertEpisodes.repository";
import {getEpisodeReferenceAPI} from "../../../../config/apis/jikan/episode.reference"; import {getEpisodeReferenceAPI} from "../../../../config/apis/jikan/episode.reference";
import { selectMediaByMalIdRepository } from "../../../media/repositories/SELECT/selectMediaByMalId.repository"; import {findMediaWithMalIdRepository} from "../../repositories/findMediaWithMalId.repository";
export const bulkInsertEpisodeService = async (mal_id: number, page: number = 1) => { export const bulkInsertEpisodeService = async (mal_id: number, page: number = 1) => {
try { try {
const episodeAPI = getEpisodeReferenceAPI(mal_id); const episodeAPI = getEpisodeReferenceAPI(mal_id);
const episodeData: MediaEpisodeInfoResponse = await fetch( const episodeData: MediaEpisodeInfoResponse = await fetch(
`${episodeAPI.baseURL}${episodeAPI.getEpisodeList}?page=${page}`, `${episodeAPI.baseURL}${episodeAPI.getEpisodeList}?page=${page}`,
).then((res) => res.json()); ).then((res) => res.json()) as MediaEpisodeInfoResponse;
const mediaData = await selectMediaByMalIdRepository(mal_id); const mediaData = await findMediaWithMalIdRepository(mal_id)
if (!mediaData) throw new AppError(404, `Media with Mal ID ${mal_id} not found in database`); if (!mediaData) throw new AppError(404, "Media not found");
const insertedEpisodeData = []; const constructedInput = episodeData.data.map(c => ({
episodeData.data.forEach(async (episode) => { media_id: mediaData.id,
insertedEpisodeData.push( episode_number: c.mal_id,
await bulkInsertEpisodesRepository({ title: c.title,
mediaId: mediaData.id!, title_romanji: c.title_romanji,
episode: episode.mal_id, title_origin: c.title_japanese,
name: episode.title, aired_at: c.aired,
score: episode.score, score: c.score,
uploadedBy: SystemAccountId, filler: c.filler,
}), recap: c.recap,
); forum_url: c.forum_url,
}); created_by_id: SystemAccountId
}))
const insertedEpisodes = await bulkInsertEpisodesRepository(constructedInput)
return episodeData; return episodeData;
} catch (err) { } catch (err) {
ErrorForwarder(err); ErrorForwarder(err);