From ab0c8afca4703053b053cc42ca5f2115867cde97 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Fri, 30 Jan 2026 15:18:00 +0700 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20feat:=20endpoint=20for=20create?= =?UTF-8?q?=20video=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/dbml/schema.dbml | 1 + prisma/schema.prisma | 1 + .../createVideoService.controller.ts | 32 +++++++++++++++++++ src/modules/internal/index.ts | 4 ++- .../createVideoService.repository.ts | 23 +++++++++++++ .../http/createVideoService.service.ts | 23 +++++++++++++ 6 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/modules/internal/controllers/createVideoService.controller.ts create mode 100644 src/modules/internal/repositories/createVideoService.repository.ts create mode 100644 src/modules/internal/services/http/createVideoService.service.ts diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index 0619e0f..b8dd824 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -191,6 +191,7 @@ Table video_services { hexColor String [not null] endpointVideo String [not null] endpointThumbnail String + endpointDownload String creator users [not null] createdBy String [not null] deletedAt DateTime diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 939f482..9d5307b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -229,6 +229,7 @@ model VideoService { hexColor String @db.VarChar(10) endpointVideo String @db.Text endpointThumbnail String? @db.Text + endpointDownload String? creator User @relation("UserVideoServices", fields: [createdBy], references: [id]) createdBy String @db.Uuid deletedAt DateTime? diff --git a/src/modules/internal/controllers/createVideoService.controller.ts b/src/modules/internal/controllers/createVideoService.controller.ts new file mode 100644 index 0000000..90f8d68 --- /dev/null +++ b/src/modules/internal/controllers/createVideoService.controller.ts @@ -0,0 +1,32 @@ +import { Context } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; +import { createVideoServiceInternalService } from "../services/http/createVideoService.service"; + +export interface CreateVideoServiceBodyRequest { + name: string; + domain: string; + logo: string; + hexColor: string; + endpointVideo: string; + endpointThumbnail: string; + endpointDownload?: string; +} + +export const createVideoServiceInternalController = async ( + ctx: Context & { body: CreateVideoServiceBodyRequest }, +) => { + try { + const createdVideoService = await createVideoServiceInternalService( + ctx.body, + ); + return returnWriteResponse( + ctx.set, + 201, + "Video service created", + createdVideoService, + ); + } catch (error) { + throw mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/internal/index.ts b/src/modules/internal/index.ts index b7321b5..41c539f 100644 --- a/src/modules/internal/index.ts +++ b/src/modules/internal/index.ts @@ -1,7 +1,9 @@ import Elysia from "elysia"; import { bulkInsertEpisodeController } from "./controllers/bulkInsertEpisode.controller"; import { bulkInsertMediaController } from "./controllers/bulkInsertMedia.controller"; +import { createVideoServiceInternalController } from "./controllers/createVideoService.controller"; export const internalModule = new Elysia({ prefix: "/internal" }) .post("/media/bulk-insert", bulkInsertMediaController) - .post("/episode/bulk-insert", bulkInsertEpisodeController); + .post("/episode/bulk-insert", bulkInsertEpisodeController) + .post("/video-service", createVideoServiceInternalController); diff --git a/src/modules/internal/repositories/createVideoService.repository.ts b/src/modules/internal/repositories/createVideoService.repository.ts new file mode 100644 index 0000000..b73827e --- /dev/null +++ b/src/modules/internal/repositories/createVideoService.repository.ts @@ -0,0 +1,23 @@ +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 createVideoServiceInternalRepository = async ( + payload: Omit, +) => { + try { + return await prisma.videoService.upsert({ + where: { + name: payload.name, + }, + create: { + id: generateUUIDv7(), + ...payload, + }, + update: payload, + }); + } catch (error) { + throw new AppError(500, "Failed to create video service", error); + } +}; diff --git a/src/modules/internal/services/http/createVideoService.service.ts b/src/modules/internal/services/http/createVideoService.service.ts new file mode 100644 index 0000000..47791bd --- /dev/null +++ b/src/modules/internal/services/http/createVideoService.service.ts @@ -0,0 +1,23 @@ +import { SystemAccountId } from "../../../../config/account/system"; +import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; +import { CreateVideoServiceBodyRequest } from "../../controllers/createVideoService.controller"; +import { createVideoServiceInternalRepository } from "../../repositories/createVideoService.repository"; + +export const createVideoServiceInternalService = async ( + body: CreateVideoServiceBodyRequest, +) => { + try { + return await createVideoServiceInternalRepository({ + name: body.name, + domain: body.domain, + logo: body.logo, + hexColor: body.hexColor, + endpointVideo: body.endpointVideo, + endpointThumbnail: body.endpointThumbnail, + endpointDownload: body.endpointDownload, + createdBy: SystemAccountId, + }); + } catch (error) { + ErrorForwarder(error); + } +}; From 11a607b4da630007b1c3b9732dec4c418ab5c005 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Fri, 30 Jan 2026 15:56:43 +0700 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20feat:=20endpoint=20for=20bulk?= =?UTF-8?q?=20insert=20video?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/dbml/schema.dbml | 4 +++ prisma/schema.prisma | 2 ++ .../controllers/bulkInsertVideo.controller.ts | 26 ++++++++++++++ src/modules/internal/index.ts | 2 ++ .../bulkInsertVideo.repository.ts | 26 ++++++++++++++ .../findEpisodeWithMediaId.repository.ts | 28 +++++++++++++++ .../services/http/bulkInsertVideo.service.ts | 34 +++++++++++++++++++ 7 files changed, 122 insertions(+) create mode 100644 src/modules/internal/controllers/bulkInsertVideo.controller.ts create mode 100644 src/modules/internal/repositories/bulkInsertVideo.repository.ts create mode 100644 src/modules/internal/repositories/findEpisodeWithMediaId.repository.ts create mode 100644 src/modules/internal/services/http/bulkInsertVideo.service.ts diff --git a/prisma/dbml/schema.dbml b/prisma/dbml/schema.dbml index b8dd824..5853552 100644 --- a/prisma/dbml/schema.dbml +++ b/prisma/dbml/schema.dbml @@ -181,6 +181,10 @@ Table videos { deletedAt DateTime createdAt DateTime [default: `now()`, not null] updatedAt DateTime [default: `now()`, not null] + + indexes { + (serviceId, code) [unique] + } } Table video_services { diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9d5307b..0c255ec 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -218,6 +218,8 @@ model Video { deletedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + + @@unique([serviceId, code]) @@map("videos") } diff --git a/src/modules/internal/controllers/bulkInsertVideo.controller.ts b/src/modules/internal/controllers/bulkInsertVideo.controller.ts new file mode 100644 index 0000000..d68450a --- /dev/null +++ b/src/modules/internal/controllers/bulkInsertVideo.controller.ts @@ -0,0 +1,26 @@ +import { Context } from "elysia"; +import { mainErrorHandler } from "../../../helpers/error/handler"; +import { bulkInsertVideoService } from "../services/http/bulkInsertVideo.service"; +import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; + +export interface BulkInsertVideoBodyRequest { + media_id: string; + data: Array<{ + episode: number; + videos: Array<{ + service_id: string; + code: string; + }>; + }>; +} + +export const bulkInsertVideoController = async ( + ctx: Context & { body: BulkInsertVideoBodyRequest }, +) => { + try { + const insertedVideos = await bulkInsertVideoService(ctx.body); + return returnWriteResponse(ctx.set, 201, "Videos inserted", insertedVideos); + } catch (error) { + throw mainErrorHandler(ctx.set, error); + } +}; diff --git a/src/modules/internal/index.ts b/src/modules/internal/index.ts index 41c539f..471c1c9 100644 --- a/src/modules/internal/index.ts +++ b/src/modules/internal/index.ts @@ -2,8 +2,10 @@ import Elysia from "elysia"; import { bulkInsertEpisodeController } from "./controllers/bulkInsertEpisode.controller"; import { bulkInsertMediaController } from "./controllers/bulkInsertMedia.controller"; import { createVideoServiceInternalController } from "./controllers/createVideoService.controller"; +import { bulkInsertVideoController } from "./controllers/bulkInsertVideo.controller"; export const internalModule = new Elysia({ prefix: "/internal" }) .post("/media/bulk-insert", bulkInsertMediaController) .post("/episode/bulk-insert", bulkInsertEpisodeController) + .post("/video/bulk-insert", bulkInsertVideoController) .post("/video-service", createVideoServiceInternalController); diff --git a/src/modules/internal/repositories/bulkInsertVideo.repository.ts b/src/modules/internal/repositories/bulkInsertVideo.repository.ts new file mode 100644 index 0000000..21eb3cf --- /dev/null +++ b/src/modules/internal/repositories/bulkInsertVideo.repository.ts @@ -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 bulkInsertVideoRepository = async ( + payload: Omit, +) => { + try { + return await prisma.video.upsert({ + where: { + serviceId_code: { + serviceId: payload.serviceId, + code: payload.code, + }, + }, + create: { + id: generateUUIDv7(), + ...payload, + }, + update: payload, + }); + } catch (error) { + throw new AppError(500, "Error inserting video", error); + } +}; diff --git a/src/modules/internal/repositories/findEpisodeWithMediaId.repository.ts b/src/modules/internal/repositories/findEpisodeWithMediaId.repository.ts new file mode 100644 index 0000000..0817e06 --- /dev/null +++ b/src/modules/internal/repositories/findEpisodeWithMediaId.repository.ts @@ -0,0 +1,28 @@ +import { AppError } from "../../../helpers/error/instances/app"; +import { prisma } from "../../../utils/databases/prisma/connection"; + +export const findEpisodeWithMediaIdRepository = async ({ + media, + episode, +}: { + media: string; + episode: number; +}) => { + try { + const foundEpisode = await prisma.episode.findUnique({ + where: { + mediaId_episode: { + mediaId: media, + episode: episode, + }, + }, + select: { + id: true, + }, + }); + if (!foundEpisode) throw new AppError(404, "Episode not found"); + return foundEpisode; + } catch (error) { + throw new AppError(500, "Error finding episode with media id", error); + } +}; diff --git a/src/modules/internal/services/http/bulkInsertVideo.service.ts b/src/modules/internal/services/http/bulkInsertVideo.service.ts new file mode 100644 index 0000000..d10fa5b --- /dev/null +++ b/src/modules/internal/services/http/bulkInsertVideo.service.ts @@ -0,0 +1,34 @@ +import { SystemAccountId } from "../../../../config/account/system"; +import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; +import { BulkInsertVideoBodyRequest } from "../../controllers/bulkInsertVideo.controller"; +import { findEpisodeWithMediaIdRepository } from "../../repositories/findEpisodeWithMediaId.repository"; +import { bulkInsertVideoRepository } from "../../repositories/bulkInsertVideo.repository"; + +export const bulkInsertVideoService = async ( + body: BulkInsertVideoBodyRequest, +) => { + try { + const insertedVideos: string[] = []; + for (const episodeData of body.data) { + const episodeId = await findEpisodeWithMediaIdRepository({ + media: body.media_id, + episode: episodeData.episode, + }); + + for (const videoData of episodeData.videos) { + const insertedVideo = await bulkInsertVideoRepository({ + episodeId: episodeId.id, + serviceId: videoData.service_id, + code: videoData.code, + uploadedBy: SystemAccountId, + }); + + insertedVideos.push(insertedVideo.id); + } + } + + return insertedVideos; + } catch (error) { + ErrorForwarder(error); + } +};