diff --git a/flake.nix b/flake.nix index ca74dc1..e12cfa5 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ }; packages = rec { default = shows-api; - shows-api = pkgs.callPackage ./package.nix {}; + shows-api = pkgs.callPackage ./package.nix { }; }; } ); diff --git a/package.nix b/package.nix index 659c30a..4078811 100644 --- a/package.nix +++ b/package.nix @@ -1,7 +1,7 @@ -{ - lib, - buildNpmPackage, - nodejs, +{ lib +, buildNpmPackage +, nodejs +, }: buildNpmPackage { name = "shows-api"; @@ -28,7 +28,7 @@ buildNpmPackage { meta = { description = "NestJS API to store info about shows on a MongoDB database"; homepage = "https://git.everest.tailscale/Toast/shows-api"; - sourceProvenance = [lib.sourceTypes.fromSource]; + sourceProvenance = [ lib.sourceTypes.fromSource ]; mainProgram = "shows-api"; }; } diff --git a/src/main.ts b/src/main.ts index 2e23c55..ff80913 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; -import { Logger } from '@nestjs/common'; +import { BadRequestException, Logger, ValidationPipe } from '@nestjs/common'; +import { ValidationError } from 'class-validator'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -17,6 +18,31 @@ async function bootstrap() { credentials: true, origin, }); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + // https://stackoverflow.com/questions/75581669/customize-error-message-in-nest-js-using-class-validator + exceptionFactory: (validationErrors: ValidationError[] = []) => { + const errors = validationErrors.map((error) => ({ + error: Object.values(error.constraints).join(', '), + })); + + let errorMessage: string; + errors.forEach((value, index) => { + if (index == 0) { + errorMessage = value.error; + } else { + errorMessage = errorMessage.concat(', ', value.error); + } + }); + + return new BadRequestException({ + status: 'Validation Error', + message: errorMessage, + }); + }, + }), + ); await app.listen(process.env.PORT); } diff --git a/src/pagination.dto.ts b/src/pagination.dto.ts new file mode 100644 index 0000000..fbdd2e1 --- /dev/null +++ b/src/pagination.dto.ts @@ -0,0 +1,15 @@ +import { Type } from "class-transformer"; +import { IsNumber, IsOptional, Min } from "class-validator"; + +export class PaginationDto { + @IsOptional() + @IsNumber() + @Type(() => Number) + @Min(1) + page?: number = 1; + @IsOptional() + @IsNumber() + @Type(() => Number) + @Min(1) + limit?: number = 1; +} diff --git a/src/shows/shows.controller.ts b/src/shows/shows.controller.ts index 7924d92..86e4ca1 100644 --- a/src/shows/shows.controller.ts +++ b/src/shows/shows.controller.ts @@ -13,6 +13,14 @@ import { } from '@nestjs/common'; import { ShowsService } from './shows.service'; import { ShowDto } from './dto/show.dto'; +import { PaginationDto } from 'src/pagination.dto'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class SearchDto { + @IsString() + @IsNotEmpty() + query: string; +} @Controller('shows') export class ShowsController { @@ -36,10 +44,15 @@ export class ShowsController { } @Get() - async findAll() { + async findAll(@Query() pagination: PaginationDto) { try { - const shows = await this.showsService.findAll(); - return { status: 'Ok', shows, totalShows: shows.length }; + const { page, limit } = pagination; + const serviceResponse = await this.showsService.findAll(page, limit); + return { + status: 'Ok', + shows: serviceResponse.shows, + showCount: serviceResponse.showCount, + }; } catch (error) { throw new InternalServerErrorException({ status: error.name, @@ -72,15 +85,16 @@ export class ShowsController { } @Get('search') - async search(@Query('query') name: string) { + async search(@Query() search: SearchDto) { try { - const shows = await this.showsService.search(name); + const { query } = search; + const shows = await this.showsService.search(query); if (shows.length > 0) { return { status: 'Ok', show: shows, test: shows.length }; } else { throw new NotFoundException({ status: 'Error', - message: `Can't find show matching ${name}`, + message: `Can't find show matching ${query}`, }); } } catch (error) { diff --git a/src/shows/shows.service.ts b/src/shows/shows.service.ts index 0c28442..76189f8 100644 --- a/src/shows/shows.service.ts +++ b/src/shows/shows.service.ts @@ -13,8 +13,22 @@ export class ShowsService { return show.save(); } - async findAll(): Promise { - return this.showModel.find(); + async findAll(page: number, showsPerPage: number): Promise { + // Default value is 1 and I can't think any reason why you would want + // a single item per page, so if showsPerPage is 1 then just don't do + // any pagination at all + let shows; + if (showsPerPage != 1) { + const skip = (page - 1) * showsPerPage; + shows = await this.showModel.find().skip(skip).limit(showsPerPage); + } else { + shows = await this.showModel.find(); + } + const total = await this.showModel.countDocuments(); + return { + shows: shows, + showCount: total, + }; } async findId(id: string): Promise {