💥 breaking: upgrade Elysia to v1.4 and update codebase accordingly

This commit is contained in:
2026-03-07 13:41:13 +07:00
parent c992314cf1
commit 0b786206e4
15 changed files with 352 additions and 287 deletions

View File

@ -0,0 +1,18 @@
import { ElysiaOpenAPIConfig } from "@elysiajs/openapi";
export const openAPIConfig: ElysiaOpenAPIConfig = {
documentation: {
info: {
title: "TV Nounoz API",
description: "API documentation for TV Nounoz backend services",
version: "1.0.0",
},
tags: [
{
name: "Internal",
description:
"Endpoints for internal use, such as administrative tasks and data management operations. These endpoints may require authentication and are not intended for public use.",
},
],
},
};

View File

@ -0,0 +1,8 @@
import { Static } from "elysia";
export type InferSchema<S> = {
body: S extends { body: any } ? Static<S["body"]> : never;
query: S extends { query: any } ? Static<S["query"]> : never;
params: S extends { params: any } ? Static<S["params"]> : never;
headers: S extends { headers: any } ? Static<S["headers"]> : never;
};

View File

@ -3,6 +3,7 @@
import openapi from "@elysiajs/openapi";
import { middleware } from "./middleware";
import { validateEnv } from "./utils/startups/validateEnv";
import { openAPIConfig } from "./config/documentation/openAPI";
validateEnv();
@ -18,19 +19,7 @@ async function bootstrap() {
new Elysia()
.use(middleware)
.use(routes)
.use(
openapi({
documentation: {
tags: [
{
name: "Internal",
description:
"Endpoints for internal use only, not exposed to public API consumers.",
},
],
},
}),
)
.use(openapi(openAPIConfig))
.listen(process.env.APP_PORT || 3000);
console.log(

View File

@ -1,47 +1,22 @@
import { Context } from "elysia";
import { Context, Static } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler";
import { bulkInsertAnimeService } from "../services/http/bulkInsertAnime.service";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { bulkInsertMediaSchema } from "../schemas/bulkInsertMedia.schema";
/**
* @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.
* Insert anime and its related data into the database using MAL ID.
*
* @param {Context & { body: { mal_id: number } }} ctx
* The context object containing the request body.
* The body must include:
* - mal_id: number - The MyAnimeList ID of the anime to be inserted.
* This controller orchestrates the bulk insertion process including
* genres, studios, producers, licensors, themes, voice actors, and relations.
*
* @example
* Request route: POST /internal/anime/bulk-insert
* Request body:
* {
* "mal_id": 12345
* }
*
* @returns {Promise<Object>}
* A response object indicating success or failure.
* Return example:
* {
* success: true,
* status: 201,
* message: "Bulk insert anime 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
* }
* See OpenAPI documentation for request/response schema.
*/
export const bulkInsertMediaController = async (
ctx: Context & { body: { mal_id: number } },
) => {
export const bulkInsertMediaController = async (ctx: {
set: Context["set"];
body: Static<typeof bulkInsertMediaSchema.body>;
query: Static<typeof bulkInsertMediaSchema.query>;
}) => {
try {
const bulkInsertResult = await bulkInsertAnimeService(ctx.body.mal_id);
return returnWriteResponse(

View File

@ -3,7 +3,7 @@ import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
export const bulkInsertMediaSchema = {
body: t.Object({
media_mal_id: t.Number({
mal_id: t.Number({
description:
"The MyAnimeList ID of the media for which episodes will be inserted",
}),
@ -15,35 +15,89 @@ export const bulkInsertMediaSchema = {
}),
),
}),
response: {
201: t.Object({
success: t.Boolean({ default: true }),
status: t.Number(),
message: t.String(),
data: t.Optional(
t.Unknown(),
),
}),
404: t.Object({
success: t.Boolean({ default: false }),
status: t.Number(),
message: t.String(),
error: t.Optional(
t.Unknown(),
),
}),
500: t.Object({
success: t.Optional(t.Boolean({ default: false })),
status: t.Number(),
message: t.String(),
error: t.Optional(
t.Unknown(),
),
}),
},
detail: {
summary: "Bulk insert media",
description:
"Fetch media data from external sources and insert them into database",
responses: {
201: {
description: "Bulk insert media operation completed successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", default: true },
status: { type: "integer", default: 201 },
message: {
type: "string",
default: "Bulk insert anime operation completed successfully",
},
data: {
type: "object",
properties: {
status: { type: "string", default: "airing" },
id: {
type: "string",
default: "019cc6c9-80b2-7f9a-b1b4-c8fb612ed481",
},
title: { type: "string", default: "Sakamoto Days" },
titleAlternative: { type: "object", default: {} },
slug: { type: "string", default: "sakamoto-days" },
malId: { type: "integer", default: 58939 },
pictureMedium: {
type: "string",
default:
"https://myanimelist.net/images/anime/1026/146459.webp",
},
pictureLarge: {
type: "string",
default:
"https://myanimelist.net/images/anime/1026/146459.webp",
},
country: { type: "string", default: "JP" },
score: { type: "string", default: "9.0" },
startAiring: {
type: "string",
format: "date-time",
default: "2022-07-01T00:00:00.000Z",
},
endAiring: {
type: "string",
format: "date-time",
default: "2022-07-01T00:00:00.000Z",
},
synopsis: {
type: "string",
default: "No synopsis available",
},
ageRating: { type: "string", default: "PG-13" },
mediaType: { type: "string", default: "ANIME" },
source: { type: "string" },
onDraft: { type: "boolean", default: false },
uploadedBy: { type: "string", default: "system" },
deletedAt: {
type: "string",
format: "date-time",
default: "2022-07-01T00:00:00.000Z",
},
createdAt: {
type: "string",
format: "date-time",
default: "2022-07-01T00:00:00.000Z",
},
updatedAt: {
type: "string",
format: "date-time",
default: "2022-07-01T00:00:00.000Z",
},
},
},
},
},
},
},
},
},
},
} satisfies AppRouteSchema;

View File

@ -1,5 +1,4 @@
import { Prisma } from "@prisma/client";
import { getContentReferenceAPI } from "../../../../config/apis/media.reference";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { bulkInsertGenresRepository } from "../../repositories/bulkInsertGenres.repository";
import { InsertMediaRepository } from "../../repositories/bulkinsertMedia.repository";
@ -9,6 +8,7 @@ import { generateSlug } from "../../../../helpers/characters/generateSlug";
import { bulkInsertCharWithVAService } from "../internal/bulkInsertCharWithVA.service";
import { generateUUIDv7 } from "../../../../helpers/databases/uuidv7";
import { SystemAccountId } from "../../../../config/account/system";
import { getContentReferenceAPI } from "../../../../config/apis/jikan/media.reference";
export const bulkInsertAnimeService = async (malId: number) => {
try {
@ -24,8 +24,8 @@ export const bulkInsertAnimeService = async (malId: number) => {
const constructMediaPayload: Prisma.MediaUpsertArgs["create"] = {
id: generateUUIDv7(),
title: mediaFullInfo.data.title,
titleAlternative: (mediaFullInfo.data
.titles as unknown) as Prisma.InputJsonValue,
titleAlternative: mediaFullInfo.data
.titles as unknown as Prisma.InputJsonValue,
slug: await generateSlug(mediaFullInfo.data.title, {
model: "media",
target: "slug",

View File

@ -1,10 +1,10 @@
import { getEpisodeReferenceAPI } from "../../../../config/apis/episode.reference";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { MediaEpisodeInfoResponse } from "../../types/mediaEpisodeInfo.type";
import { getMediaByMalIdRepository } from "../../../media/repositories/GET/getMediaByMalId.repository";
import { AppError } from "../../../../helpers/error/instances/app";
import { SystemAccountId } from "../../../../config/account/system";
import { bulkInsertEpisodesRepository } from "../../repositories/bulkInsertEpisodes.repository";
import { getEpisodeReferenceAPI } from "../../../../config/apis/jikan/episode.reference";
export const bulkInsertEpisodeService = async (
mal_id: number,

View File

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