Compare commits

..

50 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
ae508ded6d Merge pull request #7 from rafiarrafif/fix/linting-issues
Fix/linting issues
2026-01-31 09:04:57 +07:00
fd8f980d9a 🚨 fix: resolve linting type error 2026-01-31 09:03:32 +07:00
68fec64efc 🚨 fix: resolve linting type error 2026-01-31 08:13:04 +07:00
5a43769f69 🚨 fix: resolve linting type error 2026-01-30 21:21:43 +07:00
6fff049c18 Merge pull request #6 from rafiarrafif/feat/bulk-insert-video
Feat/bulk insert video
2026-01-30 15:58:51 +07:00
11a607b4da feat: endpoint for bulk insert video 2026-01-30 15:56:43 +07:00
ab0c8afca4 feat: endpoint for create video service 2026-01-30 15:18:00 +07:00
0521c27834 Merge pull request #5 from rafiarrafif/documentation
Documentation
2026-01-30 05:06:13 +07:00
ce56e13f30 📝 docs: complete pagination documentation 2026-01-30 05:02:41 +07:00
4e8eda081c 📝 docs: add documentation for bulk insert episode 2026-01-30 05:02:03 +07:00
56 changed files with 1007 additions and 51 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."

35
.github/workflows/ci.yaml vendored Normal file
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

@ -5,7 +5,7 @@ import { defineConfig } from "eslint/config";
export default defineConfig([ export default defineConfig([
{ {
ignores: ["src/modules/debug/**"], ignores: ["src/modules/debug/**", "src/helpers/characters/generateSlug.ts"],
}, },
{ {
files: ["src/**/*.{js,mjs,cjs,ts,mts,cts}"], files: ["src/**/*.{js,mjs,cjs,ts,mts,cts}"],

View File

@ -14,6 +14,7 @@
"prisma:dbml": "bunx prisma db pull && bunx prisma dbml --output ./prisma/dbml/schema.dbml", "prisma:dbml": "bunx prisma db pull && bunx prisma dbml --output ./prisma/dbml/schema.dbml",
"prisma:reset": "bunx prisma db push --force-reset", "prisma:reset": "bunx prisma db push --force-reset",
"prisma:seed": "bun run ./prisma/seed/index.ts", "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", "route:sync": "bun run ./scripts/sync-routes.ts",
"env:publish": "bun run ./scripts/create-example-env.ts" "env:publish": "bun run ./scripts/create-example-env.ts"
}, },

View File

@ -181,6 +181,10 @@ Table videos {
deletedAt DateTime deletedAt DateTime
createdAt DateTime [default: `now()`, not null] createdAt DateTime [default: `now()`, not null]
updatedAt DateTime [default: `now()`, not null] updatedAt DateTime [default: `now()`, not null]
indexes {
(serviceId, code) [unique]
}
} }
Table video_services { Table video_services {
@ -191,6 +195,7 @@ Table video_services {
hexColor String [not null] hexColor String [not null]
endpointVideo String [not null] endpointVideo String [not null]
endpointThumbnail String endpointThumbnail String
endpointDownload String
creator users [not null] creator users [not null]
createdBy String [not null] createdBy String [not null]
deletedAt DateTime deletedAt DateTime

View File

@ -218,6 +218,8 @@ model Video {
deletedAt DateTime? deletedAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt
@@unique([serviceId, code])
@@map("videos") @@map("videos")
} }
@ -229,6 +231,7 @@ model VideoService {
hexColor String @db.VarChar(10) hexColor String @db.VarChar(10)
endpointVideo String @db.Text endpointVideo String @db.Text
endpointThumbnail String? @db.Text endpointThumbnail String? @db.Text
endpointDownload String?
creator User @relation("UserVideoServices", fields: [createdBy], references: [id]) creator User @relation("UserVideoServices", fields: [createdBy], references: [id])
createdBy String @db.Uuid createdBy String @db.Uuid
deletedAt DateTime? deletedAt DateTime?

View File

@ -7,7 +7,7 @@ async function main() {
console.log("🔌 Connecting to database..."); console.log("🔌 Connecting to database...");
const userSystemSeedResult = await userSystemSeed(); const userSystemSeedResult = await userSystemSeed();
const userRoleSeedResult = await userRoleSeed(userSystemSeedResult.id); await userRoleSeed(userSystemSeedResult.id);
console.log("🌳 All seeds completed"); console.log("🌳 All seeds completed");
} }

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

@ -50,7 +50,7 @@ try {
// Extract the key and the remainder after "=" // Extract the key and the remainder after "="
const key = line.substring(0, delimiterIndex).trim(); const key = line.substring(0, delimiterIndex).trim();
let remainder = line.substring(delimiterIndex + 1); const remainder = line.substring(delimiterIndex + 1);
// Attempt to separate value and inline comment (if any) // Attempt to separate value and inline comment (if any)
let value = remainder; let value = remainder;

View File

@ -14,7 +14,7 @@ for (const remote of remotes) {
try { try {
execSync(`git push ${remote} main`, { stdio: "inherit" }); execSync(`git push ${remote} main`, { stdio: "inherit" });
} catch (err) { } catch (err) {
console.error(`❌ Failed to push to ${remote}`); console.error(`❌ Failed to push to ${remote}`, err);
} }
} }

View File

@ -14,17 +14,13 @@ export async function generateSlug(
const baseSlug = slugify(input, { lower: true, strict: true }); const baseSlug = slugify(input, { lower: true, strict: true });
let uniqueSlug = baseSlug; let uniqueSlug = baseSlug;
// CASE 1: Tidak ada config → langsung return slug
if (!config) return uniqueSlug; if (!config) return uniqueSlug;
const { model, target } = config; const { model, target } = config;
// CASE 2: Validasi pasangan model-target
if (!model || !target) { if (!model || !target) {
throw new Error(`Both "model" and "target" must be provided together.`); throw new Error(`Both "model" and "target" must be provided together.`);
} }
// CASE 3: Cek unique
const prismaModel = (prisma as any)[model]; const prismaModel = (prisma as any)[model];
if (!prismaModel) { if (!prismaModel) {
throw new Error(`Model "${model as string}" not found in PrismaClient.`); throw new Error(`Model "${model as string}" not found in PrismaClient.`);

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

@ -1,8 +1,10 @@
import { generateUUIDv7 } from "./uuidv7"; import { generateUUIDv7 } from "./uuidv7";
function createManyWithUUID<T extends { id?: string }>(items: T[]): T[] { export const createManyWithUUID = <T extends { id?: string }>(
items: T[],
): T[] => {
return items.map((i) => ({ return items.map((i) => ({
...i, ...i,
id: i.id ?? generateUUIDv7(), id: i.id ?? generateUUIDv7(),
})); }));
} };

View File

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

View File

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-require-imports */
import { middleware } from "./middleware"; import { middleware } from "./middleware";
import { validateEnv } from "./utils/startups/validateEnv"; import { validateEnv } from "./utils/startups/validateEnv";
@ -12,7 +14,7 @@ async function bootstrap() {
sentryInit(); sentryInit();
console.log("\x1b[1m\x1b[33m🚀 Starting backend services...\x1b[0m"); console.log("\x1b[1m\x1b[33m🚀 Starting backend services...\x1b[0m");
const app = new Elysia() new Elysia()
.use(middleware) .use(middleware)
.use(routes) .use(routes)
.listen(process.env.APP_PORT || 3000); .listen(process.env.APP_PORT || 3000);

View File

@ -1,4 +1,4 @@
import Elysia, { Context } from "elysia"; import Elysia from "elysia";
import { returnErrorResponse } from "../../helpers/callback/httpResponse"; import { returnErrorResponse } from "../../helpers/callback/httpResponse";
export const appAccessTokenMiddleware = () => export const appAccessTokenMiddleware = () =>

View File

@ -1,5 +1,12 @@
import { Context } from "elysia"; import { Context } from "elysia";
export const isAdminMiddleware = (ctx: Context) => { export const isAdminMiddleware = (ctx: Context) => {
//validate here const isAdmin = ctx.headers["isAdmin"];
if (!isAdmin) {
ctx.set.status = 403;
return {
error: "Forbidden",
message: "You don't have access to this resource",
};
}
}; };

View File

@ -1,6 +1,5 @@
import { AppError } from "../../../../helpers/error/instances/app"; import { AppError } from "../../../../helpers/error/instances/app";
import { jwtDecode } from "../../../../helpers/http/jwt/decode"; import { jwtDecode } from "../../../../helpers/http/jwt/decode";
import { jwtEncode } from "../../../../helpers/http/jwt/encode";
export const tokenValidationService = (payload: string) => { export const tokenValidationService = (payload: string) => {
try { try {

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

@ -3,7 +3,46 @@ import { mainErrorHandler } from "../../../helpers/error/handler";
import { bulkInsertEpisodeService } from "../services/http/bulkInsertEpisode.service"; import { bulkInsertEpisodeService } from "../services/http/bulkInsertEpisode.service";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
// add pagination query /**
* @function bulkInsertMediaController
* @description Perform bulk insert of episodes for a specific media. This operation fetches episode data from external sources and inserts them into the database. The page parameter is optional; if not provided, the first page of episodes will be fetched.
*
* @param {Context & { body: { media_mal_id: number }; query: { page?: number } }} ctx
* The context object containing the request body.
* The body must include:
* - media_mal_id: number - The MyAnimeList ID of the media for which episodes will be inserted.
* The query may include:
* - page?: number - (Optional) The page number of episodes to fetch and insert. If not provided, defaults to the first page.
*
* @example
* Request route: POST /internal/episode/bulk-insert
* Request body:
* {
* "media_mal_id": 12345
* }
* Query parameter:
* ?page=2 (Optional, specifies the page number of episodes to fetch and insert)
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 201,
* message: "Bulk insert episode operation completed successfully",
* data: { ...bulkInsertResult } // 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 bulkInsertEpisodeController = async ( export const bulkInsertEpisodeController = async (
ctx: Context & { body: { media_mal_id: number }; query: { page?: number } }, ctx: Context & { body: { media_mal_id: number }; query: { page?: number } },
) => { ) => {
@ -15,7 +54,7 @@ export const bulkInsertEpisodeController = async (
return returnWriteResponse( return returnWriteResponse(
ctx.set, ctx.set,
201, 201,
"Success bulk insert for episode", "Bulk insert episode operation completed successfully",
bulkInsertResult, bulkInsertResult,
); );
} catch (err) { } catch (err) {

View File

@ -4,7 +4,7 @@ import { bulkInsertAnimeService } from "../services/http/bulkInsertAnime.service
import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
/** /**
* @function bulkInsertAnimeController * @function bulkInsertMediaController
* @description Insert new anime to the database only with mal_id. This operation including inserting related data such as genres, studios, producers, licensors, themes, demographics, and relations. * @description Insert new anime to the database only with mal_id. This operation including inserting related data such as genres, studios, producers, licensors, themes, demographics, and relations.
* *
* @param {Context & { body: { mal_id: number } }} ctx * @param {Context & { body: { mal_id: number } }} ctx

View File

@ -0,0 +1,87 @@
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;
}>;
}>;
}
/**
* @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 },
) => {
try {
const insertedVideos = await bulkInsertVideoService(ctx.body);
return returnWriteResponse(ctx.set, 201, "Videos inserted", insertedVideos);
} catch (error) {
throw mainErrorHandler(ctx.set, error);
}
};

View File

@ -0,0 +1,80 @@
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 CreateVideoServiceInternalBodyRequest {
name: string;
domain: string;
logo: string;
hexColor: string;
endpointVideo: string;
endpointThumbnail: string;
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: CreateVideoServiceInternalBodyRequest },
) => {
try {
const createdVideoService = await createVideoServiceInternalService(
ctx.body,
);
return returnWriteResponse(
ctx.set,
201,
"Video service created",
createdVideoService,
);
} catch (error) {
throw mainErrorHandler(ctx.set, error);
}
};

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

@ -1,7 +1,13 @@
import Elysia from "elysia"; import Elysia from "elysia";
import { bulkInsertEpisodeController } from "./controllers/bulkInsertEpisode.controller"; import { bulkInsertEpisodeController } from "./controllers/bulkInsertEpisode.controller";
import { bulkInsertMediaController } from "./controllers/bulkInsertMedia.controller"; 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" }) export const internalModule = new Elysia({ prefix: "/internal" })
.post("/media/bulk-insert", bulkInsertMediaController) .post("/media/bulk-insert", bulkInsertMediaController)
.post("/episode/bulk-insert", bulkInsertEpisodeController); .post("/episode/bulk-insert", bulkInsertEpisodeController)
.put("/episode/update-thumbnails", updateAllEpisodeThumbnailController)
.post("/video/bulk-insert", bulkInsertVideoController)
.post("/video-service", createVideoServiceInternalController);

View File

@ -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<Prisma.VideoUncheckedCreateInput, "id">,
) => {
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);
}
};

View File

@ -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<Prisma.VideoServiceUncheckedCreateInput, "id">,
) => {
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);
}
};

View File

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

View File

@ -1,4 +1,3 @@
import { Prisma } from "@prisma/client";
import { getEpisodeReferenceAPI } from "../../../../config/apis/episode.reference"; import { getEpisodeReferenceAPI } from "../../../../config/apis/episode.reference";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { MediaEpisodeInfoResponse } from "../../types/mediaEpisodeInfo.type"; import { MediaEpisodeInfoResponse } from "../../types/mediaEpisodeInfo.type";

View File

@ -0,0 +1,35 @@
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({
pendingUpload: false,
episodeId: episodeId.id,
serviceId: videoData.service_id,
code: videoData.code,
uploadedBy: SystemAccountId,
});
insertedVideos.push(insertedVideo.id);
}
}
return insertedVideos;
} catch (error) {
ErrorForwarder(error);
}
};

View File

@ -0,0 +1,23 @@
import { SystemAccountId } from "../../../../config/account/system";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { CreateVideoServiceInternalBodyRequest } from "../../controllers/createVideoService.controller";
import { createVideoServiceInternalRepository } from "../../repositories/createVideoService.repository";
export const createVideoServiceInternalService = async (
body: CreateVideoServiceInternalBodyRequest,
) => {
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);
}
};

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

@ -1,6 +1,5 @@
import { SystemAccountId } from "../../../../config/account/system"; import { SystemAccountId } from "../../../../config/account/system";
import { getContentReferenceAPI } from "../../../../config/apis/media.reference"; import { getContentReferenceAPI } from "../../../../config/apis/media.reference";
import { generateUUIDv7 } from "../../../../helpers/databases/uuidv7";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { bulkInsertCharactersRepository } from "../../repositories/bulkInsertCharacters.repository"; import { bulkInsertCharactersRepository } from "../../repositories/bulkInsertCharacters.repository";
import { bulkInsertLangVARepository } from "../../repositories/bulkInsertLangVA.repository"; import { bulkInsertLangVARepository } from "../../repositories/bulkInsertLangVA.repository";

View File

@ -1,5 +1,4 @@
import { SystemAccountId } from "../../../../config/account/system"; import { SystemAccountId } from "../../../../config/account/system";
import { generateUUIDv7 } from "../../../../helpers/databases/uuidv7";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { bulkInsertVoiceActorRepository } from "../../repositories/bulkInsertVoiceActor.repository"; import { bulkInsertVoiceActorRepository } from "../../repositories/bulkInsertVoiceActor.repository";
import { Person } from "../../types/mediaCharWithVAInfo"; import { Person } from "../../types/mediaCharWithVAInfo";

View File

@ -34,12 +34,12 @@ interface Data {
year: number; year: number;
broadcast: Broadcast; broadcast: Broadcast;
producers: Genre[]; producers: Genre[];
licensors: any[]; licensors: unknown[];
studios: Genre[]; studios: Genre[];
genres: Genre[]; genres: Genre[];
explicit_genres: any[]; explicit_genres: unknown[];
themes: Genre[]; themes: Genre[];
demographics: any[]; demographics: unknown[];
relations: Relation[]; relations: Relation[];
theme: Theme; theme: Theme;
external: External[]; external: External[];

View File

@ -10,7 +10,7 @@ interface Data {
name: string; name: string;
given_name: null; given_name: null;
family_name: null; family_name: null;
alternate_names: any[]; alternate_names: string[];
birthday: Date; birthday: Date;
favorites: number; favorites: number;
about: string; about: string;

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

View File

@ -6,14 +6,17 @@ const includeOptions = ["preference", "assignedRoles"] as const;
export const getUserOptionsSchema = z.object({ export const getUserOptionsSchema = z.object({
verbosity: z.enum( verbosity: z.enum(
["exists", "basic", "full"], ["exists", "basic", "full"],
"option: verbosity value must match with enum types" "option: verbosity value must match with enum types",
), ),
include: z include: z
.string() .string()
.optional() .optional()
.transform((val) => val?.split(",") ?? []) .transform((val) => val?.split(",") ?? [])
.refine( .refine(
(arr) => arr.every((val) => includeOptions.includes(val.trim() as any)), (arr) =>
"option: include value didn't match with enum types" arr.every((val) =>
includeOptions.includes(val.trim() as typeof includeOptions[number]),
),
"option: include value didn't match with enum types",
), ),
}); });

View File

@ -2,26 +2,6 @@ import { Prisma } from "@prisma/client";
import { ErrorForwarder } from "../../../helpers/error/instances/forwarder"; import { ErrorForwarder } from "../../../helpers/error/instances/forwarder";
import { userSessionModel } from "../userSession.model"; import { userSessionModel } from "../userSession.model";
type CreateUserSessionResponse = Prisma.UserSessionGetPayload<{
select: {
id: true;
deviceType: true;
isAuthenticated: true;
validUntil: true;
user: {
select: {
id: true;
name: true;
email: true;
username: true;
avatar: true;
birthDate: true;
bioProfile: true;
};
};
};
}>;
export const createUserSessionRepository = async ( export const createUserSessionRepository = async (
data: Prisma.UserSessionUncheckedCreateInput, data: Prisma.UserSessionUncheckedCreateInput,
) => { ) => {

View File

@ -1,4 +1,4 @@
import { minioBucketName, minioClient } from "../client"; import { minioClient } from "../client";
import { ensureBucketExists } from "../validations/ensureBucketExists"; import { ensureBucketExists } from "../validations/ensureBucketExists";
export const getStreamFile = async (filename: string) => { export const getStreamFile = async (filename: string) => {

View File

@ -1,4 +1,4 @@
import { minioBucketName, minioClient, minioProtocol } from "../client"; import { minioBucketName, minioClient } from "../client";
import { ensureBucketExists } from "../validations/ensureBucketExists"; import { ensureBucketExists } from "../validations/ensureBucketExists";
import { Readable } from "stream"; import { Readable } from "stream";
@ -7,7 +7,7 @@ export const uploadFile = async (
options?: { options?: {
fileDir?: string; fileDir?: string;
fileName?: string; fileName?: string;
} },
): Promise<string> => { ): Promise<string> => {
// Ensure the target MinIO bucket exists before performing any upload // Ensure the target MinIO bucket exists before performing any upload
await ensureBucketExists(); await ensureBucketExists();