From a69973b932f5939d3700574befe00708218da387 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 20:46:18 +0200 Subject: [PATCH 1/3] :sparkles: Add new admin routes for better queue management --- src/modules/webtoon/admin/admin.controller.ts | 35 +++++++++++++++++-- .../webtoon/download-manager.service.ts | 27 +++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/modules/webtoon/admin/admin.controller.ts b/src/modules/webtoon/admin/admin.controller.ts index 4a084d5..e3026f6 100644 --- a/src/modules/webtoon/admin/admin.controller.ts +++ b/src/modules/webtoon/admin/admin.controller.ts @@ -1,8 +1,9 @@ -import {Body, Controller, Post} from "@nestjs/common"; +import {Body, Controller, Delete, Get, HttpCode, Post} from "@nestjs/common"; import {ApiResponse, ApiTags} from "@nestjs/swagger"; import {DownloadManagerService} from "../webtoon/download-manager.service"; import {AddWebtoonToQueueDto} from "./models/dto/add-webtoon-to-queue.dto"; import {HttpStatusCode} from "axios"; +import CachedWebtoonModel from "../webtoon/models/models/cached-webtoon.model"; @Controller("admin") @@ -14,16 +15,44 @@ export class AdminController{ ){} @Post("queue") - @ApiResponse({status: HttpStatusCode.Ok, description: "Adds a webtoon to the download queue"}) + @ApiResponse({status: HttpStatusCode.Created, description: "Adds a webtoon to the download queue"}) @ApiResponse({status: HttpStatusCode.TooEarly, description: "Cache not loaded."}) async addWebtoonToQueue(@Body() addWebtoonToQueueDto: AddWebtoonToQueueDto): Promise{ return this.downloadManagerService.addWebtoonToQueue(addWebtoonToQueueDto.name, addWebtoonToQueueDto.language); } @Post("update/all") - @ApiResponse({status: HttpStatusCode.Ok, description: "Updates all webtoons in the database"}) + @ApiResponse({status: HttpStatusCode.Created, description: "Updates all webtoons in the database"}) @ApiResponse({status: HttpStatusCode.TooEarly, description: "Cache not loaded."}) async updateAllWebtoons(): Promise{ return this.downloadManagerService.updateAllWebtoons(); } + + @Get("current-download") + @ApiResponse({status: HttpStatusCode.Ok, description: "Returns the current download"}) + @ApiResponse({status: HttpStatusCode.NotFound, description: "No download in progress"}) + async getCurrentDownload(): Promise{ + return this.downloadManagerService.getCurrentDownload(); + } + + @Get("queue") + @ApiResponse({status: HttpStatusCode.Ok, description: "Returns the current download queue"}) + @ApiResponse({status: HttpStatusCode.NotFound, description: "No download in progress"}) + async getQueue(): Promise{ + return this.downloadManagerService.getDownloadQueue(); + } + + @Delete("current-download") + @HttpCode(HttpStatusCode.NoContent) + @ApiResponse({status: HttpStatusCode.NoContent, description: "Cancels the current download"}) + async cancelCurrentDownload(): Promise{ + return this.downloadManagerService.skipCurrentDownload(); + } + + @Delete("queue") + @HttpCode(HttpStatusCode.NoContent) + @ApiResponse({status: HttpStatusCode.NoContent, description: "Clears the download queue"}) + async clearQueue(): Promise{ + return this.downloadManagerService.clearDownloadQueue(); + } } diff --git a/src/modules/webtoon/webtoon/download-manager.service.ts b/src/modules/webtoon/webtoon/download-manager.service.ts index e52654a..01e62b5 100644 --- a/src/modules/webtoon/webtoon/download-manager.service.ts +++ b/src/modules/webtoon/webtoon/download-manager.service.ts @@ -4,7 +4,7 @@ import {WebtoonParserService} from "./webtoon-parser.service"; import CachedWebtoonModel from "./models/models/cached-webtoon.model"; import EpisodeModel from "./models/models/episode.model"; import EpisodeDataModel from "./models/models/episode-data.model"; -import {HttpException, Injectable} from "@nestjs/common"; +import {HttpException, Injectable, NotFoundException} from "@nestjs/common"; import WebtoonModel from "./models/models/webtoon.model"; import WebtoonDataModel from "./models/models/webtoon-data.model"; import WebtoonQueue from "../../../common/utils/models/webtoon-queue"; @@ -71,10 +71,35 @@ export class DownloadManagerService{ 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++){ + if(!this.currentDownload) + break; const epImageLinks: string[] = await this.webtoonParser.getEpisodeLinks(this.currentDownload, epList[i]); const episodeData: EpisodeDataModel = await this.webtoonDownloader.downloadEpisode(epList[i], epImageLinks); + if(!this.currentDownload) + break; await this.webtoonDatabase.saveEpisode(this.currentDownload, epList[i], episodeData); } } } + + getCurrentDownload(): CachedWebtoonModel | undefined{ + if(this.currentDownload) + return this.currentDownload; + throw new NotFoundException("No download in progress."); + } + + getDownloadQueue(): CachedWebtoonModel[]{ + if(!this.currentDownload) + throw new NotFoundException("No download in progress.");; + return [this.currentDownload, ...this.queue.getElements()]; + } + + skipCurrentDownload(): void{ + this.currentDownload = undefined; + } + + clearDownloadQueue(){ + this.queue.clear(); + this.currentDownload = undefined; + } } From 8b5a9e05755a3487830709db9b6ddec83fe92c76 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 21:06:54 +0200 Subject: [PATCH 2/3] :lock: Implement guard to admin routes to force the use of the admin key from .env --- .env.example | 3 +++ src/modules/webtoon/admin/admin.controller.ts | 12 +++++++++-- src/modules/webtoon/admin/admin.module.ts | 3 ++- .../webtoon/admin/guard/admin.guard.ts | 20 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/modules/webtoon/admin/guard/admin.guard.ts diff --git a/.env.example b/.env.example index 176e132..28dabf9 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,6 @@ SSL_CERT_FILE="" # API PREFIX="api/v1/" + +# Security +ADMIN_KEY="admin" diff --git a/src/modules/webtoon/admin/admin.controller.ts b/src/modules/webtoon/admin/admin.controller.ts index e3026f6..46476d8 100644 --- a/src/modules/webtoon/admin/admin.controller.ts +++ b/src/modules/webtoon/admin/admin.controller.ts @@ -1,13 +1,15 @@ -import {Body, Controller, Delete, Get, HttpCode, Post} from "@nestjs/common"; -import {ApiResponse, ApiTags} from "@nestjs/swagger"; +import {Body, Controller, Delete, Get, HttpCode, Post, UseGuards} from "@nestjs/common"; +import {ApiBearerAuth, ApiResponse, ApiTags} from "@nestjs/swagger"; import {DownloadManagerService} from "../webtoon/download-manager.service"; import {AddWebtoonToQueueDto} from "./models/dto/add-webtoon-to-queue.dto"; import {HttpStatusCode} from "axios"; import CachedWebtoonModel from "../webtoon/models/models/cached-webtoon.model"; +import {AdminGuard} from "./guard/admin.guard"; @Controller("admin") @ApiTags("Admin") +@UseGuards(AdminGuard) export class AdminController{ constructor( @@ -15,6 +17,7 @@ export class AdminController{ ){} @Post("queue") + @ApiBearerAuth() @ApiResponse({status: HttpStatusCode.Created, description: "Adds a webtoon to the download queue"}) @ApiResponse({status: HttpStatusCode.TooEarly, description: "Cache not loaded."}) async addWebtoonToQueue(@Body() addWebtoonToQueueDto: AddWebtoonToQueueDto): Promise{ @@ -22,6 +25,7 @@ export class AdminController{ } @Post("update/all") + @ApiBearerAuth() @ApiResponse({status: HttpStatusCode.Created, description: "Updates all webtoons in the database"}) @ApiResponse({status: HttpStatusCode.TooEarly, description: "Cache not loaded."}) async updateAllWebtoons(): Promise{ @@ -29,6 +33,7 @@ export class AdminController{ } @Get("current-download") + @ApiBearerAuth() @ApiResponse({status: HttpStatusCode.Ok, description: "Returns the current download"}) @ApiResponse({status: HttpStatusCode.NotFound, description: "No download in progress"}) async getCurrentDownload(): Promise{ @@ -36,6 +41,7 @@ export class AdminController{ } @Get("queue") + @ApiBearerAuth() @ApiResponse({status: HttpStatusCode.Ok, description: "Returns the current download queue"}) @ApiResponse({status: HttpStatusCode.NotFound, description: "No download in progress"}) async getQueue(): Promise{ @@ -43,6 +49,7 @@ export class AdminController{ } @Delete("current-download") + @ApiBearerAuth() @HttpCode(HttpStatusCode.NoContent) @ApiResponse({status: HttpStatusCode.NoContent, description: "Cancels the current download"}) async cancelCurrentDownload(): Promise{ @@ -50,6 +57,7 @@ export class AdminController{ } @Delete("queue") + @ApiBearerAuth() @HttpCode(HttpStatusCode.NoContent) @ApiResponse({status: HttpStatusCode.NoContent, description: "Clears the download queue"}) async clearQueue(): Promise{ diff --git a/src/modules/webtoon/admin/admin.module.ts b/src/modules/webtoon/admin/admin.module.ts index 13274fc..b3f2745 100644 --- a/src/modules/webtoon/admin/admin.module.ts +++ b/src/modules/webtoon/admin/admin.module.ts @@ -1,10 +1,11 @@ import {Module} from "@nestjs/common"; import {AdminController} from "./admin.controller"; import {WebtoonModule} from "../webtoon/webtoon.module"; +import {AdminGuard} from "./guard/admin.guard"; @Module({ imports: [WebtoonModule], controllers: [AdminController], - providers: [], + providers: [AdminGuard], }) export class AdminModule{} diff --git a/src/modules/webtoon/admin/guard/admin.guard.ts b/src/modules/webtoon/admin/guard/admin.guard.ts new file mode 100644 index 0000000..8780901 --- /dev/null +++ b/src/modules/webtoon/admin/guard/admin.guard.ts @@ -0,0 +1,20 @@ +import {CanActivate, ExecutionContext, Injectable, UnauthorizedException} from "@nestjs/common"; +import {ConfigService} from "@nestjs/config"; + +@Injectable() +export class AdminGuard implements CanActivate{ + constructor( + private readonly configService: ConfigService, + ){} + + async canActivate(context: ExecutionContext): Promise{ + const adminKey = this.configService.get("ADMIN_KEY"); + const request = context.switchToHttp().getRequest(); + const token = request.headers.authorization?.split(" ")[1]; + if(!token) + throw new UnauthorizedException("No token provided"); + if(token !== adminKey) + throw new UnauthorizedException("Invalid token"); + return true; + } +} From 4e1ecad08f765d693675cb492522bacd14c9f087 Mon Sep 17 00:00:00 2001 From: Xen0Xys Date: Wed, 15 May 2024 21:08:07 +0200 Subject: [PATCH 3/3] :art: Remove admin guard from admin module providers --- src/modules/webtoon/admin/admin.module.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/webtoon/admin/admin.module.ts b/src/modules/webtoon/admin/admin.module.ts index b3f2745..13274fc 100644 --- a/src/modules/webtoon/admin/admin.module.ts +++ b/src/modules/webtoon/admin/admin.module.ts @@ -1,11 +1,10 @@ import {Module} from "@nestjs/common"; import {AdminController} from "./admin.controller"; import {WebtoonModule} from "../webtoon/webtoon.module"; -import {AdminGuard} from "./guard/admin.guard"; @Module({ imports: [WebtoonModule], controllers: [AdminController], - providers: [AdminGuard], + providers: [], }) export class AdminModule{}