From 9dd02d097dea8729bea4cdac2c7ef1012774c481 Mon Sep 17 00:00:00 2001 From: Vivy Bot Date: Thu, 5 Feb 2026 21:47:02 +0700 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20endpoint=20to=20g?= =?UTF-8?q?et=20episode=20details?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../getEpisodeDetails.controller.ts | 25 ++++++++ src/modules/episode/index.ts | 8 +-- .../GET/getEpisodeDetails.repository.ts | 63 +++++++++++++++++++ .../http/getEpisodeDetails.service.ts | 27 ++++++++ .../GET/getMediaIdFromSlug.repository.ts | 15 +++++ 5 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 src/modules/episode/controllers/getEpisodeDetails.controller.ts create mode 100644 src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts create mode 100644 src/modules/episode/services/http/getEpisodeDetails.service.ts create mode 100644 src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts diff --git a/src/modules/episode/controllers/getEpisodeDetails.controller.ts b/src/modules/episode/controllers/getEpisodeDetails.controller.ts new file mode 100644 index 0000000..a1f83f1 --- /dev/null +++ b/src/modules/episode/controllers/getEpisodeDetails.controller.ts @@ -0,0 +1,25 @@ +import { Context } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { returnReadResponse } from "../../../helpers/callback/httpResponse"; +import { getEpisodeDetailsService } from "../services/http/getEpisodeDetails.service"; + +export interface GetEpisodeDetailsParams { + mediaSlug?: string; + episode?: string; +} + +export const getEpisodeDetailsController = async ( + ctx: Context & { params: GetEpisodeDetailsParams }, +) => { + try { + const result = await getEpisodeDetailsService(ctx.params); + return returnReadResponse( + ctx.set, + 200, + "Episode details fetched successfully.", + result, + ); + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/episode/index.ts b/src/modules/episode/index.ts index 1c8b22d..f70af3a 100644 --- a/src/modules/episode/index.ts +++ b/src/modules/episode/index.ts @@ -1,7 +1,7 @@ import Elysia from "elysia"; import { getAllEpisodeFromSpecificMediaController } from "./controllers/getAllEpisodeFromSpecificMedia.controller"; +import { getEpisodeDetailsController } from "./controllers/getEpisodeDetails.controller"; -export const episodeModule = new Elysia({ prefix: "/episodes/:mediaSlug" }).get( - "/", - getAllEpisodeFromSpecificMediaController, -); +export const episodeModule = new Elysia({ prefix: "/episodes/:mediaSlug" }) + .get("/", getAllEpisodeFromSpecificMediaController) + .get("/:episode", getEpisodeDetailsController); diff --git a/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts b/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts new file mode 100644 index 0000000..216f35d --- /dev/null +++ b/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts @@ -0,0 +1,63 @@ +import { AppError } from "../../../../helpers/error/instances/app"; +import { episodeModel } from "../../episode.model"; + +export const getEpisodeDetailsRepository = async (payload: { + mediaId: string; + episode: number; +}) => { + try { + return await episodeModel.findUnique({ + where: { + mediaId_episode: { + mediaId: payload.mediaId, + episode: payload.episode, + }, + deletedAt: null, + }, + select: { + episode: true, + name: true, + score: true, + pictureThumbnail: true, + viewed: true, + likes: true, + updatedAt: true, + uploader: { + select: { + name: true, + username: true, + }, + }, + videos: { + where: { + pendingUpload: false, + deletedAt: null, + }, + select: { + code: true, + service: { + select: { + endpointThumbnail: true, + endpointVideo: true, + endpointDownload: true, + }, + }, + }, + }, + media: { + select: { + slug: true, + title: true, + _count: { + select: { + episodes: true, + }, + }, + }, + }, + }, + }); + } catch (error) { + throw new AppError(500, "Failed to fetch episode details.", error); + } +}; diff --git a/src/modules/episode/services/http/getEpisodeDetails.service.ts b/src/modules/episode/services/http/getEpisodeDetails.service.ts new file mode 100644 index 0000000..fa7d1b3 --- /dev/null +++ b/src/modules/episode/services/http/getEpisodeDetails.service.ts @@ -0,0 +1,27 @@ +import { AppError } from "../../../../helpers/error/instances/app"; +import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; +import { getMediaIdFromSlugRepository } from "../../../media/repositories/GET/getMediaIdFromSlug.repository"; +import { GetEpisodeDetailsParams } from "../../controllers/getEpisodeDetails.controller"; +import { getEpisodeDetailsRepository } from "../../repositories/GET/getEpisodeDetails.repository"; + +export const getEpisodeDetailsService = async ( + params: GetEpisodeDetailsParams, +) => { + try { + if (!params.mediaSlug || !params.episode) + throw new AppError(400, "Media slug and episode are required."); + + const mediaId = await getMediaIdFromSlugRepository(params.mediaSlug); + if (!mediaId?.id) throw new AppError(404, "Media not found."); + + const result = await getEpisodeDetailsRepository({ + mediaId: mediaId.id, + episode: Number(params.episode), + }); + if (!result) throw new AppError(404, "Episode not found."); + + return result; + } catch (error) { + ErrorForwarder(error); + } +}; diff --git a/src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts b/src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts new file mode 100644 index 0000000..b29064e --- /dev/null +++ b/src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts @@ -0,0 +1,15 @@ +import { AppError } from "../../../../helpers/error/instances/app"; +import { mediaModel } from "../../model"; + +export const getMediaIdFromSlugRepository = async (slug: string) => { + try { + return await mediaModel.findUnique({ + where: { slug }, + select: { + id: true, + }, + }); + } catch (error) { + throw new AppError(500, "Failed to fetch media ID from slug.", error); + } +}; From 81cc1057b4be7f33d709e996133a371836ff0290 Mon Sep 17 00:00:00 2001 From: Vivy Bot Date: Thu, 5 Feb 2026 22:20:25 +0700 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20handle=20bigint=20wit?= =?UTF-8?q?h=20json=20serialize=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/helpers/characters/serializeBigInt.ts | 5 +++++ .../episode/repositories/GET/getEpisodeDetails.repository.ts | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/helpers/characters/serializeBigInt.ts diff --git a/src/helpers/characters/serializeBigInt.ts b/src/helpers/characters/serializeBigInt.ts new file mode 100644 index 0000000..5a24683 --- /dev/null +++ b/src/helpers/characters/serializeBigInt.ts @@ -0,0 +1,5 @@ +export const serializeBigInt = (data: T): T => { + return JSON.parse( + JSON.stringify(data, (_, v) => (typeof v === "bigint" ? Number(v) : v)), + ); +}; diff --git a/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts b/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts index 216f35d..7a582bf 100644 --- a/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts +++ b/src/modules/episode/repositories/GET/getEpisodeDetails.repository.ts @@ -1,3 +1,4 @@ +import { serializeBigInt } from "../../../../helpers/characters/serializeBigInt"; import { AppError } from "../../../../helpers/error/instances/app"; import { episodeModel } from "../../episode.model"; @@ -6,7 +7,7 @@ export const getEpisodeDetailsRepository = async (payload: { episode: number; }) => { try { - return await episodeModel.findUnique({ + const result = await episodeModel.findUnique({ where: { mediaId_episode: { mediaId: payload.mediaId, @@ -57,6 +58,8 @@ export const getEpisodeDetailsRepository = async (payload: { }, }, }); + + return serializeBigInt(result); } catch (error) { throw new AppError(500, "Failed to fetch episode details.", error); } From 90bf31a2099e84a9c8f4050752e9e06e77d68be4 Mon Sep 17 00:00:00 2001 From: Vivy Bot Date: Thu, 5 Feb 2026 22:22:05 +0700 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20correct=20payload=20f?= =?UTF-8?q?or=20bulk=20video=20insert=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/internal/services/http/bulkInsertVideo.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/internal/services/http/bulkInsertVideo.service.ts b/src/modules/internal/services/http/bulkInsertVideo.service.ts index d10fa5b..6c7cbea 100644 --- a/src/modules/internal/services/http/bulkInsertVideo.service.ts +++ b/src/modules/internal/services/http/bulkInsertVideo.service.ts @@ -17,6 +17,7 @@ export const bulkInsertVideoService = async ( for (const videoData of episodeData.videos) { const insertedVideo = await bulkInsertVideoRepository({ + pendingUpload: false, episodeId: episodeId.id, serviceId: videoData.service_id, code: videoData.code,