Compare commits

..

2 Commits

11 changed files with 213 additions and 127 deletions

View File

@ -1,3 +1,4 @@
{ {
"parser": "typescript" "parser": "typescript",
"printWidth": 120
} }

View File

@ -1,62 +1,23 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler"; 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";
import { bulkInsertEpisodeSchema } from "../schemas/bulkInsertEpisode.schema";
/** /**
* @function bulkInsertMediaController * Perform bulk insert of episodes for a specific media.
* @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. * This operation fetches episode data from external sources and inserts them into the database.
* *
* @param {Context & { body: { media_mal_id: number }; query: { page?: number } }} ctx * See OpenAPI documentation for request/response schema.
* 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: {
ctx: Context & { body: { media_mal_id: number }; query: { page?: number } }, set: Context["set"];
) => { body: Static<typeof bulkInsertEpisodeSchema.body>;
query: Static<typeof bulkInsertEpisodeSchema.query>;
}) => {
try { try {
const bulkInsertResult = await bulkInsertEpisodeService( const bulkInsertResult = await bulkInsertEpisodeService(ctx.body.media_mal_id, ctx.query.page);
ctx.body.media_mal_id, return returnWriteResponse(ctx.set, 201, "Bulk insert episode operation completed successfully", bulkInsertResult);
ctx.query.page,
);
return returnWriteResponse(
ctx.set,
201,
"Bulk insert episode operation completed successfully",
bulkInsertResult,
);
} catch (err) { } catch (err) {
return mainErrorHandler(ctx.set, err); return mainErrorHandler(ctx.set, err);
} }

View File

@ -15,7 +15,6 @@ import { bulkInsertMediaSchema } from "../schemas/bulkInsertMedia.schema";
export const bulkInsertMediaController = async (ctx: { export const bulkInsertMediaController = async (ctx: {
set: Context["set"]; set: Context["set"];
body: Static<typeof bulkInsertMediaSchema.body>; body: Static<typeof bulkInsertMediaSchema.body>;
query: Static<typeof bulkInsertMediaSchema.query>;
}) => { }) => {
try { try {
const bulkInsertResult = await bulkInsertAnimeService(ctx.body.mal_id); const bulkInsertResult = await bulkInsertAnimeService(ctx.body.mal_id);

View File

@ -1,19 +1,8 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler"; import { mainErrorHandler } from "../../../helpers/error/handler";
import { bulkInsertVideoService } from "../services/http/bulkInsertVideo.service"; import { bulkInsertVideoService } from "../services/http/bulkInsertVideo.service";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { bulkInsertVideoSchema } from "../schemas/bulkInsertVideo.schema";
export interface BulkInsertVideoBodyRequest {
media_id: string;
data: Array<{
episode: number;
videos: Array<{
service_id: string;
video_code: string;
thumbnail_code?: string;
}>;
}>;
}
/** /**
* @function bulkInsertVideoController * @function bulkInsertVideoController
@ -76,12 +65,13 @@ export interface BulkInsertVideoBodyRequest {
* error: { ...errorDetails } // Additional error details if available and the env run on development mode * error: { ...errorDetails } // Additional error details if available and the env run on development mode
* } * }
*/ */
export const bulkInsertVideoController = async ( export const bulkInsertVideoController = async (ctx: {
ctx: Context & { body: BulkInsertVideoBodyRequest }, set: Context["set"];
) => { body: Static<typeof bulkInsertVideoSchema.body>;
}) => {
try { try {
const insertedVideos = await bulkInsertVideoService(ctx.body); const insertedVideos = await bulkInsertVideoService(ctx.body);
return returnWriteResponse(ctx.set, 201, "Videos inserted", insertedVideos); return returnWriteResponse(ctx.set, 201, "Videos inserted successfully", insertedVideos);
} catch (error) { } catch (error) {
throw mainErrorHandler(ctx.set, error); throw mainErrorHandler(ctx.set, error);
} }

View File

@ -1,55 +1,24 @@
import { Context } from "elysia"; import { Context, Static } from "elysia";
import { mainErrorHandler } from "../../../helpers/error/handler"; import { mainErrorHandler } from "../../../helpers/error/handler";
import { returnWriteResponse } from "../../../helpers/callback/httpResponse"; import { returnWriteResponse } from "../../../helpers/callback/httpResponse";
import { updateAllEpisodeThumbnailService } from "../services/http/updateAllEpisodeThumbnail.service"; import { updateAllEpisodeThumbnailService } from "../services/http/updateAllEpisodeThumbnail.service";
import { updateAllEpisodeThumbnailSchema } from "../schemas/updateAllEpisodeThumbnail.schema";
/** /**
* @function updateAllEpisodeThumbnailController * Updating all episode thumbnails for a specific target service reference ID.
* @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 * This controller handles the bulk update of episode thumbnails for all episodes associated with a specific service reference ID.
* The context object containing the request body. * It fetches the latest thumbnail data from external sources and updates the existing episode records in the database accordingly.
* The body must include:
* - service_reference_id: string - The ID of the service to which the episodes belong.
* *
* @example * See OpenAPI documentation for request/response schema.
* 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 ( export const updateAllEpisodeThumbnailController = async (ctx: {
ctx: Context & { body: { service_reference_id?: string } }, set: Context["set"];
) => { body: Static<typeof updateAllEpisodeThumbnailSchema.body>;
}) => {
try { try {
const newEpisodeThumbnailsCount = await updateAllEpisodeThumbnailService( const newEpisodeThumbnailsCount = await updateAllEpisodeThumbnailService(ctx.body.service_reference_id);
ctx.body.service_reference_id, return returnWriteResponse(ctx.set, 204, `Updating ${newEpisodeThumbnailsCount} episode thumbnails successfully.`);
);
return returnWriteResponse(
ctx.set,
204,
`Updating ${newEpisodeThumbnailsCount} episode thumbnails successfully.`,
);
} catch (error) { } catch (error) {
return mainErrorHandler(ctx.set, error); return mainErrorHandler(ctx.set, error);
} }

View File

@ -7,15 +7,18 @@ import { updateAllEpisodeThumbnailController } from "./controllers/updateAllEpis
import { purgeUnusedSessionController } from "./controllers/purgeUnusedSession.controller"; import { purgeUnusedSessionController } from "./controllers/purgeUnusedSession.controller";
import { createHeroBannerController } from "./controllers/createHeroBanner.controller"; import { createHeroBannerController } from "./controllers/createHeroBanner.controller";
import { bulkInsertMediaSchema } from "./schemas/bulkInsertMedia.schema"; import { bulkInsertMediaSchema } from "./schemas/bulkInsertMedia.schema";
import { bulkInsertEpisodeSchema } from "./schemas/bulkInsertEpisode.schema";
import { updateAllEpisodeThumbnailSchema } from "./schemas/updateAllEpisodeThumbnail.schema";
import { bulkInsertVideoSchema } from "./schemas/bulkInsertVideo.schema";
export const internalModule = new Elysia({ export const internalModule = new Elysia({
prefix: "/internal", prefix: "/internal",
tags: ["Internal"], tags: ["Internal"],
}) })
.post("/media/bulk-insert", bulkInsertMediaController, bulkInsertMediaSchema) .post("/media/bulk-insert", bulkInsertMediaController, bulkInsertMediaSchema)
.post("/episode/bulk-insert", bulkInsertEpisodeController) .post("/episode/bulk-insert", bulkInsertEpisodeController, bulkInsertEpisodeSchema)
.put("/episode/update-thumbnails", updateAllEpisodeThumbnailController) .put("/episode/update-thumbnails", updateAllEpisodeThumbnailController, updateAllEpisodeThumbnailSchema)
.post("/video/bulk-insert", bulkInsertVideoController) .post("/video/bulk-insert", bulkInsertVideoController, bulkInsertVideoSchema)
.post("/video-service", createVideoServiceInternalController) .post("/video-service", createVideoServiceInternalController)
.post("/user-session/purge-unused", purgeUnusedSessionController) .post("/user-session/purge-unused", purgeUnusedSessionController)
.post("/hero-banner", createHeroBannerController); .post("/hero-banner", createHeroBannerController);

View File

@ -0,0 +1,73 @@
import { t } from "elysia";
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
export const bulkInsertEpisodeSchema = {
body: t.Object({
media_mal_id: t.Number({
description: "The MyAnimeList ID of the media for which episodes will be inserted",
}),
}),
query: t.Object({
page: t.Optional(
t.Number({
description: "Episode page number to fetch",
}),
),
}),
detail: {
summary: "Bulk insert episodes for a media",
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.",
responses: {
201: {
description:
"Bulk insert episode operation completed successfully (Data returned only if the env run on development mode)",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", default: true },
status: { type: "integer", default: 201 },
message: {
type: "string",
default: "Bulk insert episode operation completed successfully",
},
data: {
type: "object",
properties: {
pagination: {
type: "object",
properties: {
last_visible_page: { type: "integer", default: 1 },
has_next_page: { type: "boolean", default: false },
},
},
data: {
type: "array",
items: {
type: "object",
properties: {
mal_id: { type: "integer", default: 1 },
url: { type: "string", default: "https://myanimelist.net/anime/1" },
title: { type: "string", default: "Example Episode Title" },
title_japanese: { type: "string", default: "例のエピソードタイトル" },
title_romanji: { type: "string", default: "Rei no Episōdo Taitoru" },
aired: { type: "string", format: "date-time", default: "2022-01-01T00:00:00.000Z" },
score: { type: "number", default: 8.5 },
filler: { type: "boolean", default: false },
recap: { type: "boolean", default: false },
forum_url: { type: "string", default: "https://myanimelist.net/forum/1" },
},
},
},
},
},
},
},
},
},
},
},
},
} satisfies AppRouteSchema;

View File

@ -8,13 +8,6 @@ export const bulkInsertMediaSchema = {
"The MyAnimeList ID of the media for which episodes will be inserted", "The MyAnimeList ID of the media for which episodes will be inserted",
}), }),
}), }),
query: t.Object({
page: t.Optional(
t.Number({
description: "Episode page number to fetch",
}),
),
}),
detail: { detail: {
summary: "Bulk insert media", summary: "Bulk insert media",
description: description:

View File

@ -0,0 +1,63 @@
import { t } from "elysia";
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
export const bulkInsertVideoSchema = {
body: t.Object({
media_id: t.String({
description: "The ID of the media for which episodes will be inserted",
}),
data: t.Array(
t.Object({
episode: t.Number({
description: "The episode number",
}),
videos: t.Array(
t.Object({
service_id: t.String({
description: "The ID of the video service",
}),
video_code: t.String({
description: "The code of the video on the service",
}),
thumbnail_code: t.Optional(
t.String({
description: "The code of the thumbnail for the video on the service",
}),
),
}),
),
}),
),
}),
detail: {
summary: "Bulk insert videos for a media episode",
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.",
responses: {
201: {
description: "Videos inserted successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", default: true },
status: { type: "integer", default: 201 },
message: { type: "string", default: "Videos inserted successfully" },
data: {
type: "array",
default: ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
description: "An array of IDs of the inserted videos",
items: {
type: "string",
description: "The ID of the inserted video",
},
},
},
},
},
},
},
},
},
} satisfies AppRouteSchema;

View File

@ -0,0 +1,35 @@
import { t } from "elysia";
import { AppRouteSchema } from "../../../helpers/types/AppRouteSchema";
export const updateAllEpisodeThumbnailSchema = {
body: t.Object({
service_reference_id: t.String({
description: "The ID of the service to which the target of episode thumbnails belong",
}),
}),
detail: {
summary: "Bulk update episode thumbnails",
description:
"Perform bulk update of episode thumbnails for all episodes associated with a specific service reference ID. This operation fetches the latest thumbnail data from external sources and updates the existing episode records in the database accordingly.",
responses: {
204: {
description: "Updating episode thumbnails operation completed successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", default: true },
status: { type: "integer", default: 204 },
message: {
type: "string",
default: "Updating {newEpisodeThumbnailsCount} episode thumbnails operation completed successfully",
},
},
},
},
},
},
},
},
} satisfies AppRouteSchema;

View File

@ -1,12 +1,11 @@
import { SystemAccountId } from "../../../../config/account/system"; import { SystemAccountId } from "../../../../config/account/system";
import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder"; import { ErrorForwarder } from "../../../../helpers/error/instances/forwarder";
import { BulkInsertVideoBodyRequest } from "../../controllers/bulkInsertVideo.controller";
import { findEpisodeWithMediaIdRepository } from "../../repositories/findEpisodeWithMediaId.repository"; import { findEpisodeWithMediaIdRepository } from "../../repositories/findEpisodeWithMediaId.repository";
import { bulkInsertVideoRepository } from "../../repositories/bulkInsertVideo.repository"; import { bulkInsertVideoRepository } from "../../repositories/bulkInsertVideo.repository";
import { Static } from "elysia";
import { bulkInsertVideoSchema } from "../../schemas/bulkInsertVideo.schema";
export const bulkInsertVideoService = async ( export const bulkInsertVideoService = async (body: Static<typeof bulkInsertVideoSchema.body>) => {
body: BulkInsertVideoBodyRequest,
) => {
try { try {
const insertedVideos: string[] = []; const insertedVideos: string[] = [];
for (const episodeData of body.data) { for (const episodeData of body.data) {