🚧 wip: create bulk insert endpoint
This commit is contained in:
25
src/modules/internal/index.ts
Normal file
25
src/modules/internal/index.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Elysia, { Context } from "elysia";
|
||||
import { MediaFullInfoResponse } from "./types/mediaFullInfo.type";
|
||||
import { InsertMediaRepository } from "./repositories/insertMedia.repository";
|
||||
import { mainErrorHandler } from "../../helpers/error/handler";
|
||||
|
||||
const masterSourceAPI = "https://api.jikan.moe/v4";
|
||||
|
||||
export const internalModule = new Elysia({ prefix: "/internal" }).post(
|
||||
"/medias",
|
||||
async (ctx: Context & { body: { mal_id: number } }) => {
|
||||
try {
|
||||
const fullMediaData = await fetch(
|
||||
`${masterSourceAPI}/anime/${ctx.body.mal_id}/full`,
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => data as MediaFullInfoResponse);
|
||||
|
||||
// return fullMediaData;
|
||||
const createMedia = await InsertMediaRepository(fullMediaData);
|
||||
return createMedia;
|
||||
} catch (error) {
|
||||
return mainErrorHandler(ctx.set, error);
|
||||
}
|
||||
},
|
||||
);
|
||||
134
src/modules/internal/repositories/insertMedia.repository.ts
Normal file
134
src/modules/internal/repositories/insertMedia.repository.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { generateSlug } from "../../../helpers/characters/generateSlug";
|
||||
import { AppError } from "../../../helpers/error/instances/app";
|
||||
import { prisma } from "../../../utils/databases/prisma/connection";
|
||||
import { MediaFullInfoResponse } from "../types/mediaFullInfo.type";
|
||||
|
||||
export const InsertMediaRepository = async (data: MediaFullInfoResponse) => {
|
||||
try {
|
||||
/**
|
||||
* Genres Insertion
|
||||
*
|
||||
* This section handles the insertion of genres associated with the media.
|
||||
* It iterates over each genre in the media data, generates a slug for it,
|
||||
* and performs an upsert operation to ensure that the genre is either created
|
||||
* or updated in the database. The IDs of the inserted or updated genres are
|
||||
* collected for later association with the media.
|
||||
*
|
||||
* @param data - The full media data containing genres information.
|
||||
*/
|
||||
const genreIds: string[] = [];
|
||||
for (const genre of data.data.genres) {
|
||||
const slug = (await generateSlug(genre.name)) as string;
|
||||
const genrePayload = {
|
||||
name: genre.name,
|
||||
malId: genre.mal_id,
|
||||
malUrl: genre.url,
|
||||
createdBy: "b734b9bc-b4ea-408f-a80e-0a837ce884da",
|
||||
slug,
|
||||
};
|
||||
const insertedGenre = await prisma.genre.upsert({
|
||||
where: { slug },
|
||||
create: genrePayload,
|
||||
update: genrePayload,
|
||||
select: { id: true },
|
||||
});
|
||||
genreIds.push(insertedGenre.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Studios Insertion
|
||||
*
|
||||
* This section manages the insertion of studios associated with the media.
|
||||
* It processes each studio listed in the media data, generating a slug for
|
||||
* each and performing an upsert operation to either create or update the
|
||||
* studio record in the database. The IDs of the inserted or updated studios
|
||||
* are collected for later association with the media.
|
||||
*
|
||||
* @param data - The full media data containing studios information.
|
||||
*/
|
||||
const studioIds: string[] = [];
|
||||
for (const studio of data.data.studios) {
|
||||
const slug = (await generateSlug(studio.name)) as string;
|
||||
const studioPayload = {
|
||||
name: studio.name,
|
||||
malId: studio.mal_id,
|
||||
linkAbout: studio.url,
|
||||
createdBy: "b734b9bc-b4ea-408f-a80e-0a837ce884da",
|
||||
slug,
|
||||
};
|
||||
const insertedStudio = await prisma.studio.upsert({
|
||||
where: { slug },
|
||||
create: studioPayload,
|
||||
update: studioPayload,
|
||||
select: { id: true },
|
||||
});
|
||||
studioIds.push(insertedStudio.id);
|
||||
}
|
||||
for (const studio of data.data.producers) {
|
||||
const slug = (await generateSlug(studio.name)) as string;
|
||||
const studioPayload = {
|
||||
name: studio.name,
|
||||
malId: studio.mal_id,
|
||||
linkAbout: studio.url,
|
||||
createdBy: "b734b9bc-b4ea-408f-a80e-0a837ce884da",
|
||||
slug,
|
||||
};
|
||||
const insertedStudio = await prisma.studio.upsert({
|
||||
where: { slug },
|
||||
create: studioPayload,
|
||||
update: studioPayload,
|
||||
select: { id: true },
|
||||
});
|
||||
studioIds.push(insertedStudio.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Media Payload Construction and Upsert
|
||||
*
|
||||
* This section constructs the payload for the media insertion or update.
|
||||
* It gathers all necessary information from the media data, including
|
||||
* title, alternative titles, slug, associated genres and studios, score,
|
||||
* images, status, airing dates, synopsis, age rating, media type, source,
|
||||
* and other relevant details. This payload is then used in an upsert
|
||||
* operation to ensure that the media record is either created or updated
|
||||
* in the database.
|
||||
*
|
||||
* @param data - The full media data for constructing the media payload.
|
||||
*/
|
||||
const construct = {
|
||||
title: data.data.title,
|
||||
titleAlternative: (data.data.titles as unknown) as Prisma.InputJsonValue,
|
||||
slug: await generateSlug(data.data.title, {
|
||||
model: "media",
|
||||
target: "slug",
|
||||
}),
|
||||
malId: data.data.mal_id,
|
||||
genres: {
|
||||
connect: genreIds.map((id) => ({ id })),
|
||||
},
|
||||
studios: {
|
||||
connect: studioIds.map((id) => ({ id })),
|
||||
},
|
||||
score: data.data.score,
|
||||
pictureMedium: data.data.images.webp.image_url,
|
||||
pictureLarge: data.data.images.webp.large_image_url,
|
||||
status: data.data.status,
|
||||
startAiring: data.data.aired.from,
|
||||
endAiring: data.data.aired.to,
|
||||
synopsis: data.data.synopsis,
|
||||
ageRating: data.data.rating,
|
||||
mediaType: data.data.type,
|
||||
source: data.data.source,
|
||||
onDraft: false,
|
||||
uploadedBy: "b734b9bc-b4ea-408f-a80e-0a837ce884da",
|
||||
};
|
||||
return await prisma.media.upsert({
|
||||
where: { malId: data.data.mal_id },
|
||||
update: construct,
|
||||
create: construct,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new AppError(500, "Failed to insert media", error);
|
||||
}
|
||||
};
|
||||
125
src/modules/internal/types/mediaFullInfo.type.ts
Normal file
125
src/modules/internal/types/mediaFullInfo.type.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { MediaType } from "@prisma/client";
|
||||
export interface MediaFullInfoResponse {
|
||||
data: Data;
|
||||
}
|
||||
|
||||
interface Data {
|
||||
mal_id: number;
|
||||
url: string;
|
||||
images: { [key: string]: Image };
|
||||
trailer: Trailer;
|
||||
approved: boolean;
|
||||
titles: Title[];
|
||||
title: string;
|
||||
title_english: string;
|
||||
title_japanese: string;
|
||||
title_synonyms: string[];
|
||||
type: MediaType;
|
||||
source: string;
|
||||
episodes: number;
|
||||
status: string;
|
||||
airing: boolean;
|
||||
aired: Aired;
|
||||
duration: string;
|
||||
rating: string;
|
||||
score: number;
|
||||
scored_by: number;
|
||||
rank: number;
|
||||
popularity: number;
|
||||
members: number;
|
||||
favorites: number;
|
||||
synopsis: string;
|
||||
background: string;
|
||||
season: string;
|
||||
year: number;
|
||||
broadcast: Broadcast;
|
||||
producers: Genre[];
|
||||
licensors: any[];
|
||||
studios: Genre[];
|
||||
genres: Genre[];
|
||||
explicit_genres: any[];
|
||||
themes: Genre[];
|
||||
demographics: any[];
|
||||
relations: Relation[];
|
||||
theme: Theme;
|
||||
external: External[];
|
||||
streaming: External[];
|
||||
}
|
||||
|
||||
interface Aired {
|
||||
from: Date;
|
||||
to: Date;
|
||||
prop: Prop;
|
||||
string: string;
|
||||
}
|
||||
|
||||
interface Prop {
|
||||
from: From;
|
||||
to: From;
|
||||
}
|
||||
|
||||
interface From {
|
||||
day: number;
|
||||
month: number;
|
||||
year: number;
|
||||
}
|
||||
|
||||
interface Broadcast {
|
||||
day: string;
|
||||
time: string;
|
||||
timezone: string;
|
||||
string: string;
|
||||
}
|
||||
|
||||
interface External {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Genre {
|
||||
mal_id: number;
|
||||
type: Type;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
enum Type {
|
||||
Anime = "anime",
|
||||
Manga = "manga",
|
||||
}
|
||||
|
||||
interface Image {
|
||||
image_url: string;
|
||||
small_image_url: string;
|
||||
large_image_url: string;
|
||||
}
|
||||
|
||||
interface Relation {
|
||||
relation: string;
|
||||
entry: Genre[];
|
||||
}
|
||||
|
||||
interface Theme {
|
||||
openings: string[];
|
||||
endings: string[];
|
||||
}
|
||||
|
||||
interface Title {
|
||||
type: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Trailer {
|
||||
youtube_id: null;
|
||||
url: null;
|
||||
embed_url: string;
|
||||
images: Images;
|
||||
}
|
||||
|
||||
interface Images {
|
||||
image_url: null;
|
||||
small_image_url: null;
|
||||
medium_image_url: null;
|
||||
large_image_url: null;
|
||||
maximum_image_url: null;
|
||||
}
|
||||
Reference in New Issue
Block a user