Compare commits

..

40 Commits

Author SHA1 Message Date
d7270f8696 Merge pull request 'docs' (#12) from docs into main
All checks were successful
Sync to GitHub / sync (push) Successful in 7s
Reviewed-on: #12
2026-02-06 22:30:22 +07:00
bd66705eae 📝 docs: add documentation for get all episodes controller
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 57s
2026-02-06 22:28:14 +07:00
7fb1d4f1f5 📝 docs: add documentation for get episode detail controller 2026-02-06 22:26:11 +07:00
7f129a1b55 📝 docs: add documentation for bulk update thumbnail controller 2026-02-06 22:22:49 +07:00
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
6f754a878b Merge pull request ' feat: add automatic thumbnail generation' (#10) from feat/thumbnail-generation into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #10
2026-02-05 21:04:53 +07:00
e3e4df35e2 🔇 chore: remove debug console.log
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 32s
2026-02-05 21:04:06 +07:00
f3522f6cac 🚚 chore: move get episode with thumbnile repository file
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 29s
2026-02-05 21:02:53 +07:00
745fd213f9 feat: add automatic thumbnail generation
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 57s
2026-02-05 20:59:34 +07:00
dea8c6b7ce Merge pull request ' feat: add endpoint to get all episodes by media' (#9) from feat/endpoint-get-episodes into main
All checks were successful
Sync to GitHub / sync (push) Successful in 7s
Reviewed-on: #9
2026-02-04 23:32:46 +07:00
c1f90c40f2 feat: add endpoint to get all episodes by media
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 57s
2026-02-04 23:31:12 +07:00
d6fa5efaff Merge pull request ' feat: add get all media endpoint' (#8) from feat/get-all-media into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #8
2026-02-03 15:26:14 +07:00
4b9ade64c3 feat: add get all media endpoint
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 56s
2026-02-03 15:25:15 +07:00
9afa0e62f9 Merge pull request 'create controller documentation' (#7) from docs into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #7
2026-02-02 08:32:12 +07:00
9e487297cd 💡 docs: add documentation to createVideoService controller
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 48s
2026-02-02 08:30:34 +07:00
5cb9b475be 💡 docs: add documentation for bulk insert video controller 2026-02-02 08:26:29 +07:00
0e3b0a341f Merge pull request '💚 ci: change to secret token that have full repo access' (#6) from ci/add-automatic-sync into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #6
2026-02-01 22:02:49 +07:00
aa6e3424d3 💚 ci: change to secret token that have full repo access
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 31s
2026-02-01 22:01:47 +07:00
92620f35bc Merge pull request '💚 ci: add checkout to fix unkown repos' (#5) from ci/add-automatic-sync into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #5
2026-02-01 21:56:00 +07:00
bb6bed884a 💚 ci: add checkout to fix unkown repos
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 29s
2026-02-01 21:54:35 +07:00
491d41e44d Merge pull request '💚 ci: fixing auto PR' (#4) from ci/add-automatic-sync into main
All checks were successful
Sync to GitHub / sync (push) Successful in 8s
Reviewed-on: #4
2026-02-01 21:50:57 +07:00
414cdffe1b 💚 ci: fixing auto PR
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 31s
2026-02-01 21:49:07 +07:00
9766a6cde6 Merge pull request '💚 ci: fixing unresolved depth' (#3) from ci/add-automatic-sync into main
All checks were successful
Sync to GitHub / sync (push) Successful in 7s
Reviewed-on: rafiarrafif/AnimeTV-Backend#3
2026-02-01 21:38:49 +07:00
38855a47c2 💚 ci: fixing unresolved depth
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 31s
2026-02-01 21:37:07 +07:00
d13de6ac98 Merge pull request 'ci/add-automatic-sync' (#2) from ci/add-automatic-sync into main
Some checks failed
Sync to GitHub / sync (push) Failing after 7s
Reviewed-on: rafiarrafif/AnimeTV-Backend#2
2026-02-01 21:31:17 +07:00
0be954af01 💚 ci: upload sync code to github
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 59s
2026-02-01 21:22:41 +07:00
589cf62a6e 💚 ci: create automatic sync between git platform 2026-02-01 21:06:30 +07:00
c66731f07d Merge branch 'main' of https://github.com/rafiarrafif/SyzneTV-backend 2026-02-01 12:03:49 +07:00
c94a7abfb2 Merge pull request 'ci/adjust-for-gitea' (#1) from ci/adjust-for-gitea into main
Reviewed-on: rafi/AnimeTV-Backend#1
2026-02-01 12:00:07 +07:00
f9427c577c 💚 ci: add node setup for gitea ci
All checks were successful
Integration Tests / integration-tests (pull_request) Successful in 5m12s
2026-02-01 11:48:15 +07:00
48b0f7b8a3 💚 ci: adjust workflow for gitea
Some checks failed
Integration Tests / integration-tests (pull_request) Failing after 2m26s
2026-02-01 11:12:39 +07:00
8c90df4618 Merge pull request #8 from rafiarrafif/ci
Implement a continue integration test
2026-01-31 18:42:28 +07:00
83792848ed 💚 ci: fix auto script causing CI failure 2026-01-31 18:41:09 +07:00
09c74b28ab 💚 ci: fix auto script causing CI failure 2026-01-31 18:10:59 +07:00
d233ec757c 💚 ci: fix CI error 2026-01-31 15:26:38 +07:00
dedafaa4de 🩹 fix: minor linting issue 2026-01-31 15:24:34 +07:00
e213dba0e5 👷 ci: setup github runner 2026-01-31 15:21:41 +07:00
30 changed files with 731 additions and 4 deletions

42
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,42 @@
name: Integration Tests
on:
pull_request:
branches:
- main
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node (required by Prisma)
uses: actions/setup-node@v4
with:
node-version: "24.13.0"
- name: Setup runtime environment (Bun)
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Index route sync
run: bun run route:sync
- name: Linting test
run: bun run lint
- name: Create dummy system account
run: bun run dummy:systemaccount
- name: Generate prisma schema
run: bunx prisma generate
- name: Build test
run: bun run build

View File

@ -0,0 +1,20 @@
name: Sync to GitHub
on:
push:
branches:
- main
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout from Gitea
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Push to GitHub mirror-main
run: |
git remote add github https://vivy-agent:${{ secrets.GH_TOKEN }}@github.com/rafiarrafif/SyzneTV-backend.git
git push github HEAD:mirror-main --force

View File

@ -0,0 +1,23 @@
name: Auto PR from mirror-main
on:
push:
branches:
- mirror-main
jobs:
pr:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Create PR via GitHub CLI
env:
GH_TOKEN: ${{ secrets.BOT_PAT }}
run: |
gh pr create \
--base main \
--head mirror-main \
--title "Sync from Gitea main" \
--body "Automated PR created from Gitea mirror branch."

View File

@ -0,0 +1,35 @@
name: Intergration Tests
on:
pull_request:
branches:
- main
jobs:
integration-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup runtime environment (Bun)
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install depedencies
run: bun install --frozen-lockfile
- name: Index route sync
run: bun run route:sync
- name: Linting test
run: bun run lint
- name: Create dummy system account
run: bun run dummy:systemaccount
- name: Generate prisma schema
run: bunx prisma generate
- name: Build test
run: bun run build

View File

@ -14,6 +14,7 @@
"prisma:dbml": "bunx prisma db pull && bunx prisma dbml --output ./prisma/dbml/schema.dbml",
"prisma:reset": "bunx prisma db push --force-reset",
"prisma:seed": "bun run ./prisma/seed/index.ts",
"dummy:systemaccount": "bun run ./scripts/create-dummy-system-account.ts",
"route:sync": "bun run ./scripts/sync-routes.ts",
"env:publish": "bun run ./scripts/create-example-env.ts"
},

View File

@ -0,0 +1,16 @@
import { generateUUIDv7 } from "../src/helpers/databases/uuidv7";
import { createFile } from "../src/helpers/files/createFile";
const createDummySystemAccount = async () => {
const file = await createFile(
`export const SystemAccountId = "${generateUUIDv7()}";`,
{
fileName: "system.ts",
targetDir: "src/config/account",
overwriteIfExists: true,
},
);
console.log(`Dummy system account created with id in file: ${file}`);
};
createDummySystemAccount();

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

@ -25,4 +25,6 @@ export const createFile = async (content: string, config: CreateFileConfig) => {
// Write content to the file
await fs.promises.writeFile(targetFile, content, "utf8");
return targetFile;
};

View File

@ -0,0 +1,54 @@
import { Context } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
import { getAllEpisodeFromSpecificMediaService } from "../services/http/getAllEpisodeFromSpecificMedia.service";
/**
* @function getAllEpisodeFromSpecificMediaController
* @description Controller to handle fetching all episodes associated with a specific media slug.
*
* @param {Context & { params: { mediaSlug: string } }} ctx
* The context object containing the request body.
* The params must include:
* - mediaSlug: string - The slug of the media to which the episode belongs.
*
* @example
* Request route: GET /episodes/:mediaSlug
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 200,
* message: "Episodes fetched successfully.",
* data: { ...episodeDetails } // Data returned only if the env run on development mode
* }
*
* @throws {Object}
* An error response object if validation fails or an error occurs during bulk insert operation.
* Return example:
* {
* success: false,
* status: <Status Code>,
* message: "<Error Message>",
* error: { ...errorDetails } // Additional error details if available and the env run on development mode
* }
*/
export const getAllEpisodeFromSpecificMediaController = async (
ctx: Context & { params: { mediaSlug: string } },
) => {
try {
const episodesData = await getAllEpisodeFromSpecificMediaService(
ctx.params.mediaSlug,
);
return returnReadResponse(
ctx.set,
200,
"Episodes fetched successfully",
episodesData,
);
} catch (error) {
return mainErrorHandler(ctx.set, error);
}
};

View File

@ -0,0 +1,58 @@
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;
}
/**
* @function getEpisodeDetailsController
* @description Controller to handle fetching episode details based on provided parameters.
*
* @param {Context & { params: GetEpisodeDetailsParams }} ctx
* The context object containing the request body.
* The params must include:
* - mediaSlug?: string - The slug of the media to which the episode belongs.
* - episode?: string - The identifier of the episode.
*
* @example
* Request route: GET /episodes/:mediaSlug/:episode
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 200,
* message: "Episode details fetched successfully.",
* data: { ...episodeDetails } // Data returned only if the env run on development mode
* }
*
* @throws {Object}
* An error response object if validation fails or an error occurs during bulk insert operation.
* Return example:
* {
* success: false,
* status: <Status Code>,
* message: "<Error Message>",
* error: { ...errorDetails } // Additional error details if available and the env run on development mode
* }
*/
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

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

View File

@ -0,0 +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)
.get("/:episode", getEpisodeDetailsController);

View File

@ -0,0 +1,22 @@
import { AppError } from "../../../../helpers/error/instances/app";
import { mediaModel } from "../../../media/model";
export const getAllEpisodeFromMediaRepository = async (mediaSlug: string) => {
try {
return mediaModel.findUnique({
where: { slug: mediaSlug },
select: {
episodes: {
select: {
id: true,
name: true,
episode: true,
pictureThumbnail: true,
},
},
},
});
} catch (error) {
throw new AppError(500, "Failed to fetch episodes from media", error);
}
};

View File

@ -0,0 +1,34 @@
import { AppError } from "../../../../helpers/error/instances/app";
import { episodeModel } from "../../episode.model";
export const getAllEpisodeWithThumbnailLinkRepository = async (
serviceReferenceId: string,
) => {
try {
return await episodeModel.findMany({
where: {
deletedAt: null,
},
select: {
id: true,
episode: true,
videos: {
where: {
deletedAt: null,
serviceId: serviceReferenceId,
},
select: {
code: true,
service: {
select: {
endpointThumbnail: true,
},
},
},
},
},
});
} catch (error) {
throw new AppError(500, "Failed to get all episode thumbnails", error);
}
};

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,18 @@
import { Prisma } from "@prisma/client";
import { AppError } from "../../../../helpers/error/instances/app";
import { episodeModel } from "../../episode.model";
export const updateEpisodeRepository = async (
payload: Prisma.EpisodeUncheckedUpdateInput,
) => {
try {
return await episodeModel.update({
where: {
id: payload.id as string,
},
data: payload,
});
} catch (error) {
throw new AppError(500, "Failed to edit episode", error);
}
};

View File

@ -0,0 +1,16 @@
import { AppError } from "../../../../helpers/error/instances/app";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { getAllEpisodeFromMediaRepository } from "../../repositories/GET/getAllEpisodeFromMedia.repository";
export const getAllEpisodeFromSpecificMediaService = async (
mediaSlug: string,
) => {
try {
const mediaData = await getAllEpisodeFromMediaRepository(mediaSlug);
if (!mediaData)
throw new AppError(404, `Media with slug ${mediaSlug} not found`);
return mediaData.episodes;
} catch (error) {
ErrorForwarder(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

@ -14,6 +14,67 @@ export interface BulkInsertVideoBodyRequest {
}>;
}
/**
* @function bulkInsertVideoController
* @description Perform bulk insert of videos for specific episodes of a media. This operation inserts multiple videos associated with different episodes into the database based on the provided data.
*
* @param {Context & { body: BulkInsertVideoBodyRequest }} ctx
* The context object containing the request body.
* The body must include:
* - media_id: string - The ID of the media for which episodes will be inserted.
* - data: Array - An array of episode data, each containing:
* - episode: number - The episode number.
* - videos: Array - An array of video data for the episode, each containing:
* - service_id: string - The ID of the video service.
* - code: string - The code of the video on the service.
*
* @example
* Request route: POST /internal/video/bulk-insert
* Request body:
* {
* "media_id": "019c064e-a03d-7cc3-b2ae-5d6850ea456b",
* "data": [
* {
* "episode": 1,
* "videos": [
* {
* "service_id": "019c0df6-f8fe-7565-82cd-9c29b20232ab",
* "code": "fzwu9n8ge2qt"
* }
* ]
* },
* {
* "episode": 2,
* "videos": [
* {
* "service_id": "019c0df6-f8fe-7565-82cd-9c29b20232ab",
* "code": "w2maywh53rt8"
* }
* ]
* }
* ]
* },
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 201,
* message: "Videos inserted",
* data: { ...insertedVideos } // Data returned only if the env run on development mode
* }
*
* @throws {Object}
* An error response object if validation fails or an error occurs during bulk insert operation.
* Return example:
* {
* success: false,
* status: <Status Code>,
* message: "<Error Message>",
* error: { ...errorDetails } // Additional error details if available and the env run on development mode
* }
*/
export const bulkInsertVideoController = async (
ctx: Context & { body: BulkInsertVideoBodyRequest },
) => {

View File

@ -3,7 +3,7 @@ import { mainErrorHandler } from "../../../helpers/error/handler";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { createVideoServiceInternalService } from "../services/http/createVideoService.service";
export interface CreateVideoServiceBodyRequest {
export interface CreateVideoServiceInternalBodyRequest {
name: string;
domain: string;
logo: string;
@ -13,8 +13,56 @@ export interface CreateVideoServiceBodyRequest {
endpointDownload?: string;
}
/**
* @function createVideoServiceInternalController
* @description Perform creation of a new video service. This operation adds a new video service to the database based on the provided data.
*
* @param {Context & { body: CreateVideoServiceInternalBodyRequest }} ctx
* The context object containing the request body.
* The body must include:
* - name: string - The name of the video service.
* - domain: string - The domain of the video service.
* - logo: string - The logo URL of the video service.
* - hexColor: string - The hex color associated with the video service.
* - endpointVideo: string - The endpoint URL for video streaming.
* - endpointThumbnail: string - The endpoint URL for thumbnails.
* - endpointDownload?: string - (Optional) The endpoint URL for downloads.
*
* @example
* Request route: POST /internal/video-service
* Request body:
* {
* "name": "Example Video Service",
* "domain": "example.com",
* "logo": "https://example.com/logo.png",
* "hexColor": "#FF5733",
* "endpointVideo": "https://api.example.com/videos",
* "endpointThumbnail": "https://api.example.com/thumbnails",
* "endpointDownload": "https://api.example.com/downloads"
* },
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 201,
* message: "Video service created",
* data: { ...createdVideoService } // Data returned only if the env run on development mode
* }
*
* @throws {Object}
* An error response object if validation fails or an error occurs during bulk insert operation.
* Return example:
* {
* success: false,
* status: <Status Code>,
* message: "<Error Message>",
* error: { ...errorDetails } // Additional error details if available and the env run on development mode
* }
*/
export const createVideoServiceInternalController = async (
ctx: Context & { body: CreateVideoServiceBodyRequest },
ctx: Context & { body: CreateVideoServiceInternalBodyRequest },
) => {
try {
const createdVideoService = await createVideoServiceInternalService(

View File

@ -0,0 +1,56 @@
import { Context } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { updateAllEpisodeThumbnailService } from "../services/http/updateAllEpisodeThumbnail.service";
/**
* @function updateAllEpisodeThumbnailController
* @description Controller to handle the bulk updating of episode thumbnails for all episodes associated with a specific service reference ID.
*
* @param {Context & { body: { service_reference_id: string } }} ctx
* The context object containing the request body.
* The body must include:
* - service_reference_id: string - The ID of the service to which the episodes belong.
*
* @example
* Request route: PUT /internal/episode/update-thumbnails
* Request body:
* {
* "service_reference_id": "019c0df6-f8fe-7565-82cd-9c29b20232ab"
* },
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 204,
* message: "Updating {newEpisodeThumbnailsCount} episode thumbnails successfully.",
* }
*
* @throws {Object}
* An error response object if validation fails or an error occurs during bulk insert operation.
* Return example:
* {
* success: false,
* status: <Status Code>,
* message: "<Error Message>",
* error: { ...errorDetails } // Additional error details if available and the env run on development mode
* }
*/
export const updateAllEpisodeThumbnailController = async (
ctx: Context & { body: { service_reference_id: string } },
) => {
try {
const newEpisodeThumbnailsCount = await updateAllEpisodeThumbnailService(
ctx.body.service_reference_id,
);
return returnWriteResponse(
ctx.set,
204,
`Updating ${newEpisodeThumbnailsCount} episode thumbnails successfully.`,
);
} catch (error) {
return mainErrorHandler(ctx.set, error);
}
};

View File

@ -3,9 +3,11 @@ import { bulkInsertEpisodeController } from "./controllers/bulkInsertEpisode.con
import { bulkInsertMediaController } from "./controllers/bulkInsertMedia.controller";
import { createVideoServiceInternalController } from "./controllers/createVideoService.controller";
import { bulkInsertVideoController } from "./controllers/bulkInsertVideo.controller";
import { updateAllEpisodeThumbnailController } from "./controllers/updateAllEpisodeThumbnail.controller";
export const internalModule = new Elysia({ prefix: "/internal" })
.post("/media/bulk-insert", bulkInsertMediaController)
.post("/episode/bulk-insert", bulkInsertEpisodeController)
.put("/episode/update-thumbnails", updateAllEpisodeThumbnailController)
.post("/video/bulk-insert", bulkInsertVideoController)
.post("/video-service", createVideoServiceInternalController);

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

@ -1,10 +1,10 @@
import { SystemAccountId } from "../../../../config/account/system";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { CreateVideoServiceBodyRequest } from "../../controllers/createVideoService.controller";
import { CreateVideoServiceInternalBodyRequest } from "../../controllers/createVideoService.controller";
import { createVideoServiceInternalRepository } from "../../repositories/createVideoService.repository";
export const createVideoServiceInternalService = async (
body: CreateVideoServiceBodyRequest,
body: CreateVideoServiceInternalBodyRequest,
) => {
try {
return await createVideoServiceInternalRepository({

View File

@ -0,0 +1,36 @@
import { AppError } from "../../../../helpers/error/instances/app";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { updateEpisodeRepository } from "../../../episode/repositories/PUT/updateEpisode.repository";
import { getAllEpisodeWithThumbnailLinkRepository } from "../../../episode/repositories/GET/getAllEpisodeWithThumbnailLink.repository";
export const updateAllEpisodeThumbnailService = async (
serviceReferenceId: string,
) => {
try {
if (!serviceReferenceId)
throw new AppError(400, "Service Reference ID is required.");
const episodesData = await getAllEpisodeWithThumbnailLinkRepository(
serviceReferenceId,
);
let updatedThumbnailsCount = 0;
for (const episode of episodesData) {
if (episode.videos.length === 0) continue;
await updateEpisodeRepository({
id: episode.id,
pictureThumbnail:
episode.videos[0].service.endpointThumbnail?.replace(
":code:",
episode.videos[0].code,
) || null,
});
updatedThumbnailsCount++;
}
return updatedThumbnailsCount;
} catch (error) {
ErrorForwarder(error);
}
};

View File

@ -0,0 +1,20 @@
import { Context } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { getAllMediaService } from "../services/http/getAllMedia.service";
import { returnReadResponse } from "../../../helpers/callback/httpResponse";
export const getAllMediaController = async (
ctx: Context & { query: { page: string } },
) => {
try {
const mediaData = await getAllMediaService(ctx.query.page);
return returnReadResponse(
ctx.set,
200,
"Media fetched successfully",
mediaData,
);
} catch (error) {
return mainErrorHandler(ctx.set, error);
}
};

View File

@ -0,0 +1,7 @@
import Elysia from "elysia";
import { getAllMediaController } from "./controllers/getAllMedia.controller";
export const mediaModule = new Elysia({ prefix: "/media" }).get(
"/",
getAllMediaController,
);

View File

@ -0,0 +1,17 @@
import { AppError } from "../../../../helpers/error/instances/app";
import { mediaModel } from "../../model";
export const getAllMediaRepository = async (page: number) => {
try {
const limit = 10;
return await mediaModel.findMany({
take: limit,
skip: (page - 1) * limit,
where: {
deletedAt: null,
},
});
} catch (error) {
throw new AppError(500, "Failed to get all media from repository", error);
}
};

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

View File

@ -0,0 +1,15 @@
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { getAllMediaRepository } from "../../repositories/GET/getAllMedia.repository";
export const getAllMediaService = async (pagination: string) => {
try {
const page =
/^\d+$/.test(pagination) && Number(pagination) > 0
? Number(pagination)
: 1;
return getAllMediaRepository(page);
} catch (error) {
ErrorForwarder(error);
}
};