From 1a0c09914cc5dcc7260fd4999d1d52f282df30ff Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Mon, 13 May 2024 18:50:22 +0200 Subject: [PATCH 1/7] :card_file_box: Change database structure to new one for better image saving --- package.json | 3 + .../20240504123945_init/migration.sql | 36 -------- .../20240513163433_init/migration.sql | 91 +++++++++++++++++++ prisma/schema.prisma | 91 ++++++++++++++----- prisma/seed.ts | 38 ++++++++ .../webtoon/models/enums/image-types.ts | 9 ++ 6 files changed, 210 insertions(+), 58 deletions(-) delete mode 100644 prisma/migrations/20240504123945_init/migration.sql create mode 100644 prisma/migrations/20240513163433_init/migration.sql create mode 100644 prisma/seed.ts create mode 100644 src/modules/webtoon/webtoon/models/enums/image-types.ts diff --git a/package.json b/package.json index 5e9c0ba..1863f15 100644 --- a/package.json +++ b/package.json @@ -77,5 +77,8 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" + }, + "prisma": { + "seed": "ts-node prisma/seed.ts" } } diff --git a/prisma/migrations/20240504123945_init/migration.sql b/prisma/migrations/20240504123945_init/migration.sql deleted file mode 100644 index f95857c..0000000 --- a/prisma/migrations/20240504123945_init/migration.sql +++ /dev/null @@ -1,36 +0,0 @@ --- CreateTable -CREATE TABLE "webtoons" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "author" TEXT NOT NULL, - "language" TEXT NOT NULL, - "episodes_thumbnails_zip_sum" TEXT, - "thumbnail_sum" TEXT NOT NULL, - "background_banner_sum" TEXT NOT NULL, - "top_banner_sum" TEXT NOT NULL, - "mobile_banner_sum" TEXT NOT NULL -); - --- CreateTable -CREATE TABLE "episodes" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "title" TEXT NOT NULL, - "webtoon_id" INTEGER NOT NULL, - "number" INTEGER NOT NULL, - "images_zip_sum" TEXT NOT NULL, - "thumbnail_sum" TEXT NOT NULL, - CONSTRAINT "episodes_webtoon_id_fkey" FOREIGN KEY ("webtoon_id") REFERENCES "webtoons" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "episode_images" ( - "episode_id" INTEGER NOT NULL, - "number" INTEGER NOT NULL, - "sum" TEXT NOT NULL, - - PRIMARY KEY ("episode_id", "number"), - CONSTRAINT "episode_images_episode_id_fkey" FOREIGN KEY ("episode_id") REFERENCES "episodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "episodes_webtoon_id_number_key" ON "episodes"("webtoon_id", "number"); diff --git a/prisma/migrations/20240513163433_init/migration.sql b/prisma/migrations/20240513163433_init/migration.sql new file mode 100644 index 0000000..a75b178 --- /dev/null +++ b/prisma/migrations/20240513163433_init/migration.sql @@ -0,0 +1,91 @@ +-- CreateTable +CREATE TABLE "image_types" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "images" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "sum" TEXT NOT NULL, + "type_id" INTEGER NOT NULL, + CONSTRAINT "images_type_id_fkey" FOREIGN KEY ("type_id") REFERENCES "image_types" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "genres" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL +); + +-- CreateTable +CREATE TABLE "webtoon_genres" ( + "webtoon_id" INTEGER NOT NULL, + "genre_id" INTEGER NOT NULL, + + PRIMARY KEY ("webtoon_id", "genre_id"), + CONSTRAINT "webtoon_genres_webtoon_id_fkey" FOREIGN KEY ("webtoon_id") REFERENCES "webtoons" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "webtoon_genres_genre_id_fkey" FOREIGN KEY ("genre_id") REFERENCES "genres" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "webtoons" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "title" TEXT NOT NULL, + "author" TEXT NOT NULL, + "language" TEXT NOT NULL, + "thumbnail_id" INTEGER NOT NULL, + "background_banner_id" INTEGER NOT NULL, + "top_banner_id" INTEGER NOT NULL, + "mobile_banner_id" INTEGER NOT NULL, + CONSTRAINT "webtoons_thumbnail_id_fkey" FOREIGN KEY ("thumbnail_id") REFERENCES "images" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "webtoons_background_banner_id_fkey" FOREIGN KEY ("background_banner_id") REFERENCES "images" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "webtoons_top_banner_id_fkey" FOREIGN KEY ("top_banner_id") REFERENCES "images" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "webtoons_mobile_banner_id_fkey" FOREIGN KEY ("mobile_banner_id") REFERENCES "images" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "episodes" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "title" TEXT NOT NULL, + "number" INTEGER NOT NULL, + "webtoon_id" INTEGER NOT NULL, + "thumbnail_id" INTEGER NOT NULL, + CONSTRAINT "episodes_webtoon_id_fkey" FOREIGN KEY ("webtoon_id") REFERENCES "webtoons" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "episodes_thumbnail_id_fkey" FOREIGN KEY ("thumbnail_id") REFERENCES "images" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "episode_images" ( + "number" INTEGER NOT NULL, + "episode_id" INTEGER NOT NULL, + "image_id" INTEGER NOT NULL, + + PRIMARY KEY ("episode_id", "number"), + CONSTRAINT "episode_images_episode_id_fkey" FOREIGN KEY ("episode_id") REFERENCES "episodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "episode_images_image_id_fkey" FOREIGN KEY ("image_id") REFERENCES "images" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "images_sum_key" ON "images"("sum"); + +-- CreateIndex +CREATE UNIQUE INDEX "webtoons_thumbnail_id_key" ON "webtoons"("thumbnail_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "webtoons_background_banner_id_key" ON "webtoons"("background_banner_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "webtoons_top_banner_id_key" ON "webtoons"("top_banner_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "webtoons_mobile_banner_id_key" ON "webtoons"("mobile_banner_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "webtoons_title_author_language_key" ON "webtoons"("title", "author", "language"); + +-- CreateIndex +CREATE UNIQUE INDEX "episodes_webtoon_id_number_key" ON "episodes"("webtoon_id", "number"); + +-- CreateIndex +CREATE UNIQUE INDEX "episode_images_image_id_key" ON "episode_images"("image_id"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0c6da95..782c249 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,40 +13,87 @@ datasource db { url = "file:./database.db" } -model Webtoon { - id Int @id @default(autoincrement()) - title String - author String - language String - episodes_thumbnails_zip_sum String? - thumbnail_sum String - background_banner_sum String - top_banner_sum String - mobile_banner_sum String - episodes Episode[] +model ImageTypes { + id Int @id @default(autoincrement()) + name String + image Images[] + @@map("image_types") +} + +model Images { + id Int @id @default(autoincrement()) + sum String @unique + type_id Int + type ImageTypes @relation(fields: [type_id], references: [id]) + thumbnail Webtoons? @relation("thumbnail") + background_banner Webtoons? @relation("background_banner") + top_banner Webtoons? @relation("top_banner") + mobile_banner Webtoons? @relation("mobile_banner") + episodes Episodes[] + episode_images EpisodeImages[] + + @@map("images") +} + +model Genres { + id Int @id @default(autoincrement()) + name String + webtoon_genres WebtoonGenres[] + + @@map("genres") +} + +model WebtoonGenres { + webtoon_id Int + genre_id Int + webtoon Webtoons @relation(fields: [webtoon_id], references: [id]) + genre Genres @relation(fields: [genre_id], references: [id]) + + @@id([webtoon_id, genre_id]) + @@map("webtoon_genres") +} + +model Webtoons { + id Int @id @default(autoincrement()) + title String + author String + language String + thumbnail_id Int @unique + thumbnail Images @relation(fields: [thumbnail_id], references: [id], name: "thumbnail") + background_banner_id Int @unique + background_banner Images @relation(fields: [background_banner_id], references: [id], name: "background_banner") + top_banner_id Int @unique + top_banner Images @relation(fields: [top_banner_id], references: [id], name: "top_banner") + mobile_banner_id Int @unique + mobile_banner Images @relation(fields: [mobile_banner_id], references: [id], name: "mobile_banner") + genres WebtoonGenres[] + episodes Episodes[] + + @@unique([title, author, language]) @@map("webtoons") } -model Episode { - id Int @id @default(autoincrement()) +model Episodes { + id Int @id @default(autoincrement()) title String - webtoon_id Int - webtoon Webtoon @relation(fields: [webtoon_id], references: [id]) number Int - images_zip_sum String - thumbnail_sum String - episode_images EpisodeImage[] + webtoon_id Int + webtoon Webtoons @relation(fields: [webtoon_id], references: [id]) + thumbnail_id Int + thumbnail Images @relation(fields: [thumbnail_id], references: [id]) + episode_images EpisodeImages[] @@unique([webtoon_id, number]) @@map("episodes") } -model EpisodeImage { - episode_id Int - episode Episode @relation(fields: [episode_id], references: [id]) +model EpisodeImages { number Int - sum String + episode_id Int + episode Episodes @relation(fields: [episode_id], references: [id]) + image_id Int @unique + image Images @relation(fields: [image_id], references: [id]) @@id([episode_id, number]) @@map("episode_images") diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..c7664ae --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,38 @@ +import {PrismaClient} from "@prisma/client"; +import * as dotenv from "dotenv"; +import WebtoonGenres from "./../src/modules/webtoon/webtoon/models/enums/webtoon-genres"; +import ImageTypes from "./../src/modules/webtoon/webtoon/models/enums/image-types"; + +dotenv.config(); + +// initialize Prisma Client +const prisma = new PrismaClient(); + +async function main(){ + const webtoon_genres_values = Object.values(WebtoonGenres).map(value => ({name: value})); + await seed(prisma.genres, webtoon_genres_values); + + const image_types_values = Object.values(ImageTypes).map(value => ({name: value})); + await seed(prisma.imageTypes, image_types_values); +} + +async function seed(table: any, data: any[]){ + for(let i = 1; i <= data.length; i++){ + await table.upsert({ + where: {id: i}, + update: { + ...data[i - 1], + }, + create: { + ...data[i - 1], + }, + }); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}).finally(async() => { + await prisma.$disconnect(); +}); diff --git a/src/modules/webtoon/webtoon/models/enums/image-types.ts b/src/modules/webtoon/webtoon/models/enums/image-types.ts new file mode 100644 index 0000000..e3ee3da --- /dev/null +++ b/src/modules/webtoon/webtoon/models/enums/image-types.ts @@ -0,0 +1,9 @@ +enum ImageTypes{ + WEBTOON_THUMBNAIL = "WEBTOON_THUMBNAIL", + WEBTOON_BACKGROUND_BANNER = "WEBTOON_BACKGROUND_BANNER", + WEBTOON_TOP_BANNER = "WEBTOON_TOP_BANNER", + WEBTOON_MOBILE_BANNER = "WEBTOON_MOBILE_BANNER", + EPISODE_THUMBNAIL = "EPISODE_THUMBNAIL", + EPISODE_IMAGE = "EPISODE_IMAGE", +} +export default ImageTypes; From 2e4835fdfe390db4f8f2ce838bebb78e9d2b43cd Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Mon, 13 May 2024 18:51:00 +0200 Subject: [PATCH 2/7] :fire: Deprecate future useless methods --- src/modules/misc/misc.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/misc/misc.service.ts b/src/modules/misc/misc.service.ts index 7228e25..53a4431 100644 --- a/src/modules/misc/misc.service.ts +++ b/src/modules/misc/misc.service.ts @@ -33,6 +33,10 @@ export class MiscService{ return createHash("sha256").update(data).digest("hex"); } + /** + * @deprecated + * @param zip + */ async generateZip(zip: JSZip): Promise<[Buffer, string]>{ const zipBuffer = await zip.generateAsync({ type: "nodebuffer", From f9a0694723e1a35f3fda4cf932aacfd00022e02e Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 19:27:33 +0200 Subject: [PATCH 3/7] :sparkles: Implement new image storage behavior --- .../migration.sql | 3 - prisma/schema.prisma | 2 +- .../webtoon/download-manager.service.ts | 23 +- .../webtoon/webtoon-database.service.ts | 291 ++++++++++-------- .../webtoon/webtoon-downloader.service.ts | 7 +- 5 files changed, 176 insertions(+), 150 deletions(-) rename prisma/migrations/{20240513163433_init => 20240513184357_init}/migration.sql (97%) diff --git a/prisma/migrations/20240513163433_init/migration.sql b/prisma/migrations/20240513184357_init/migration.sql similarity index 97% rename from prisma/migrations/20240513163433_init/migration.sql rename to prisma/migrations/20240513184357_init/migration.sql index a75b178..5306491 100644 --- a/prisma/migrations/20240513163433_init/migration.sql +++ b/prisma/migrations/20240513184357_init/migration.sql @@ -86,6 +86,3 @@ CREATE UNIQUE INDEX "webtoons_title_author_language_key" ON "webtoons"("title", -- CreateIndex CREATE UNIQUE INDEX "episodes_webtoon_id_number_key" ON "episodes"("webtoon_id", "number"); - --- CreateIndex -CREATE UNIQUE INDEX "episode_images_image_id_key" ON "episode_images"("image_id"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 782c249..c5c6760 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -92,7 +92,7 @@ model EpisodeImages { number Int episode_id Int episode Episodes @relation(fields: [episode_id], references: [id]) - image_id Int @unique + image_id Int image Images @relation(fields: [image_id], references: [id]) @@id([episode_id, number]) diff --git a/src/modules/webtoon/webtoon/download-manager.service.ts b/src/modules/webtoon/webtoon/download-manager.service.ts index 5dbfbb0..e52654a 100644 --- a/src/modules/webtoon/webtoon/download-manager.service.ts +++ b/src/modules/webtoon/webtoon/download-manager.service.ts @@ -16,6 +16,7 @@ export class DownloadManagerService{ private cacheLoaded: boolean = false; private readonly cachePromise: Promise; private readonly queue: WebtoonQueue; + private currentDownload: CachedWebtoonModel | undefined; constructor( private readonly webtoonParser: WebtoonParserService, @@ -39,21 +40,19 @@ export class DownloadManagerService{ if(this.queue.getElements().find(w => w.title === webtoonOverview.title)) return; // If queue is empty, start download - const empty: boolean = this.queue.isEmpty(); this.queue.enqueue(webtoonOverview); - if(empty) + if(!this.currentDownload) this.startDownload().then(() => console.log("Download finished.")); } async updateAllWebtoons(): Promise{ if(!this.cacheLoaded) throw new HttpException("Cache not loaded.", HttpStatusCode.TooEarly); - const empty: boolean = this.queue.isEmpty(); for(const webtoonLanguageName of await this.webtoonDatabase.getWebtoonList()){ const webtoonLanguage: CachedWebtoonModel[] = this.webtoonParser.webtoons[webtoonLanguageName.language]; this.queue.enqueue(webtoonLanguage.find(w => w.title === webtoonLanguageName.title) as CachedWebtoonModel); } - if(empty) + if(!this.currentDownload) this.startDownload().then(() => console.log("Download finished.")); } @@ -61,20 +60,20 @@ export class DownloadManagerService{ if(!this.cacheLoaded) throw new HttpException("Cache not loaded.", HttpStatusCode.TooEarly); while(!this.queue.isEmpty()){ - const webtoonOverview: CachedWebtoonModel | undefined = this.queue.dequeue(); - if(!webtoonOverview) + this.currentDownload = this.queue.dequeue(); + if(!this.currentDownload) return; - if(!await this.webtoonDatabase.isWebtoonSaved(webtoonOverview.title, webtoonOverview.language)){ - const webtoon: WebtoonModel = await this.webtoonParser.getWebtoonInfos(webtoonOverview); + if(!await this.webtoonDatabase.isWebtoonSaved(this.currentDownload.title, this.currentDownload.language)){ + const webtoon: WebtoonModel = await this.webtoonParser.getWebtoonInfos(this.currentDownload); const webtoonData: WebtoonDataModel = await this.webtoonDownloader.downloadWebtoon(webtoon); await this.webtoonDatabase.saveWebtoon(webtoon, webtoonData); } - const startEpisode: number = await this.webtoonDatabase.getLastSavedEpisodeNumber(webtoonOverview.title, webtoonOverview.language); - const epList: EpisodeModel[] = await this.webtoonParser.getEpisodes(webtoonOverview); + const startEpisode: number = await this.webtoonDatabase.getLastSavedEpisodeNumber(this.currentDownload.title, this.currentDownload.language); + const epList: EpisodeModel[] = await this.webtoonParser.getEpisodes(this.currentDownload); for(let i = startEpisode; i < epList.length; i++){ - const epImageLinks: string[] = await this.webtoonParser.getEpisodeLinks(webtoonOverview, epList[i]); + const epImageLinks: string[] = await this.webtoonParser.getEpisodeLinks(this.currentDownload, epList[i]); const episodeData: EpisodeDataModel = await this.webtoonDownloader.downloadEpisode(epList[i], epImageLinks); - await this.webtoonDatabase.saveEpisode(webtoonOverview, epList[i], episodeData); + await this.webtoonDatabase.saveEpisode(this.currentDownload, epList[i], episodeData); } } } diff --git a/src/modules/webtoon/webtoon/webtoon-database.service.ts b/src/modules/webtoon/webtoon/webtoon-database.service.ts index 26903b5..300fe6a 100644 --- a/src/modules/webtoon/webtoon/webtoon-database.service.ts +++ b/src/modules/webtoon/webtoon/webtoon-database.service.ts @@ -1,5 +1,4 @@ import * as fs from "fs"; -import * as JSZip from "jszip"; import CachedWebtoonModel from "./models/models/cached-webtoon.model"; import EpisodeModel from "./models/models/episode.model"; import EpisodeDataModel from "./models/models/episode-data.model"; @@ -12,6 +11,7 @@ import WebtoonDataModel from "./models/models/webtoon-data.model"; import {Injectable, NotFoundException} from "@nestjs/common"; import {PrismaService} from "../../misc/prisma.service"; import {MiscService} from "../../misc/misc.service"; +import ImageTypes from "./models/enums/image-types"; @Injectable() export class WebtoonDatabaseService{ @@ -24,115 +24,134 @@ export class WebtoonDatabaseService{ async saveEpisode(webtoon: CachedWebtoonModel, episode: EpisodeModel, episodeData: EpisodeDataModel): Promise{ console.log(`Saving episode ${episode.number}...`); - const dbWebtoon = await this.prismaService.webtoon.findFirst({ + const dbWebtoon = await this.prismaService.webtoons.findFirst({ where: { title: webtoon.title } }); if(!dbWebtoon) throw new NotFoundException(`Webtoon ${webtoon.title} not found in database.`); - if(await this.isEpisodeSaved(dbWebtoon.id, episode.number)){ + if(await this.isEpisodeSaved(dbWebtoon.id, episode.number)) return; - } - // Generate sum for each image - const imagesSum: string[] = []; - for(const image of episodeData.images) - imagesSum.push(this.miscService.getSum(image)); - // Saving - this.prismaService.$transaction(async(tx) => { - const imageZipSum: string = await this.saveEpisodeZip(episodeData, imagesSum); - const oldThumbnailSum: string | null = dbWebtoon.episodes_thumbnails_zip_sum; - const thumbnailZipSum: string = await this.saveEpisodeThumbnail(dbWebtoon, episodeData.thumbnail); - // Database saving - await this.saveEpisodeData(tx, episode, episodeData.thumbnail, dbWebtoon.id, imageZipSum, thumbnailZipSum, imagesSum); - // Remove old thumbnail zip - if(oldThumbnailSum) - fs.rmSync(`./images/webtoons/thumbnails/${oldThumbnailSum}.zip`, {force: true}); - console.log(`Episode ${episode.number} saved!`); - }); - } - - private async saveEpisodeZip(episodeData: EpisodeDataModel, imagesSum: string[]): Promise{ - const zip: JSZip = new JSZip(); - for(let i = 0; i < imagesSum.length; i++) - zip.file(`${imagesSum[i]}.webp`, episodeData.images[i]); - const [zipData, zipSum] = await this.miscService.generateZip(zip); - fs.mkdirSync("./images/webtoons/episodes", {recursive: true}); - fs.writeFileSync(`./images/webtoons/episodes/${zipSum}.zip`, zipData); - return zipSum; - } - - private async saveEpisodeData(tx: any, episode: EpisodeModel, episodeThumbnail: Buffer, webtoonId: number, zipSum: string, thumbnailZipSum: string, imagesSum: string[]): Promise{ - await tx.webtoon.update({ - where: { - id: webtoonId - }, - data: { - episodes_thumbnails_zip_sum: thumbnailZipSum - } - }); - const dbEpisode = await tx.episode.create({ - data: { - title: episode.title, - webtoon_id: webtoonId, - number: episode.number, - thumbnail_sum: this.miscService.getSum(episodeThumbnail), - images_zip_sum: zipSum - } - }); - for(let i = 0; i < imagesSum.length; i++){ - await tx.episodeImage.create({ + // Start prisma transaction + await this.prismaService.$transaction(async(tx) => { + const thumbnailType = await tx.imageTypes.findFirst({where: {name: ImageTypes.EPISODE_THUMBNAIL}}); + const imageType = await tx.imageTypes.findFirst({where: {name: ImageTypes.EPISODE_IMAGE}}); + const thumbnailSum: string = this.saveImage(episodeData.thumbnail); + const dbThumbnail = await tx.images.create({ data: { - episode_id: dbEpisode.id, - number: i + 1, - sum: imagesSum[i] + sum: thumbnailSum, + type_id: thumbnailType.id } }); - } - } - - private async saveEpisodeThumbnail(dbWebtoon: any, episodeThumbnail: Buffer): Promise{ - const sum = dbWebtoon.episodes_thumbnails_zip_sum; - if(!sum){ - const zip: JSZip = new JSZip(); - zip.file(`${this.miscService.getSum(episodeThumbnail)}.webp`, episodeThumbnail); - const [zipData, zipSum] = await this.miscService.generateZip(zip); - fs.mkdirSync("./images/webtoons/thumbnails", {recursive: true}); - fs.writeFileSync(`./images/webtoons/thumbnails/${zipSum}.zip`, zipData); - return zipSum; - } - const zip: JSZip = new JSZip(); - const zipData: Buffer = fs.readFileSync(`./images/webtoons/thumbnails/${sum}.zip`); - await zip.loadAsync(zipData); - zip.file(`${this.miscService.getSum(episodeThumbnail)}.webp`, episodeThumbnail); - const [newZipData, newZipSum] = await this.miscService.generateZip(zip); - fs.writeFileSync(`./images/webtoons/thumbnails/${newZipSum}.zip`, newZipData); - return newZipSum; + const dbEpisode = await tx.episodes.create({ + data: { + title: episode.title, + number: episode.number, + webtoon_id: dbWebtoon.id, + thumbnail_id: dbThumbnail.id + } + }); + for(let i = 0; i < episodeData.images.length; i++){ + const imageSum: string = this.saveImage(episodeData.images[i]); + let dbImage = await tx.images.findUnique({ + where: { + sum: imageSum + } + }); + if(!dbImage) + dbImage = await tx.images.create({ + data: { + sum: imageSum, + type_id: imageType.id + } + }); + await tx.episodeImages.create({ + data: { + number: i, + episode_id: dbEpisode.id, + image_id: dbImage.id + } + }); + } + }); } async saveWebtoon(webtoon: WebtoonModel, webtoonData: WebtoonDataModel): Promise{ if(await this.isWebtoonSaved(webtoon.title, webtoon.language)) return; - const thumbnailSum: string = this.saveWebtoonThumbnail(webtoonData.thumbnail); - const backgroundSum: string = this.saveWebtoonThumbnail(webtoonData.backgroundBanner); - const topSum: string = this.saveWebtoonThumbnail(webtoonData.topBanner); - const mobileSum: string = this.saveWebtoonThumbnail(webtoonData.mobileBanner); - await this.prismaService.webtoon.create({ - data: { - title: webtoon.title, - language: webtoon.language, - author: webtoon.author, - episodes_thumbnails_zip_sum: null, - thumbnail_sum: thumbnailSum, - background_banner_sum: backgroundSum, - top_banner_sum: topSum, - mobile_banner_sum: mobileSum + await this.prismaService.$transaction(async(tx) => { + const genreIds: number[] = []; + for(const genre of webtoon.genres){ + const dbGenre = await tx.genres.findFirst({ + where: { + name: genre + } + }); + if(!dbGenre) + throw new NotFoundException(`Genre ${genre} not found in database.`); + genreIds.push(dbGenre.id); + } + + const thumbnailType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_THUMBNAIL}}); + const backgroundType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_BACKGROUND_BANNER}}); + const topType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_TOP_BANNER}}); + const mobileType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_MOBILE_BANNER}}); + + const thumbnailSum: string = this.saveImage(webtoonData.thumbnail); + const backgroundSum: string = this.saveImage(webtoonData.backgroundBanner); + const topSum: string = this.saveImage(webtoonData.topBanner); + const mobileSum: string = this.saveImage(webtoonData.mobileBanner); + + const dbThumbnail = await tx.images.create({ + data: { + sum: thumbnailSum, + type_id: thumbnailType.id + } + }); + const dbBackground = await tx.images.create({ + data: { + sum: backgroundSum, + type_id: backgroundType.id + } + }); + const dbTop = await tx.images.create({ + data: { + sum: topSum, + type_id: topType.id + } + }); + const dbMobile = await tx.images.create({ + data: { + sum: mobileSum, + type_id: mobileType.id + } + }); + const dbWebtoon = await tx.webtoons.create({ + data: { + title: webtoon.title, + language: webtoon.language, + author: webtoon.author, + thumbnail_id: dbThumbnail.id, + background_banner_id: dbBackground.id, + top_banner_id: dbTop.id, + mobile_banner_id: dbMobile.id + } + }); + + for(const genreId of genreIds){ + await tx.webtoonGenres.create({ + data: { + webtoon_id: dbWebtoon.id, + genre_id: genreId + } + }); } }); } async isWebtoonSaved(webtoonTitle: string, language: string): Promise{ - return !!await this.prismaService.webtoon.findFirst({ + return !!await this.prismaService.webtoons.findFirst({ where: { title: webtoonTitle, language: language @@ -141,7 +160,7 @@ export class WebtoonDatabaseService{ } async isEpisodeSaved(webtoonId: number, episodeNumber: number): Promise{ - return !!await this.prismaService.episode.findFirst({ + return !!await this.prismaService.episodes.findFirst({ where: { webtoon_id: webtoonId, number: episodeNumber @@ -150,7 +169,7 @@ export class WebtoonDatabaseService{ } async getLastSavedEpisodeNumber(webtoonTitle: string, language: string): Promise{ - const dbWebtoon = await this.prismaService.webtoon.findFirst({ + const dbWebtoon = await this.prismaService.webtoons.findFirst({ where: { title: webtoonTitle, language: language @@ -158,7 +177,7 @@ export class WebtoonDatabaseService{ }); if(!dbWebtoon) throw new NotFoundException(`Webtoon ${webtoonTitle} not found in database.`); - const lastEpisode = await this.prismaService.episode.findFirst({ + const lastEpisode = await this.prismaService.episodes.findFirst({ where: { webtoon_id: dbWebtoon.id }, @@ -170,7 +189,7 @@ export class WebtoonDatabaseService{ } async getWebtoonList(): Promise{ - return this.prismaService.webtoon.findMany({ + return this.prismaService.webtoons.findMany({ select: { title: true, language: true @@ -178,91 +197,105 @@ export class WebtoonDatabaseService{ }); } - private saveWebtoonThumbnail(data: Buffer): string{ - const sum = this.miscService.getSum(data); - fs.mkdirSync("./images/thumbnails", {recursive: true}); - fs.writeFileSync(`./images/thumbnails/${sum}.webp`, data); - return sum; - } - async getWebtoons(): Promise{ - const webtoons = await this.prismaService.webtoon.findMany({ - select: { - id: true, - title: true, - author: true, - thumbnail_sum: true, - language: true + const webtoons = await this.prismaService.webtoons.findMany({ + include: { + thumbnail: true } }); const response: WebtoonResponse[] = []; for(const webtoon of webtoons){ - const thumbnail: string = this.miscService.bufferToDataURL(fs.readFileSync(`./images/thumbnails/${webtoon.thumbnail_sum}.webp`)); + const thumbnail: string = this.miscService.bufferToDataURL(this.loadImage(webtoon.thumbnail.sum)); response.push(new WebtoonResponse(webtoon.id, webtoon.title, webtoon.language, thumbnail, webtoon.author)); } return response; } async getEpisodeInfos(webtoonId: number): Promise{ - const dbWebtoon = await this.prismaService.webtoon.findFirst({ + const dbWebtoon = await this.prismaService.webtoons.findFirst({ where: { id: webtoonId }, - select: { - title: true, - author: true, - background_banner_sum: true, - top_banner_sum: true, - mobile_banner_sum: true, - episodes_thumbnails_zip_sum: true + include: { + thumbnail: true, + background_banner: true, + top_banner: true, + mobile_banner: true, } }); if(!dbWebtoon) throw new NotFoundException(`Webtoon with id ${webtoonId} not found in database.`); - const episodes = await this.prismaService.episode.findMany({ + const episodes = await this.prismaService.episodes.findMany({ where: { webtoon_id: webtoonId + }, + include: { + thumbnail: true + }, + orderBy: { + number: "asc" } }); const episodeLines: EpisodeLineModel[] = []; - const thumbnails: JSZip = await this.miscService.loadZip(`./images/webtoons/thumbnails/${dbWebtoon.episodes_thumbnails_zip_sum}.zip`); for(const episode of episodes){ - const thumbnailData: Buffer | undefined = await thumbnails.file(`${episode.thumbnail_sum}.webp`)?.async("nodebuffer"); + const thumbnailData: Buffer | undefined = this.loadImage(episode.thumbnail.sum); if(!thumbnailData) - throw new NotFoundException(`Thumbnail not found in zip for episode ${episode.number}`); + throw new NotFoundException(`Thumbnail not found for episode ${episode.number}`); const thumbnail: string = this.miscService.bufferToDataURL(thumbnailData); episodeLines.push(new EpisodeLineModel(episode.id, episode.title, episode.number, thumbnail)); } return { episodes: episodeLines, - backgroundBanner: this.miscService.bufferToDataURL(fs.readFileSync(`./images/thumbnails/${dbWebtoon.background_banner_sum}.webp`)), - topBanner: this.miscService.bufferToDataURL(fs.readFileSync(`./images/thumbnails/${dbWebtoon.top_banner_sum}.webp`)), - mobileBanner: this.miscService.bufferToDataURL(fs.readFileSync(`./images/thumbnails/${dbWebtoon.mobile_banner_sum}.webp`)), + backgroundBanner: this.miscService.bufferToDataURL(this.loadImage(dbWebtoon.background_banner.sum)), + topBanner: this.miscService.bufferToDataURL(this.loadImage(dbWebtoon.top_banner.sum)), + mobileBanner: this.miscService.bufferToDataURL(this.loadImage(dbWebtoon.mobile_banner.sum)), title: dbWebtoon.title, } as EpisodesResponse; } async getEpisodeImages(episodeId: number): Promise{ - const episode = await this.prismaService.episode.findFirst({ + const episode = await this.prismaService.episodes.findFirst({ where: { id: episodeId, } }); if(!episode) throw new NotFoundException(`Episode ${episodeId} not found in database.`); - const images = await this.prismaService.episodeImage.findMany({ + const images = await this.prismaService.episodeImages.findMany({ where: { episode_id: episode.id + }, + include: { + image: true + }, + orderBy: { + number: "asc" } }); - const zip: JSZip = await this.miscService.loadZip(`./images/webtoons/episodes/${episode.images_zip_sum}.zip`); const episodeImages: string[] = []; for(const image of images){ - const imageData: Buffer | undefined = await zip.file(`${image.sum}.webp`)?.async("nodebuffer"); + const imageData: Buffer | undefined = this.loadImage(image.image.sum); if(!imageData) - throw new NotFoundException(`Image not found in zip for episode ${episodeId}`); + throw new NotFoundException(`Image not found for episode ${episodeId}`); episodeImages.push(this.miscService.bufferToDataURL(imageData)); } return new EpisodeResponse(episode.title, episodeImages); } + + private saveImage(image: Buffer): string{ + if(!fs.existsSync("./images")) + fs.mkdirSync("./images"); + const imageSum: string = this.miscService.getSum(image); + const folder = imageSum.substring(0, 2); + const path = `./images/${folder}`; + if(!fs.existsSync(path)) + fs.mkdirSync(path); + fs.writeFileSync(`${path}/${imageSum}.webp`, image); + return imageSum; + } + + private loadImage(imageSum: string): Buffer{ + const folder = imageSum.substring(0, 2); + return fs.readFileSync(`./images/${folder}/${imageSum}.webp`); + } } diff --git a/src/modules/webtoon/webtoon/webtoon-downloader.service.ts b/src/modules/webtoon/webtoon/webtoon-downloader.service.ts index 583ee90..779dbf2 100644 --- a/src/modules/webtoon/webtoon/webtoon-downloader.service.ts +++ b/src/modules/webtoon/webtoon/webtoon-downloader.service.ts @@ -15,19 +15,16 @@ export class WebtoonDownloaderService{ async downloadEpisode(episode: EpisodeModel, imageUrls: string[]): Promise{ console.log(`Downloading episode ${episode.number}...`); const thumbnail: Buffer = await this.miscService.downloadImage(episode.thumbnail); - const images: Buffer[] = []; + const conversionPromises: Promise[] = []; for (let i = 0; i < imageUrls.length; i++){ console.log(`Downloading image ${i + 1}/${imageUrls.length}...`); const url = imageUrls[i]; const image = await this.miscService.downloadImage(url, episode.link); - images.push(image); + conversionPromises.push(this.miscService.convertImageToWebp(image)); await new Promise(resolve => setTimeout(resolve, this.miscService.randomInt(50, 200))); } // Convert all images to webp console.log("Converting images to webp..."); - const conversionPromises: Promise[] = []; - for (let i = 0; i < images.length; i++) - conversionPromises.push(this.miscService.convertImageToWebp(images[i])); const convertedImages: Buffer[] = await Promise.all(conversionPromises); console.log(`Download complete for episode ${episode.number}!`); return { From f52dbc87ec85ded96bf13477bd192420a4d08aae Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 19:29:24 +0200 Subject: [PATCH 4/7] :arrow_up: Update all dependencies --- package.json | 10 +- pnpm-lock.yaml | 436 ++++++++++++++++++++++++++----------------------- 2 files changed, 241 insertions(+), 205 deletions(-) diff --git a/package.json b/package.json index 1863f15..d71fba2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@fastify/static": "^7.0.3", + "@fastify/static": "^7.0.4", "@nestjs/common": "^10.3.8", "@nestjs/config": "^3.2.2", "@nestjs/core": "^10.3.8", @@ -30,10 +30,10 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", - "fastify": "^4.26.2", + "fastify": "^4.27.0", "jsdom": "^24.0.0", "jszip": "^3.10.1", - "prisma": "^5.13.0", + "prisma": "^5.14.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", "sharp": "^0.33.3", @@ -45,9 +45,9 @@ "@nestjs/testing": "^10.3.8", "@types/jest": "^29.5.12", "@types/jsdom": "^21.1.6", - "@types/node": "^20.12.10", + "@types/node": "^20.12.12", "@types/supertest": "^6.0.2", - "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/parser": "^6.21.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a2a14a..e8875a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@fastify/static': - specifier: ^7.0.3 - version: 7.0.3 + specifier: ^7.0.4 + version: 7.0.4 '@nestjs/common': specifier: ^10.3.8 version: 10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -19,13 +19,13 @@ dependencies: version: 10.3.8(@nestjs/common@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/platform-fastify': specifier: ^10.3.8 - version: 10.3.8(@fastify/static@7.0.3)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8) + version: 10.3.8(@fastify/static@7.0.4)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8) '@nestjs/swagger': specifier: ^7.3.1 - version: 7.3.1(@fastify/static@7.0.3)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 7.3.1(@fastify/static@7.0.4)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@prisma/client': specifier: 5.13.0 - version: 5.13.0(prisma@5.13.0) + version: 5.13.0(prisma@5.14.0) axios: specifier: ^1.6.8 version: 1.6.8 @@ -39,8 +39,8 @@ dependencies: specifier: ^16.4.5 version: 16.4.5 fastify: - specifier: ^4.26.2 - version: 4.26.2 + specifier: ^4.27.0 + version: 4.27.0 jsdom: specifier: ^24.0.0 version: 24.0.0 @@ -48,8 +48,8 @@ dependencies: specifier: ^3.10.1 version: 3.10.1 prisma: - specifier: ^5.13.0 - version: 5.13.0 + specifier: ^5.14.0 + version: 5.14.0 reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -80,14 +80,14 @@ devDependencies: specifier: ^21.1.6 version: 21.1.6 '@types/node': - specifier: ^20.12.10 - version: 20.12.10 + specifier: ^20.12.12 + version: 20.12.12 '@types/supertest': specifier: ^6.0.2 version: 6.0.2 '@typescript-eslint/eslint-plugin': - specifier: ^7.0.0 - version: 7.0.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.5) + specifier: ^7.9.0 + version: 7.9.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': specifier: ^6.21.0 version: 6.21.0(eslint@8.57.0)(typescript@5.4.5) @@ -102,7 +102,7 @@ devDependencies: version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + version: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -117,7 +117,7 @@ devDependencies: version: 9.5.1(typescript@5.4.5)(webpack@5.91.0) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.12.10)(typescript@5.4.5) + version: 10.9.2(@types/node@20.12.12)(typescript@5.4.5) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -186,7 +186,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.24.5 - picocolors: 1.0.0 + picocolors: 1.0.1 dev: true /@babel/compat-data@7.24.4: @@ -331,7 +331,7 @@ packages: '@babel/helper-validator-identifier': 7.24.5 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.0 + picocolors: 1.0.1 dev: true /@babel/parser@7.24.5: @@ -597,7 +597,7 @@ packages: /@fastify/fast-json-stringify-compiler@4.3.0: resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} dependencies: - fast-json-stringify: 5.15.0 + fast-json-stringify: 5.15.1 dev: false /@fastify/formbody@7.4.0: @@ -632,15 +632,15 @@ packages: mime: 3.0.0 dev: false - /@fastify/static@7.0.3: - resolution: {integrity: sha512-2tmTdF+uFCykasutaO6k4/wOt7eXyi7m3dGuCPo5micXzv0qt6ttb/nWnDYL/BlXjYGfp1JI4a1gyluTIylvQA==} + /@fastify/static@7.0.4: + resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==} dependencies: '@fastify/accept-negotiator': 1.1.0 '@fastify/send': 2.1.0 content-disposition: 0.5.4 fastify-plugin: 4.5.1 fastq: 1.17.1 - glob: 10.3.12 + glob: 10.3.15 dev: false /@humanwhocodes/config-array@0.11.14: @@ -883,7 +883,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -904,14 +904,14 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -939,7 +939,7 @@ packages: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 jest-mock: 29.7.0 dev: true @@ -966,7 +966,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.10 + '@types/node': 20.12.12 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -999,7 +999,7 @@ packages: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.12.10 + '@types/node': 20.12.12 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -1087,7 +1087,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.10 + '@types/node': 20.12.12 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -1281,7 +1281,7 @@ packages: reflect-metadata: 0.2.2 dev: false - /@nestjs/platform-fastify@10.3.8(@fastify/static@7.0.3)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8): + /@nestjs/platform-fastify@10.3.8(@fastify/static@7.0.4)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8): resolution: {integrity: sha512-gWGskBuZRB4LTQJCJWWUZvYwiF05zAJdq0X5oi6SmSZHP6bPIY+sQar+IwPFTsDtCM0Qk9nFm9UUuQdgLZTrTQ==} peerDependencies: '@fastify/static': ^6.0.0 || ^7.0.0 @@ -1297,7 +1297,7 @@ packages: '@fastify/cors': 9.0.1 '@fastify/formbody': 7.4.0 '@fastify/middie': 8.3.0 - '@fastify/static': 7.0.3 + '@fastify/static': 7.0.4 '@nestjs/common': 10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.3.8(@nestjs/common@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) fastify: 4.26.2 @@ -1338,7 +1338,7 @@ packages: - chokidar dev: true - /@nestjs/swagger@7.3.1(@fastify/static@7.0.3)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2): + /@nestjs/swagger@7.3.1(@fastify/static@7.0.4)(@nestjs/common@10.3.8)(@nestjs/core@10.3.8)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2): resolution: {integrity: sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==} peerDependencies: '@fastify/static': ^6.0.0 || ^7.0.0 @@ -1355,7 +1355,7 @@ packages: class-validator: optional: true dependencies: - '@fastify/static': 7.0.3 + '@fastify/static': 7.0.4 '@microsoft/tsdoc': 0.14.2 '@nestjs/common': 10.3.8(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.3.8(@nestjs/common@10.3.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -1430,7 +1430,7 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dev: true - /@prisma/client@5.13.0(prisma@5.13.0): + /@prisma/client@5.13.0(prisma@5.14.0): resolution: {integrity: sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==} engines: {node: '>=16.13'} requiresBuild: true @@ -1440,39 +1440,39 @@ packages: prisma: optional: true dependencies: - prisma: 5.13.0 + prisma: 5.14.0 dev: false - /@prisma/debug@5.13.0: - resolution: {integrity: sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==} + /@prisma/debug@5.14.0: + resolution: {integrity: sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w==} dev: false - /@prisma/engines-version@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b: - resolution: {integrity: sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==} + /@prisma/engines-version@5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48: + resolution: {integrity: sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA==} dev: false - /@prisma/engines@5.13.0: - resolution: {integrity: sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==} + /@prisma/engines@5.14.0: + resolution: {integrity: sha512-lgxkKZ6IEygVcw6IZZUlPIfLQ9hjSYAtHjZ5r64sCLDgVzsPFCi2XBBJgzPMkOQ5RHzUD4E/dVdpn9+ez8tk1A==} requiresBuild: true dependencies: - '@prisma/debug': 5.13.0 - '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b - '@prisma/fetch-engine': 5.13.0 - '@prisma/get-platform': 5.13.0 + '@prisma/debug': 5.14.0 + '@prisma/engines-version': 5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48 + '@prisma/fetch-engine': 5.14.0 + '@prisma/get-platform': 5.14.0 dev: false - /@prisma/fetch-engine@5.13.0: - resolution: {integrity: sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==} + /@prisma/fetch-engine@5.14.0: + resolution: {integrity: sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==} dependencies: - '@prisma/debug': 5.13.0 - '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b - '@prisma/get-platform': 5.13.0 + '@prisma/debug': 5.14.0 + '@prisma/engines-version': 5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48 + '@prisma/get-platform': 5.14.0 dev: false - /@prisma/get-platform@5.13.0: - resolution: {integrity: sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==} + /@prisma/get-platform@5.14.0: + resolution: {integrity: sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==} dependencies: - '@prisma/debug': 5.13.0 + '@prisma/debug': 5.14.0 dev: false /@sinclair/typebox@0.27.8: @@ -1561,7 +1561,7 @@ packages: /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.12.10 + '@types/node': 20.12.12 dev: true /@types/istanbul-lib-coverage@2.0.6: @@ -1590,7 +1590,7 @@ packages: /@types/jsdom@21.1.6: resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==} dependencies: - '@types/node': 20.12.10 + '@types/node': 20.12.12 '@types/tough-cookie': 4.0.5 parse5: 7.1.2 dev: true @@ -1603,16 +1603,12 @@ packages: resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} dev: true - /@types/node@20.12.10: - resolution: {integrity: sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==} + /@types/node@20.12.12: + resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==} dependencies: undici-types: 5.26.5 dev: true - /@types/semver@7.5.8: - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - dev: true - /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} dev: true @@ -1622,7 +1618,7 @@ packages: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 20.12.10 + '@types/node': 20.12.12 dev: true /@types/supertest@6.0.2: @@ -1636,8 +1632,8 @@ packages: resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} dev: true - /@types/validator@13.11.9: - resolution: {integrity: sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==} + /@types/validator@13.11.10: + resolution: {integrity: sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==} /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -1649,11 +1645,11 @@ packages: '@types/yargs-parser': 21.0.3 dev: true - /@typescript-eslint/eslint-plugin@7.0.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-M72SJ0DkcQVmmsbqlzc6EJgb/3Oz2Wdm6AyESB4YkGgCxP8u5jt5jn4/OBMPK3HLOxcttZq5xbBBU7e2By4SZQ==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/eslint-plugin@7.9.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + '@typescript-eslint/parser': ^7.0.0 eslint: ^8.56.0 typescript: '*' peerDependenciesMeta: @@ -1662,16 +1658,14 @@ packages: dependencies: '@eslint-community/regexpp': 4.10.0 '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.0.0 - '@typescript-eslint/type-utils': 7.0.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.0.0(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.0.0 - debug: 4.3.4 + '@typescript-eslint/scope-manager': 7.9.0 + '@typescript-eslint/type-utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.9.0 eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - semver: 7.6.0 ts-api-utils: 1.3.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: @@ -1707,17 +1701,17 @@ packages: '@typescript-eslint/visitor-keys': 6.21.0 dev: true - /@typescript-eslint/scope-manager@7.0.0: - resolution: {integrity: sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/scope-manager@7.9.0: + resolution: {integrity: sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==} + engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 7.0.0 - '@typescript-eslint/visitor-keys': 7.0.0 + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/visitor-keys': 7.9.0 dev: true - /@typescript-eslint/type-utils@7.0.0(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-FIM8HPxj1P2G7qfrpiXvbHeHypgo2mFpFGoh5I73ZlqmJOsloSa1x0ZyXCer43++P1doxCgNqIOLqmZR6SOT8g==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/type-utils@7.9.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 typescript: '*' @@ -1725,8 +1719,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.0.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.0.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.9.0(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) @@ -1740,9 +1734,9 @@ packages: engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/types@7.0.0: - resolution: {integrity: sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/types@7.9.0: + resolution: {integrity: sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==} + engines: {node: ^18.18.0 || >=20.0.0} dev: true /@typescript-eslint/typescript-estree@6.21.0(typescript@5.4.5): @@ -1760,49 +1754,46 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.0 + semver: 7.6.2 ts-api-utils: 1.3.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/typescript-estree@7.0.0(typescript@5.4.5): - resolution: {integrity: sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/typescript-estree@7.9.0(typescript@5.4.5): + resolution: {integrity: sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 7.0.0 - '@typescript-eslint/visitor-keys': 7.0.0 + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/visitor-keys': 7.9.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.0 + minimatch: 9.0.4 + semver: 7.6.2 ts-api-utils: 1.3.0(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@7.0.0(eslint@8.57.0)(typescript@5.4.5): - resolution: {integrity: sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/utils@7.9.0(eslint@8.57.0)(typescript@5.4.5): + resolution: {integrity: sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.0.0 - '@typescript-eslint/types': 7.0.0 - '@typescript-eslint/typescript-estree': 7.0.0(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.9.0 + '@typescript-eslint/types': 7.9.0 + '@typescript-eslint/typescript-estree': 7.9.0(typescript@5.4.5) eslint: 8.57.0 - semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript @@ -1816,11 +1807,11 @@ packages: eslint-visitor-keys: 3.4.3 dev: true - /@typescript-eslint/visitor-keys@7.0.0: - resolution: {integrity: sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==} - engines: {node: ^16.0.0 || >=18.0.0} + /@typescript-eslint/visitor-keys@7.9.0: + resolution: {integrity: sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==} + engines: {node: ^18.18.0 || >=20.0.0} dependencies: - '@typescript-eslint/types': 7.0.0 + '@typescript-eslint/types': 7.9.0 eslint-visitor-keys: 3.4.3 dev: true @@ -2281,10 +2272,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001615 - electron-to-chromium: 1.4.756 + caniuse-lite: 1.0.30001618 + electron-to-chromium: 1.4.769 node-releases: 2.0.14 - update-browserslist-db: 1.0.15(browserslist@4.23.0) + update-browserslist-db: 1.0.16(browserslist@4.23.0) dev: true /bs-logger@0.2.6: @@ -2344,8 +2335,8 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001615: - resolution: {integrity: sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==} + /caniuse-lite@1.0.30001618: + resolution: {integrity: sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==} dev: true /chalk@2.4.2: @@ -2413,9 +2404,9 @@ packages: /class-validator@0.14.1: resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} dependencies: - '@types/validator': 13.11.9 - libphonenumber-js: 1.10.62 - validator: 13.11.0 + '@types/validator': 13.11.10 + libphonenumber-js: 1.11.1 + validator: 13.12.0 /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} @@ -2581,7 +2572,7 @@ packages: typescript: 5.3.3 dev: true - /create-jest@29.7.0(@types/node@20.12.10)(ts-node@10.9.2): + /create-jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -2590,7 +2581,7 @@ packages: chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -2738,8 +2729,8 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /electron-to-chromium@1.4.756: - resolution: {integrity: sha512-RJKZ9+vEBMeiPAvKNWyZjuYyUqMndcP1f335oHqn3BEQbs2NFtVrnK5+6Xg5wSM9TknNNpWghGDUCKGYF+xWXw==} + /electron-to-chromium@1.4.769: + resolution: {integrity: sha512-bZu7p623NEA2rHTc9K1vykl57ektSPQYFFqQir8BOYf6EKOB+yIsbFB9Kpm7Cgt6tsLr9sRkqfqSZUw7LP1XxQ==} dev: true /emittery@0.13.1: @@ -2753,8 +2744,8 @@ packages: /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - /enhanced-resolve@5.16.0: - resolution: {integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==} + /enhanced-resolve@5.16.1: + resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 @@ -3037,8 +3028,8 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fast-json-stringify@5.15.0: - resolution: {integrity: sha512-BUEAAyDKb64u+kmkINYfXUUiKjBKerSmVu/dzotfaWSHBxR44JFrOZgkhMO6VxDhDfiuAoi8mx4drd5nvNdA4Q==} + /fast-json-stringify@5.15.1: + resolution: {integrity: sha512-JopGtkvvguRqrS4gHXSSA2jf4pDgOZkeBAkLO1LbzOpiOMo7/kugoR+KiWifpLpluaVeYDkAuxCJOj4Gyc6L9A==} dependencies: '@fastify/merge-json-schemas': 0.1.1 ajv: 8.13.0 @@ -3084,7 +3075,7 @@ packages: abstract-logging: 2.0.1 avvio: 8.3.0 fast-content-type-parse: 1.1.0 - fast-json-stringify: 5.15.0 + fast-json-stringify: 5.15.1 find-my-way: 8.2.0 light-my-request: 5.13.0 pino: 8.21.0 @@ -3092,7 +3083,30 @@ packages: proxy-addr: 2.0.7 rfdc: 1.3.1 secure-json-parse: 2.7.0 - semver: 7.6.0 + semver: 7.6.2 + toad-cache: 3.7.0 + transitivePeerDependencies: + - supports-color + dev: false + + /fastify@4.27.0: + resolution: {integrity: sha512-ci9IXzbigB8dyi0mSy3faa3Bsj0xWAPb9JeT4KRzubdSb6pNhcADRUaXCBml6V1Ss/a05kbtQls5LBmhHydoTA==} + dependencies: + '@fastify/ajv-compiler': 3.5.0 + '@fastify/error': 3.4.1 + '@fastify/fast-json-stringify-compiler': 4.3.0 + abstract-logging: 2.0.1 + avvio: 8.3.0 + fast-content-type-parse: 1.1.0 + fast-json-stringify: 5.15.1 + find-my-way: 8.2.0 + light-my-request: 5.13.0 + pino: 9.1.0 + process-warning: 3.0.0 + proxy-addr: 2.0.7 + rfdc: 1.3.1 + secure-json-parse: 2.7.0 + semver: 7.6.2 toad-cache: 3.7.0 transitivePeerDependencies: - supports-color @@ -3210,7 +3224,7 @@ packages: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.6.0 + semver: 7.6.2 tapable: 2.2.1 typescript: 5.3.3 webpack: 5.90.1 @@ -3324,20 +3338,20 @@ packages: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.4 - minipass: 7.1.0 - path-scurry: 1.10.2 + minipass: 7.1.1 + path-scurry: 1.11.1 dev: true - /glob@10.3.12: - resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} - engines: {node: '>=16 || 14 >=14.17'} + /glob@10.3.15: + resolution: {integrity: sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==} + engines: {node: '>=16 || 14 >=14.18'} hasBin: true dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.4 - minipass: 7.1.0 - path-scurry: 1.10.2 + minipass: 7.1.1 + path-scurry: 1.11.1 dev: false /glob@7.2.3: @@ -3358,7 +3372,7 @@ packages: fs.realpath: 1.0.0 minimatch: 8.0.4 minipass: 4.2.8 - path-scurry: 1.10.2 + path-scurry: 1.11.1 dev: true /globals@11.12.0: @@ -3707,7 +3721,7 @@ packages: '@babel/parser': 7.24.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.6.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color dev: true @@ -3769,7 +3783,7 @@ packages: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -3790,7 +3804,7 @@ packages: - supports-color dev: true - /jest-cli@29.7.0(@types/node@20.12.10)(ts-node@10.9.2): + /jest-cli@29.7.0(@types/node@20.12.12)(ts-node@10.9.2): resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -3804,10 +3818,10 @@ packages: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + create-jest: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + jest-config: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -3818,7 +3832,7 @@ packages: - ts-node dev: true - /jest-config@29.7.0(@types/node@20.12.10)(ts-node@10.9.2): + /jest-config@29.7.0(@types/node@20.12.12)(ts-node@10.9.2): resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -3833,7 +3847,7 @@ packages: '@babel/core': 7.24.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 babel-jest: 29.7.0(@babel/core@7.24.5) chalk: 4.1.2 ci-info: 3.9.0 @@ -3853,7 +3867,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.2(@types/node@20.12.10)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@20.12.12)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -3894,7 +3908,7 @@ packages: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 jest-mock: 29.7.0 jest-util: 29.7.0 dev: true @@ -3910,7 +3924,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.12.10 + '@types/node': 20.12.12 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -3961,7 +3975,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 jest-util: 29.7.0 dev: true @@ -4016,7 +4030,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -4047,7 +4061,7 @@ packages: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 chalk: 4.1.2 cjs-module-lexer: 1.3.1 collect-v8-coverage: 1.0.2 @@ -4089,7 +4103,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color dev: true @@ -4099,7 +4113,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -4124,7 +4138,7 @@ packages: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.10 + '@types/node': 20.12.12 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -4136,7 +4150,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.12.10 + '@types/node': 20.12.12 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -4145,13 +4159,13 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.12.10 + '@types/node': 20.12.12 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest@29.7.0(@types/node@20.12.10)(ts-node@10.9.2): + /jest@29.7.0(@types/node@20.12.12)(ts-node@10.9.2): resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -4164,7 +4178,7 @@ packages: '@jest/core': 29.7.0(ts-node@10.9.2) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + jest-cli: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -4207,7 +4221,7 @@ packages: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.9 + nwsapi: 2.2.10 parse5: 7.1.2 rrweb-cssom: 0.6.0 saxes: 6.0.0 @@ -4312,8 +4326,8 @@ packages: type-check: 0.4.0 dev: true - /libphonenumber-js@1.10.62: - resolution: {integrity: sha512-zbLf2yhgrs+TN4rHT7ral38WQEXjS4TWKp8QD3P5fJmHh3lCtTiPyr8XDPGaA7T41HDz2qxR7x3uwr+aNbShJQ==} + /libphonenumber-js@1.11.1: + resolution: {integrity: sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw==} /lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -4381,12 +4395,6 @@ packages: yallist: 3.1.1 dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - /magic-string@0.30.5: resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} @@ -4398,7 +4406,7 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} dependencies: - semver: 7.6.0 + semver: 7.6.2 dev: true /make-error@1.3.6: @@ -4502,8 +4510,8 @@ packages: engines: {node: '>=8'} dev: true - /minipass@7.1.0: - resolution: {integrity: sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==} + /minipass@7.1.1: + resolution: {integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==} engines: {node: '>=16 || 14 >=14.17'} /mnemonist@0.39.6: @@ -4573,8 +4581,8 @@ packages: path-key: 3.1.1 dev: true - /nwsapi@2.2.9: - resolution: {integrity: sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==} + /nwsapi@2.2.10: + resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} dev: false /object-inspect@1.13.1: @@ -4712,12 +4720,12 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-scurry@1.10.2: - resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} - engines: {node: '>=16 || 14 >=14.17'} + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.2.2 - minipass: 7.1.0 + minipass: 7.1.1 /path-to-regexp@3.2.0: resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} @@ -4731,8 +4739,8 @@ packages: engines: {node: '>=8'} dev: true - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} dev: true /picomatch@2.3.1: @@ -4756,6 +4764,10 @@ packages: resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} dev: false + /pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + dev: false + /pino@8.21.0: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} hasBin: true @@ -4773,6 +4785,23 @@ packages: thread-stream: 2.7.0 dev: false + /pino@9.1.0: + resolution: {integrity: sha512-qUcgfrlyOtjwhNLdbhoL7NR4NkHjzykAPw0V2QLFbvu/zss29h4NkRnibyFzBrNCbzCOY3WZ9hhKSwfOkNggYA==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pino-std-serializers: 7.0.0 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 4.0.1 + thread-stream: 3.0.0 + dev: false + /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -4817,13 +4846,13 @@ packages: react-is: 18.3.1 dev: true - /prisma@5.13.0: - resolution: {integrity: sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==} + /prisma@5.14.0: + resolution: {integrity: sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q==} engines: {node: '>=16.13'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 5.13.0 + '@prisma/engines': 5.14.0 dev: false /process-nextick-args@2.0.1: @@ -5110,12 +5139,10 @@ packages: hasBin: true dev: true - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + /semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} hasBin: true - dependencies: - lru-cache: 6.0.0 /serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -5154,7 +5181,7 @@ packages: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.0 + semver: 7.6.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.3 '@img/sharp-darwin-x64': 0.33.3 @@ -5236,6 +5263,12 @@ packages: atomic-sleep: 1.0.0 dev: false + /sonic-boom@4.0.1: + resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -5362,7 +5395,7 @@ packages: methods: 1.1.2 mime: 2.6.0 qs: 6.12.1 - semver: 7.6.0 + semver: 7.6.2 transitivePeerDependencies: - supports-color dev: true @@ -5510,6 +5543,12 @@ packages: real-require: 0.2.0 dev: false + /thread-stream@3.0.0: + resolution: {integrity: sha512-oUIFjxaUT6knhPtWgDMc29zF1FcSl0yXpapkyrQrCGEfYA2HUZXCilUtKyYIv6HkCyqSPAMkY+EG0GbyIrNDQg==} + dependencies: + real-require: 0.2.0 + dev: false + /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true @@ -5605,12 +5644,12 @@ packages: '@babel/core': 7.24.5 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.12.10)(ts-node@10.9.2) + jest: 29.7.0(@types/node@20.12.12)(ts-node@10.9.2) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.6.0 + semver: 7.6.2 typescript: 5.4.5 yargs-parser: 21.1.1 dev: true @@ -5623,15 +5662,15 @@ packages: webpack: ^5.0.0 dependencies: chalk: 4.1.2 - enhanced-resolve: 5.16.0 + enhanced-resolve: 5.16.1 micromatch: 4.0.5 - semver: 7.6.0 + semver: 7.6.2 source-map: 0.7.4 typescript: 5.4.5 webpack: 5.91.0 dev: true - /ts-node@10.9.2(@types/node@20.12.10)(typescript@5.4.5): + /ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: @@ -5650,7 +5689,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.12.10 + '@types/node': 20.12.12 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -5667,7 +5706,7 @@ packages: engines: {node: '>=10.13.0'} dependencies: chalk: 4.1.2 - enhanced-resolve: 5.16.0 + enhanced-resolve: 5.16.1 tsconfig-paths: 4.2.0 dev: true @@ -5737,15 +5776,15 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /update-browserslist-db@1.0.15(browserslist@4.23.0): - resolution: {integrity: sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==} + /update-browserslist-db@1.0.16(browserslist@4.23.0): + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: browserslist: 4.23.0 escalade: 3.1.2 - picocolors: 1.0.0 + picocolors: 1.0.1 dev: true /uri-js@4.4.1: @@ -5781,8 +5820,8 @@ packages: convert-source-map: 2.0.0 dev: true - /validator@13.11.0: - resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} + /validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} /w3c-xmlserializer@5.0.0: @@ -5849,7 +5888,7 @@ packages: acorn-import-assertions: 1.9.0(acorn@8.11.3) browserslist: 4.23.0 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.16.0 + enhanced-resolve: 5.16.1 es-module-lexer: 1.5.2 eslint-scope: 5.1.1 events: 3.3.0 @@ -5889,7 +5928,7 @@ packages: acorn-import-assertions: 1.9.0(acorn@8.11.3) browserslist: 4.23.0 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.16.0 + enhanced-resolve: 5.16.1 es-module-lexer: 1.5.2 eslint-scope: 5.1.1 events: 3.3.0 @@ -6016,9 +6055,6 @@ packages: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: true - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} From 4dcc84acd93caae0b273fb857281be457c514057 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 20:08:08 +0200 Subject: [PATCH 5/7] :zap: Improve database requests --- .../webtoon/webtoon-database.service.ts | 65 +++++++++++++------ 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/src/modules/webtoon/webtoon/webtoon-database.service.ts b/src/modules/webtoon/webtoon/webtoon-database.service.ts index 300fe6a..0d2e9f5 100644 --- a/src/modules/webtoon/webtoon/webtoon-database.service.ts +++ b/src/modules/webtoon/webtoon/webtoon-database.service.ts @@ -35,8 +35,19 @@ export class WebtoonDatabaseService{ return; // Start prisma transaction await this.prismaService.$transaction(async(tx) => { - const thumbnailType = await tx.imageTypes.findFirst({where: {name: ImageTypes.EPISODE_THUMBNAIL}}); - const imageType = await tx.imageTypes.findFirst({where: {name: ImageTypes.EPISODE_IMAGE}}); + const imageTypes = await tx.imageTypes.findMany({ + where: { + name: { + in: [ + ImageTypes.EPISODE_THUMBNAIL, + ImageTypes.EPISODE_IMAGE + ] + } + } + }); + const thumbnailType = imageTypes.find(type => type.name === ImageTypes.EPISODE_THUMBNAIL); + const imageType = imageTypes.find(type => type.name === ImageTypes.EPISODE_IMAGE); + const thumbnailSum: string = this.saveImage(episodeData.thumbnail); const dbThumbnail = await tx.images.create({ data: { @@ -63,16 +74,23 @@ export class WebtoonDatabaseService{ dbImage = await tx.images.create({ data: { sum: imageSum, - type_id: imageType.id + type_id: imageType.id, + episode_images: { + create: { + number: i, + episode_id: dbEpisode.id + } + } + } + }); + else + await tx.episodeImages.create({ + data: { + number: i, + episode_id: dbEpisode.id, + image_id: dbImage.id } }); - await tx.episodeImages.create({ - data: { - number: i, - episode_id: dbEpisode.id, - image_id: dbImage.id - } - }); } }); } @@ -82,21 +100,30 @@ export class WebtoonDatabaseService{ return; await this.prismaService.$transaction(async(tx) => { const genreIds: number[] = []; + const genres = await tx.genres.findMany(); for(const genre of webtoon.genres){ - const dbGenre = await tx.genres.findFirst({ - where: { - name: genre - } - }); + const dbGenre = genres.find(dbGenre => dbGenre.name === genre); if(!dbGenre) throw new NotFoundException(`Genre ${genre} not found in database.`); genreIds.push(dbGenre.id); } - const thumbnailType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_THUMBNAIL}}); - const backgroundType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_BACKGROUND_BANNER}}); - const topType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_TOP_BANNER}}); - const mobileType = await tx.imageTypes.findFirst({where: {name: ImageTypes.WEBTOON_MOBILE_BANNER}}); + const imageTypes = await tx.imageTypes.findMany({ + where: { + name: { + in: [ + ImageTypes.WEBTOON_THUMBNAIL, + ImageTypes.WEBTOON_BACKGROUND_BANNER, + ImageTypes.WEBTOON_TOP_BANNER, + ImageTypes.WEBTOON_MOBILE_BANNER + ] + } + } + }); + const thumbnailType = imageTypes.find(type => type.name === ImageTypes.WEBTOON_THUMBNAIL); + const backgroundType = imageTypes.find(type => type.name === ImageTypes.WEBTOON_BACKGROUND_BANNER); + const topType = imageTypes.find(type => type.name === ImageTypes.WEBTOON_TOP_BANNER); + const mobileType = imageTypes.find(type => type.name === ImageTypes.WEBTOON_MOBILE_BANNER); const thumbnailSum: string = this.saveImage(webtoonData.thumbnail); const backgroundSum: string = this.saveImage(webtoonData.backgroundBanner); From de42378a7efd9711ed9a10b1f1823d04c2657ba7 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 20:09:59 +0200 Subject: [PATCH 6/7] :wrench: Remove .env.vault to use classical .env --- .env.example | 12 ++++++++++++ .env.vault | 25 ------------------------- .gitignore | 16 ++++++---------- 3 files changed, 18 insertions(+), 35 deletions(-) create mode 100644 .env.example delete mode 100644 .env.vault diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..176e132 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# Server +SERVER_TYPE="http" +BIND_ADDRESS="0.0.0.0" +HTTP_PORT="3000" +HTTPS_PORT="3001" + +# SSL +SSL_KEY_FILE="" +SSL_CERT_FILE="" + +# API +PREFIX="api/v1/" diff --git a/.env.vault b/.env.vault deleted file mode 100644 index 6ec084f..0000000 --- a/.env.vault +++ /dev/null @@ -1,25 +0,0 @@ -#/-------------------.env.vault---------------------/ -#/ cloud-agnostic vaulting standard / -#/ [how it works](https://dotenv.org/env-vault) / -#/--------------------------------------------------/ - -# development -DOTENV_VAULT_DEVELOPMENT="KNvm+Qh6YSjcgjtgrKsBoDsiv4YRIKsKyvIWzO5eJEodvOscAOOuu7ceu8dqzx4zt9Mqc4KwYW+vkwb1j9FNpnytf3wC8PyaR/Ut/TwIOsgVBvusH9zBhGyTMs9UBQl+Mr2xoCiAgcJU/4wJC1si3yRfXPb8sMnbHptLUGTR/Vt6LqL8b+z7Spqb/rkopp2cnD3iOUGO8k5PdaLE+ZvTcZn4QQf3h+Y1KdPm8brKyVL3Bg==" -DOTENV_VAULT_DEVELOPMENT_VERSION=2 - -# ci -DOTENV_VAULT_CI="TvTsSWaFOwVmAJ3TljKXbY8aVGdjxrJHbfTRcHK0kapXI2mf" -DOTENV_VAULT_CI_VERSION=1 - -# staging -DOTENV_VAULT_STAGING="Ap90Jvn8Qox+t/HFk3m90rCyGo4bKBwQibQXgadLi8vytF7s" -DOTENV_VAULT_STAGING_VERSION=1 - -# production -DOTENV_VAULT_PRODUCTION="qxAoRX5pDfPo5Z1ytuadJP/5t7rO828mPjd9xj4Osl/JK7qK" -DOTENV_VAULT_PRODUCTION_VERSION=1 - -#/----------------settings/metadata-----------------/ -DOTENV_VAULT="vlt_d8e0e54bf1097c506d82770a7b492fb4d8124e1171b3c71a170f761215f23956" -DOTENV_API_URL="https://vault.dotenv.org" -DOTENV_CLI="npx dotenv-vault@latest" diff --git a/.gitignore b/.gitignore index b4468c6..623884f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ -# Created by https://www.toptal.com/developers/gitignore/api/webstorm+all,node,database -# Edit at https://www.toptal.com/developers/gitignore?templates=webstorm+all,node,database +# Created by https://www.toptal.com/developers/gitignore/api/dotenv,webstorm+all,node,database +# Edit at https://www.toptal.com/developers/gitignore?templates=dotenv,webstorm+all,node,database ### Database ### *.accdb *.db -*.db-journal *.dbf *.mdb *.pdb @@ -12,6 +11,9 @@ *.db-shm *.db-wal +### dotenv ### +.env + ### Node ### # Logs logs @@ -88,7 +90,6 @@ web_modules/ .yarn-integrity # dotenv environment variable files -.env .env.development.local .env.test.local .env.production.local @@ -240,11 +241,6 @@ fabric.properties !.idea/codeStyles !.idea/runConfigurations -# End of https://www.toptal.com/developers/gitignore/api/webstorm+all,node,database - -.env* -.flaskenv* -!.env.project -!.env.vault +# End of https://www.toptal.com/developers/gitignore/api/dotenv,webstorm+all,node,database images/ From 6ab94b683e62db8054911e87f8eac612d5872195 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 20:13:54 +0200 Subject: [PATCH 7/7] :art: Improve webtoon route naming --- src/modules/webtoon/webtoon/webtoon.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/webtoon/webtoon/webtoon.controller.ts b/src/modules/webtoon/webtoon/webtoon.controller.ts index f3a0424..0e9081b 100644 --- a/src/modules/webtoon/webtoon/webtoon.controller.ts +++ b/src/modules/webtoon/webtoon/webtoon.controller.ts @@ -16,13 +16,13 @@ export class WebtoonController{ private readonly webtoonDatabaseService: WebtoonDatabaseService, ){} - @Get("list") + @Get() @ApiResponse({status: 200, description: "Returns a list of webtoons", type: WebtoonResponse, isArray: true}) async getWebtoonList(): Promise{ return this.webtoonDatabaseService.getWebtoons(); } - @Get("episodes/:webtoonId") + @Get(":webtoonId/episodes") @ApiResponse({status: 200, description: "Returns a list of episodes for a webtoon", type: EpisodesResponse}) async getWebtoonEpisodes(@Param() webtoonIdDto: WebtoonIdDto): Promise{ return this.webtoonDatabaseService.getEpisodeInfos(webtoonIdDto.webtoonId);