From 17eb272b1d9a1668abac765c8b792310c0788ebf Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Wed, 1 Apr 2026 23:28:22 +0700 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20endpoint=20to=20f?= =?UTF-8?q?etch=20media=20by=20slug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http/getEpisodeDetails.service.ts | 11 +++---- .../http/bulkInsertEpisode.service.ts | 15 +++------- .../controllers/getMediaBySlug.controller.ts | 18 ++++++++++++ src/modules/media/index.ts | 9 +++--- .../selectAllMedia.repository.ts} | 2 +- .../selectMediaByMalId.repository.ts} | 2 +- .../selectMediaIdFromSlug.repository.ts} | 2 +- .../media/schemas/getMediaBySlug.schema.ts | 29 +++++++++++++++++++ .../services/http/getAllMedia.service.ts | 9 ++---- 9 files changed, 66 insertions(+), 31 deletions(-) create mode 100644 src/modules/media/controllers/getMediaBySlug.controller.ts rename src/modules/media/repositories/{GET/getAllMedia.repository.ts => SELECT/selectAllMedia.repository.ts} (85%) rename src/modules/media/repositories/{GET/getMediaByMalId.repository.ts => SELECT/selectMediaByMalId.repository.ts} (80%) rename src/modules/media/repositories/{GET/getMediaIdFromSlug.repository.ts => SELECT/selectMediaIdFromSlug.repository.ts} (82%) create mode 100644 src/modules/media/schemas/getMediaBySlug.schema.ts diff --git a/src/modules/episode/services/http/getEpisodeDetails.service.ts b/src/modules/episode/services/http/getEpisodeDetails.service.ts index fa7d1b3..71f55e0 100644 --- a/src/modules/episode/services/http/getEpisodeDetails.service.ts +++ b/src/modules/episode/services/http/getEpisodeDetails.service.ts @@ -1,17 +1,14 @@ import { AppError } from "../../../../helpers/error/instances/app"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; -import { getMediaIdFromSlugRepository } from "../../../media/repositories/GET/getMediaIdFromSlug.repository"; +import { selectMediaIdFromSlugRepository } from "../../../media/repositories/SELECT/selectMediaIdFromSlug.repository"; import { GetEpisodeDetailsParams } from "../../controllers/getEpisodeDetails.controller"; import { getEpisodeDetailsRepository } from "../../repositories/GET/getEpisodeDetails.repository"; -export const getEpisodeDetailsService = async ( - params: GetEpisodeDetailsParams, -) => { +export const getEpisodeDetailsService = async (params: GetEpisodeDetailsParams) => { try { - if (!params.mediaSlug || !params.episode) - throw new AppError(400, "Media slug and episode are required."); + if (!params.mediaSlug || !params.episode) throw new AppError(400, "Media slug and episode are required."); - const mediaId = await getMediaIdFromSlugRepository(params.mediaSlug); + const mediaId = await selectMediaIdFromSlugRepository(params.mediaSlug); if (!mediaId?.id) throw new AppError(404, "Media not found."); const result = await getEpisodeDetailsRepository({ diff --git a/src/modules/internal/services/http/bulkInsertEpisode.service.ts b/src/modules/internal/services/http/bulkInsertEpisode.service.ts index 326304e..ed8d820 100644 --- a/src/modules/internal/services/http/bulkInsertEpisode.service.ts +++ b/src/modules/internal/services/http/bulkInsertEpisode.service.ts @@ -1,27 +1,20 @@ 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"; import { getEpisodeReferenceAPI } from "../../../../config/apis/jikan/episode.reference"; +import { selectMediaByMalIdRepository } from "../../../media/repositories/SELECT/selectMediaByMalId.repository"; -export const bulkInsertEpisodeService = async ( - mal_id: number, - page: number = 1, -) => { +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 mediaData = await selectMediaByMalIdRepository(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) => { diff --git a/src/modules/media/controllers/getMediaBySlug.controller.ts b/src/modules/media/controllers/getMediaBySlug.controller.ts new file mode 100644 index 0000000..f51a450 --- /dev/null +++ b/src/modules/media/controllers/getMediaBySlug.controller.ts @@ -0,0 +1,18 @@ +import { Context, Static } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { getMediaBySlugSchema } from "../schemas/getMediaBySlug.schema"; + +export const getMediaBySlugController = async (ctx: { + set: Context["set"]; + params: Static; +}) => { + try { + return { + success: true, + status: 200, + message: `Media with slug '${ctx.params.slug}' fetched successfully`, + }; + } catch (error) { + return mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/media/index.ts b/src/modules/media/index.ts index 106d7de..d1cc726 100644 --- a/src/modules/media/index.ts +++ b/src/modules/media/index.ts @@ -1,7 +1,8 @@ import Elysia from "elysia"; import { getAllMediaController } from "./controllers/getAllMedia.controller"; +import { getMediaBySlugController } from "./controllers/getMediaBySlug.controller"; +import { getMediaBySlugSchema } from "./schemas/getMediaBySlug.schema"; -export const mediaModule = new Elysia({ prefix: "/media" }).get( - "/", - getAllMediaController, -); +export const mediaModule = new Elysia({ prefix: "/media" }) + .get("/", getAllMediaController) + .get("/:slug", getMediaBySlugController, getMediaBySlugSchema); diff --git a/src/modules/media/repositories/GET/getAllMedia.repository.ts b/src/modules/media/repositories/SELECT/selectAllMedia.repository.ts similarity index 85% rename from src/modules/media/repositories/GET/getAllMedia.repository.ts rename to src/modules/media/repositories/SELECT/selectAllMedia.repository.ts index 30a5ca8..63ad138 100644 --- a/src/modules/media/repositories/GET/getAllMedia.repository.ts +++ b/src/modules/media/repositories/SELECT/selectAllMedia.repository.ts @@ -1,7 +1,7 @@ import { AppError } from "../../../../helpers/error/instances/app"; import { mediaModel } from "../../model"; -export const getAllMediaRepository = async (page: number) => { +export const selectAllMediaRepository = async (page: number) => { try { const limit = 10; return await mediaModel.findMany({ diff --git a/src/modules/media/repositories/GET/getMediaByMalId.repository.ts b/src/modules/media/repositories/SELECT/selectMediaByMalId.repository.ts similarity index 80% rename from src/modules/media/repositories/GET/getMediaByMalId.repository.ts rename to src/modules/media/repositories/SELECT/selectMediaByMalId.repository.ts index 6d17559..ef3cb08 100644 --- a/src/modules/media/repositories/GET/getMediaByMalId.repository.ts +++ b/src/modules/media/repositories/SELECT/selectMediaByMalId.repository.ts @@ -1,7 +1,7 @@ import { AppError } from "../../../../helpers/error/instances/app"; import { mediaModel } from "../../model"; -export const getMediaByMalIdRepository = async (mal_id: number) => { +export const selectMediaByMalIdRepository = async (mal_id: number) => { try { return await mediaModel.findUnique({ where: { malId: mal_id }, diff --git a/src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts b/src/modules/media/repositories/SELECT/selectMediaIdFromSlug.repository.ts similarity index 82% rename from src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts rename to src/modules/media/repositories/SELECT/selectMediaIdFromSlug.repository.ts index b29064e..56b2399 100644 --- a/src/modules/media/repositories/GET/getMediaIdFromSlug.repository.ts +++ b/src/modules/media/repositories/SELECT/selectMediaIdFromSlug.repository.ts @@ -1,7 +1,7 @@ import { AppError } from "../../../../helpers/error/instances/app"; import { mediaModel } from "../../model"; -export const getMediaIdFromSlugRepository = async (slug: string) => { +export const selectMediaIdFromSlugRepository = async (slug: string) => { try { return await mediaModel.findUnique({ where: { slug }, diff --git a/src/modules/media/schemas/getMediaBySlug.schema.ts b/src/modules/media/schemas/getMediaBySlug.schema.ts new file mode 100644 index 0000000..5bbb2b7 --- /dev/null +++ b/src/modules/media/schemas/getMediaBySlug.schema.ts @@ -0,0 +1,29 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const getMediaBySlugSchema = { + params: t.Object({ + slug: t.String({ description: "The slug of the media to fetch" }), + }), + detail: { + summary: "Fetch a media item by its slug", + description: "Fetch the specified media item using its slug. This endpoint returns the media details if found.", + responses: { + 200: { + description: "Media item fetched successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { type: "boolean", example: true }, + status: { type: "number", example: 200 }, + message: { type: "string", example: "Media fetched successfully" }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; diff --git a/src/modules/media/services/http/getAllMedia.service.ts b/src/modules/media/services/http/getAllMedia.service.ts index 88316f4..89c85d0 100644 --- a/src/modules/media/services/http/getAllMedia.service.ts +++ b/src/modules/media/services/http/getAllMedia.service.ts @@ -1,14 +1,11 @@ import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; -import { getAllMediaRepository } from "../../repositories/GET/getAllMedia.repository"; +import { selectAllMediaRepository } from "../../repositories/SELECT/selectAllMedia.repository"; export const getAllMediaService = async (pagination: string) => { try { - const page = - /^\d+$/.test(pagination) && Number(pagination) > 0 - ? Number(pagination) - : 1; + const page = /^\d+$/.test(pagination) && Number(pagination) > 0 ? Number(pagination) : 1; - return getAllMediaRepository(page); + return selectAllMediaRepository(page); } catch (error) { ErrorForwarder(error); } From b27479cd3e19c6e5afffb1d140d9a72e18cb6b2e Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Wed, 1 Apr 2026 23:38:32 +0700 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20fix:=20resolve=20schema=20ty?= =?UTF-8?q?pe=20error=20in=20getAllMedia=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/getAllMedia.controller.ts | 17 +++--- src/modules/media/index.ts | 5 +- .../media/schemas/getAllMedia.schema.ts | 58 +++++++++++++++++++ 3 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 src/modules/media/schemas/getAllMedia.schema.ts diff --git a/src/modules/media/controllers/getAllMedia.controller.ts b/src/modules/media/controllers/getAllMedia.controller.ts index fdadf8c..25fc820 100644 --- a/src/modules/media/controllers/getAllMedia.controller.ts +++ b/src/modules/media/controllers/getAllMedia.controller.ts @@ -1,19 +1,16 @@ -import { Context } from "elysia"; +import { Context, Static } from "elysia"; import { mainErrorHandler } from "../../../helpers/error/handler"; import { getAllMediaService } from "../services/http/getAllMedia.service"; import { returnReadResponse } from "../../../helpers/callback/httpResponse"; +import { getAllMediaSchema } from "../schemas/getAllMedia.schema"; -export const getAllMediaController = async ( - ctx: Context & { query: { page: string } }, -) => { +export const getAllMediaController = async (ctx: { + set: Context["set"]; + query: Static; +}) => { try { const mediaData = await getAllMediaService(ctx.query.page); - return returnReadResponse( - ctx.set, - 200, - "Media fetched successfully", - mediaData, - ); + return returnReadResponse(ctx.set, 200, "Media fetched successfully", mediaData); } catch (error) { return mainErrorHandler(ctx.set, error); } diff --git a/src/modules/media/index.ts b/src/modules/media/index.ts index d1cc726..7d94df5 100644 --- a/src/modules/media/index.ts +++ b/src/modules/media/index.ts @@ -2,7 +2,8 @@ import Elysia from "elysia"; import { getAllMediaController } from "./controllers/getAllMedia.controller"; import { getMediaBySlugController } from "./controllers/getMediaBySlug.controller"; import { getMediaBySlugSchema } from "./schemas/getMediaBySlug.schema"; +import { getAllMediaSchema } from "./schemas/getAllMedia.schema"; -export const mediaModule = new Elysia({ prefix: "/media" }) - .get("/", getAllMediaController) +export const mediaModule = new Elysia({ prefix: "/media", tags: ["Media"] }) + .get("/", getAllMediaController, getAllMediaSchema) .get("/:slug", getMediaBySlugController, getMediaBySlugSchema); diff --git a/src/modules/media/schemas/getAllMedia.schema.ts b/src/modules/media/schemas/getAllMedia.schema.ts new file mode 100644 index 0000000..9b382e9 --- /dev/null +++ b/src/modules/media/schemas/getAllMedia.schema.ts @@ -0,0 +1,58 @@ +import { t } from "elysia"; +import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema"; + +export const getAllMediaSchema = { + query: t.Object({ + page: t.String({ description: "The page number for pagination", default: "1" }), + }), + detail: { + summary: "Fetch all media items with pagination", + description: + "Fetch a paginated list of all media items. The 'page' query parameter can be used to specify the page number for pagination.", + responses: { + 200: { + description: "Media items fetched successfully", + content: { + "application/json": { + schema: { + type: "object", + properties: { + success: { type: "boolean", example: true }, + status: { type: "number", example: 200 }, + message: { type: "string", example: "Media fetched successfully" }, + data: { + type: "array", + items: { + type: "object", + properties: { + status: { type: "string", example: "Finished Airing" }, + id: { type: "string", example: "12345" }, + title: { type: "string", example: "Example Media Title" }, + slug: { type: "string", example: "example-media-title" }, + malId: { type: "number", example: 67890 }, + pictureMedium: { type: "string", example: "https://example.com/medium.jpg" }, + pictureLarge: { type: "string", example: "https://example.com/large.jpg" }, + country: { type: "string", example: "JP" }, + score: { type: "number", example: 8.5 }, + startAiring: { type: "string", format: "date-time", example: "2023-01-01T00:00:00Z" }, + endAiring: { type: "string", format: "date-time", example: "2023-12-31T23:59:59Z" }, + synopsis: { type: "string", example: "This is an example synopsis of the media item." }, + ageRating: { type: "string", example: "PG-13" }, + mediaType: { type: "string", example: "Anime" }, + source: { type: "string", example: "Manga" }, + onDraft: { type: "boolean", example: false }, + uploadedBy: { type: "string", example: "admin" }, + deletedAt: { type: "string", format: "date-time", nullable: true, example: null }, + createdAt: { type: "string", format: "date-time", example: "2023-01-01T00:00:00Z" }, + updatedAt: { type: "string", format: "date-time", example: "2023-01-02T00:00:00Z" }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} satisfies AppRouteSchema; From 59228f7d1e80e95a696ebac721277c0343607fc7 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Thu, 2 Apr 2026 09:34:03 +0700 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20service=20for=20g?= =?UTF-8?q?etBySlug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../media/controllers/getMediaBySlug.controller.ts | 9 ++++----- .../media/services/http/getMediaBySlug.service.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 src/modules/media/services/http/getMediaBySlug.service.ts diff --git a/src/modules/media/controllers/getMediaBySlug.controller.ts b/src/modules/media/controllers/getMediaBySlug.controller.ts index f51a450..138bedb 100644 --- a/src/modules/media/controllers/getMediaBySlug.controller.ts +++ b/src/modules/media/controllers/getMediaBySlug.controller.ts @@ -1,17 +1,16 @@ import { Context, Static } from "elysia"; import { mainErrorHandler } from "../../../helpers/error/handler"; import { getMediaBySlugSchema } from "../schemas/getMediaBySlug.schema"; +import { getMediaBySlugService } from "../services/http/getMediaBySlug.service"; +import { returnReadResponse } from "../../../helpers/callback/httpResponse"; export const getMediaBySlugController = async (ctx: { set: Context["set"]; params: Static; }) => { try { - return { - success: true, - status: 200, - message: `Media with slug '${ctx.params.slug}' fetched successfully`, - }; + const mediaData = getMediaBySlugService(ctx.params.slug); + return returnReadResponse(ctx.set, 200, "Media fetched successfully", mediaData); } catch (error) { return mainErrorHandler(ctx.set, error); } diff --git a/src/modules/media/services/http/getMediaBySlug.service.ts b/src/modules/media/services/http/getMediaBySlug.service.ts new file mode 100644 index 0000000..b886415 --- /dev/null +++ b/src/modules/media/services/http/getMediaBySlug.service.ts @@ -0,0 +1,9 @@ +import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; + +export const getMediaBySlugService = (slug: string) => { + try { + return `Mengambil media dengan slug '${slug}'`; + } catch (error) { + ErrorForwarder(error); + } +}; From 72f8e9e4eb9e38ec4c0d8b7a31965f6ba1aa147c Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Thu, 2 Apr 2026 09:48:31 +0700 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=91=94=20feat:=20implement=20database?= =?UTF-8?q?=20repository=20for=20get=20media=20by=20slug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../media/controllers/getMediaBySlug.controller.ts | 2 +- .../SELECT/selectMediaBySlug.repository.ts | 12 ++++++++++++ .../media/services/http/getMediaBySlug.service.ts | 9 +++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/modules/media/repositories/SELECT/selectMediaBySlug.repository.ts diff --git a/src/modules/media/controllers/getMediaBySlug.controller.ts b/src/modules/media/controllers/getMediaBySlug.controller.ts index 138bedb..212202f 100644 --- a/src/modules/media/controllers/getMediaBySlug.controller.ts +++ b/src/modules/media/controllers/getMediaBySlug.controller.ts @@ -9,7 +9,7 @@ export const getMediaBySlugController = async (ctx: { params: Static; }) => { try { - const mediaData = getMediaBySlugService(ctx.params.slug); + const mediaData = await getMediaBySlugService(ctx.params.slug); return returnReadResponse(ctx.set, 200, "Media fetched successfully", mediaData); } catch (error) { return mainErrorHandler(ctx.set, error); diff --git a/src/modules/media/repositories/SELECT/selectMediaBySlug.repository.ts b/src/modules/media/repositories/SELECT/selectMediaBySlug.repository.ts new file mode 100644 index 0000000..94490c5 --- /dev/null +++ b/src/modules/media/repositories/SELECT/selectMediaBySlug.repository.ts @@ -0,0 +1,12 @@ +import { AppError } from "../../../../helpers/error/instances/app"; +import { prisma } from "../../../../utils/databases/prisma/connection"; + +export const selectMediaBySlugRepository = async (slug: string) => { + try { + return await prisma.media.findUnique({ + where: { slug }, + }); + } catch (error) { + throw new AppError(500, "Failed to fetch media by slug", error); + } +}; diff --git a/src/modules/media/services/http/getMediaBySlug.service.ts b/src/modules/media/services/http/getMediaBySlug.service.ts index b886415..1dfcf96 100644 --- a/src/modules/media/services/http/getMediaBySlug.service.ts +++ b/src/modules/media/services/http/getMediaBySlug.service.ts @@ -1,8 +1,13 @@ +import { AppError } from "../../../../helpers/error/instances/app"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; +import { selectMediaBySlugRepository } from "../../repositories/SELECT/selectMediaBySlug.repository"; -export const getMediaBySlugService = (slug: string) => { +export const getMediaBySlugService = async (slug: string) => { try { - return `Mengambil media dengan slug '${slug}'`; + const mediaData = await selectMediaBySlugRepository(slug); + if (!mediaData) throw new AppError(404, "Media not found with the provided slug."); + + return mediaData; } catch (error) { ErrorForwarder(error); }