feat: create bulk insert for episode endpoint

This commit is contained in:
Rafi Arrafif
2026-01-29 13:00:19 +07:00
parent 1485971cb7
commit 5c7e82cd52
14 changed files with 169 additions and 20 deletions

View File

@ -135,7 +135,8 @@ Table episodes {
mediaId String [not null]
episode Int [not null]
name String [not null]
pictureThumbnail String [not null]
score Decimal [not null, default: 0]
pictureThumbnail String
viewed BigInt [not null, default: 0]
likes BigInt [not null, default: 0]
dislikes BigInt [not null, default: 0]
@ -149,6 +150,10 @@ Table episodes {
videos videos [not null]
user_histories watch_histories [not null]
comments comments [not null]
indexes {
(mediaId, episode) [unique]
}
}
Table episode_likes {

View File

@ -171,7 +171,8 @@ model Episode {
mediaId String @db.Uuid
episode Int
name String @db.VarChar(255)
pictureThumbnail String @db.Text
score Decimal @db.Decimal(4,2) @default(0.00)
pictureThumbnail String? @db.Text
viewed BigInt @default(0)
likes BigInt @default(0)
dislikes BigInt @default(0)
@ -186,6 +187,8 @@ model Episode {
videos Video[] @relation("EpisodeVideos")
user_histories WatchHistory[] @relation("EpisodeWatchHistories")
comments Comment[] @relation("EpisodeComments")
@@unique([mediaId, episode])
@@map("episodes")
}

View File

@ -0,0 +1,8 @@
import { baseURL } from "./baseUrl";
export const getEpisodeReferenceAPI = (malId: number) => {
return {
baseURL,
getEpisodeList: `/anime/${malId}/episodes`,
};
};

View File

@ -1,6 +1,6 @@
import { Context } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { bulkInsertAnimeService } from "../services/bulkInsertAnime.service";
import { bulkInsertAnimeService } from "../services/http/bulkInsertAnime.service";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { bulkInsertCharWithVAService } from "../services/internal/bulkInsertCharWithVA.service";

View File

@ -0,0 +1,24 @@
import { Context } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { bulkInsertEpisodeService } from "../services/http/bulkInsertEpisode.service";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
// add pagination query
export const bulkInsertEpisodeController = async (
ctx: Context & { body: { media_mal_id: number }; query: { page?: number } },
) => {
try {
const bulkInsertResult = await bulkInsertEpisodeService(
ctx.body.media_mal_id,
ctx.query.page,
);
return returnWriteResponse(
ctx.set,
201,
"Success bulk insert for episode",
bulkInsertResult,
);
} catch (err) {
return mainErrorHandler(ctx.set, err);
}
};

View File

@ -1,7 +1,7 @@
import Elysia from "elysia";
import { bulkInsertAnimeController } from "./controllers/bulkInsertAnime.controller";
import { bulkInsertEpisodeController } from "./controllers/bulkInsertEpisode.controller";
export const internalModule = new Elysia({ prefix: "/internal" }).post(
"/media/bulk-insert",
bulkInsertAnimeController,
);
export const internalModule = new Elysia({ prefix: "/internal" })
.post("/media/bulk-insert", bulkInsertAnimeController)
.post("/episode/bulk-insert", bulkInsertEpisodeController);

View File

@ -0,0 +1,26 @@
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";
export const bulkInsertEpisodesRepository = async (
payload: Omit<Prisma.EpisodeUncheckedCreateInput, "id">,
) => {
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);
}
};

View File

@ -1,7 +1,6 @@
import { Prisma } from "@prisma/client";
import { AppError } from "../../../helpers/error/instances/app";
import { prisma } from "../../../utils/databases/prisma/connection";
import { MediaFullInfoResponse } from "../types/mediaFullInfo.type";
import { generateUUIDv7 } from "../../../helpers/databases/uuidv7";
/**

View File

@ -1,14 +1,14 @@
import { Prisma } from "@prisma/client";
import { getContentReferenceAPI } from "../../../config/apis/media.reference";
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
import { bulkInsertGenresRepository } from "../repositories/bulkInsertGenres.repository";
import { InsertMediaRepository } from "../repositories/bulkinsertMedia.repository";
import { bulkInsertStudiosRepository } from "../repositories/bulkInsertStudios.repository";
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/media.reference";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { bulkInsertGenresRepository } from "../../repositories/bulkInsertGenres.repository";
import { InsertMediaRepository } from "../../repositories/bulkinsertMedia.repository";
import { bulkInsertStudiosRepository } from "../../repositories/bulkInsertStudios.repository";
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";
export const bulkInsertAnimeService = async (malId: number) => {
try {

View File

@ -0,0 +1,43 @@
import { Prisma } from "@prisma/client";
import { getEpisodeReferenceAPI } from "../../../../config/apis/episode.reference";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { MediaEpisodeInfoResponse } from "../../types/mediaEpisodeInfo.type";
import { getMediaByMalIdRepository } from "../../../media/repositories/GET/getMediaByMalId.repository";
import { AppError } from "../../../../helpers/error/instances/app";
import { SystemAccountId } from "../../../../config/account/system";
import { bulkInsertEpisodesRepository } from "../../repositories/bulkInsertEpisodes.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());
const mediaData = await getMediaByMalIdRepository(mal_id);
if (!mediaData)
throw new AppError(
404,
`Media with Mal ID ${mal_id} not found in database`,
);
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);
}
};

View File

@ -0,0 +1,22 @@
export interface MediaEpisodeInfoResponse {
pagination: Pagination;
data: Datum[];
}
export interface Datum {
mal_id: number;
url: string;
title: string;
title_japanese: string;
title_romanji: string;
aired: Date;
score: number;
filler: boolean;
recap: boolean;
forum_url: string;
}
export interface Pagination {
last_visible_page: number;
has_next_page: boolean;
}

View File

@ -0,0 +1,3 @@
import { prisma } from "../../utils/databases/prisma/connection";
export const mediaModel = prisma.media;

View File

@ -0,0 +1,12 @@
import { AppError } from "../../../../helpers/error/instances/app";
import { mediaModel } from "../../model";
export const getMediaByMalIdRepository = async (mal_id: number) => {
try {
return await mediaModel.findUnique({
where: { malId: mal_id },
});
} catch (err) {
throw new AppError(500, "Failed to get media by MAL ID", err);
}
};

View File

@ -1,16 +1,20 @@
import { generateUUIDv7 } from "../../../../helpers/databases/uuidv7";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { userModel } from "../../user.model";
import { createUserViaRegisterInput } from "../../user.types";
export const createUserViaRegisterRepository = async (
payload: createUserViaRegisterInput
payload: createUserViaRegisterInput,
) => {
try {
return await userModel.create({
data: {
...payload,
id: generateUUIDv7(),
preference: {
create: {},
create: {
id: generateUUIDv7(),
},
},
},
});