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 4a084d5..46476d8 100644 --- a/src/modules/webtoon/admin/admin.controller.ts +++ b/src/modules/webtoon/admin/admin.controller.ts @@ -1,12 +1,15 @@ -import {Body, Controller, 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( @@ -14,16 +17,50 @@ export class AdminController{ ){} @Post("queue") - @ApiResponse({status: HttpStatusCode.Ok, description: "Adds a webtoon to the download 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{ return this.downloadManagerService.addWebtoonToQueue(addWebtoonToQueueDto.name, addWebtoonToQueueDto.language); } @Post("update/all") - @ApiResponse({status: HttpStatusCode.Ok, description: "Updates all webtoons in the database"}) + @ApiBearerAuth() + @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") + @ApiBearerAuth() + @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") + @ApiBearerAuth() + @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") + @ApiBearerAuth() + @HttpCode(HttpStatusCode.NoContent) + @ApiResponse({status: HttpStatusCode.NoContent, description: "Cancels the current download"}) + async cancelCurrentDownload(): Promise{ + return this.downloadManagerService.skipCurrentDownload(); + } + + @Delete("queue") + @ApiBearerAuth() + @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/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; + } +} 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; + } }