Compare commits

...

4 Commits

Author SHA1 Message Date
3d3a9af9dc Merge pull request 'feat/episode-details' (#11) from feat/episode-details into main
All checks were successful
Sync to GitHub / sync (push) Successful in 9s
Reviewed-on: #11
2026-02-05 22:22:51 +07:00
90bf31a209 🐛 fix: correct payload for bulk video insert API
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 37s
2026-02-05 22:22:05 +07:00
81cc1057b4 🐛 fix: handle bigint with json serialize helper 2026-02-05 22:20:25 +07:00
9dd02d097d feat: add endpoint to get episode details 2026-02-05 21:47:02 +07:00
7 changed files with 143 additions and 4 deletions

View File

@ -0,0 +1,5 @@
export const serializeBigInt = <T>(data: T): T => {
return JSON.parse(
JSON.stringify(data, (_, v) => (typeof v === "bigint" ? Number(v) : v)),
);
};

View File

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

View File

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

View File

@ -0,0 +1,66 @@
import { serializeBigInt } from "../../../../helpers/characters/serializeBigInt";
import { AppError } from "../../../../helpers/error/instances/app";
import { episodeModel } from "../../episode.model";
export const getEpisodeDetailsRepository = async (payload: {
mediaId: string;
episode: number;
}) => {
try {
const result = 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,
},
},
},
},
},
});
return serializeBigInt(result);
} catch (error) {
throw new AppError(500, "Failed to fetch episode details.", error);
}
};

View File

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

View File

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

View File

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