diff --git a/backend/src/album/album.controller.ts b/backend/src/album/album.controller.ts index 5fecd04..d340947 100644 --- a/backend/src/album/album.controller.ts +++ b/backend/src/album/album.controller.ts @@ -31,7 +31,9 @@ export class AlbumController { @Post() @UsePipes(new ValidationPipe({ transform: true })) - async create(@Body() createAlbumDto: CreateAlbumDto): Promise { + async create( + @Body() createAlbumDto: CreateAlbumDto, + ): Promise { return this.albumService.create(createAlbumDto); } diff --git a/backend/src/album/album.service.ts b/backend/src/album/album.service.ts index 92e852e..a812e91 100644 --- a/backend/src/album/album.service.ts +++ b/backend/src/album/album.service.ts @@ -13,40 +13,45 @@ export class AlbumService { ) {} findAll(): Promise { - return this.albumRepository.find({ - order: { - artist: 'ASC', - title: 'ASC', - }, - }).then((albums) => { - return albums; - }) - .catch((error) => { - return `There was a problem getting the list of albums: ${error}` - }); + return this.albumRepository + .find({ + order: { + artist: 'ASC', + title: 'ASC', + }, + }) + .then((albums) => { + return albums; + }) + .catch((error) => { + return `There was a problem getting the list of albums: ${error}`; + }); } findOneById(id: number): Promise { - return this.albumRepository.findOne({ - where: { - id: id, - }, - relations: { - songs: true, - }, - order: { - songs: { - trackNumber: 'ASC', + return this.albumRepository + .findOne({ + where: { + id: id, }, - }, - }).then((album) => { - return album; - }) - .catch((error) => { - return `There was a problem creating the Album identified by ID ${id}: ${error}` - }).finally(() => { - return `There was a problem creating the Album identified by ID ${id}` - }); + relations: { + songs: true, + }, + order: { + songs: { + trackNumber: 'ASC', + }, + }, + }) + .then((album) => { + return album; + }) + .catch((error) => { + return `There was a problem creating the Album identified by ID ${id}: ${error}`; + }) + .finally(() => { + return `There was a problem creating the Album identified by ID ${id}`; + }); } async create(createAlbumDto: CreateAlbumDto): Promise { @@ -55,12 +60,13 @@ export class AlbumService { artist: createAlbumDto.artist, genre: createAlbumDto.genre, }); - return await this.albumRepository.save(album) + return await this.albumRepository + .save(album) .then((savedAlbum) => { return savedAlbum; }) .catch((error) => { - return `There was a problem creating the Album (${createAlbumDto.title} by ${createAlbumDto.artist} (${createAlbumDto.genre})): ${error}` + return `There was a problem creating the Album (${createAlbumDto.title} by ${createAlbumDto.artist} (${createAlbumDto.genre})): ${error}`; }); } @@ -100,12 +106,13 @@ export class AlbumService { } async remove(id: number): Promise { - return await this.albumRepository.delete(id) - .then((deleteResult) => { - return deleteResult; - }) - .catch((error) => { - return `There was a problem deleting the Album identified by ID ${id}: ${error}` - }); + return await this.albumRepository + .delete(id) + .then((deleteResult) => { + return deleteResult; + }) + .catch((error) => { + return `There was a problem deleting the Album identified by ID ${id}: ${error}`; + }); } } diff --git a/backend/src/album/dto/create-album.dto.ts b/backend/src/album/dto/create-album.dto.ts index a8fc239..95e942b 100644 --- a/backend/src/album/dto/create-album.dto.ts +++ b/backend/src/album/dto/create-album.dto.ts @@ -1,3 +1,5 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + export class CreateAlbumDto { @IsString() @IsNotEmpty() diff --git a/backend/src/album/dto/update-album.dto.ts b/backend/src/album/dto/update-album.dto.ts index 61911d5..e9ba2ef 100644 --- a/backend/src/album/dto/update-album.dto.ts +++ b/backend/src/album/dto/update-album.dto.ts @@ -1,5 +1,6 @@ +import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; + export class UpdateAlbumDto { - @Column() @IsNumber() id: number; @@ -13,7 +14,6 @@ export class UpdateAlbumDto { @IsString() @IsNotEmpty() - @IsString() @IsNotEmpty() genre: string; diff --git a/backend/src/main.ts b/backend/src/main.ts index d2a4d7a..70cc327 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,5 +1,5 @@ import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; +import { UnprocessableEntityException, ValidationError, ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; async function bootstrap() { @@ -13,16 +13,53 @@ async function bootstrap() { allowedHeaders: 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe', }); - app.useGlobalPipes(new ValidationPipe({ - transform: true, - exceptionFactory: (errors) => { - const result = errors.map((error) => ({ - property: error.property, - message: error.constraints[Object.keys(error.constraints)[0]], - })); - return new UnprocessableEntityException(result); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + exceptionFactory: (validationErrors: ValidationError[] = []) => { + const getPrettyClassValidatorErrors = ( + validationErrors: ValidationError[], + parentProperty = '', + ): Array<{ property: string; errors: string[] }> => { + const errors : any[] = []; + + const getValidationErrorsRecursively = ( + validationErrors: ValidationError[], + parentProperty = '', + ) => { + for (const error of validationErrors) { + const propertyPath = parentProperty + ? `${parentProperty}.${error.property}` + : error.property; + + if (error.constraints) { + errors.push({ + property: propertyPath, + errors: Object.values(error.constraints), + }); + } + + if (error.children?.length) { + getValidationErrorsRecursively(error.children, propertyPath); + } + } + }; + + getValidationErrorsRecursively(validationErrors, parentProperty); + + return errors; + }; + + const errors = getPrettyClassValidatorErrors(validationErrors); + + return new UnprocessableEntityException({ + message: 'validation error', + errors: errors, + }); }, - })); + }), + ); await app.listen(process.env.PORT ?? 3000); } bootstrap(); diff --git a/backend/src/song/dto/create-song.dto.ts b/backend/src/song/dto/create-song.dto.ts index 68b1fb5..3481a6c 100644 --- a/backend/src/song/dto/create-song.dto.ts +++ b/backend/src/song/dto/create-song.dto.ts @@ -1,21 +1,16 @@ -import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; export class CreateSongDto { - @Column({ unique: true }) @IsString() @IsNotEmpty() title: string; - @Column() @IsNumber() duration: number; - @Column() @IsNumber() trackNumber: number; - @Column() @IsNumber() albumId: number; } diff --git a/backend/src/song/dto/update-song.dto.ts b/backend/src/song/dto/update-song.dto.ts index d4f5ae6..25d2005 100644 --- a/backend/src/song/dto/update-song.dto.ts +++ b/backend/src/song/dto/update-song.dto.ts @@ -1,22 +1,19 @@ +import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; + export class UpdateSongDto { - @Column() @IsNumber() id: number; - @Column({ unique: true }) @IsString() @IsNotEmpty() title: string; - @Column() @IsNumber() duration: number; - @Column() @IsNumber() trackNumber: number; - @Column() @IsNumber() albumId: number; } diff --git a/backend/src/song/song.controller.ts b/backend/src/song/song.controller.ts index cd3d43c..3851996 100644 --- a/backend/src/song/song.controller.ts +++ b/backend/src/song/song.controller.ts @@ -31,7 +31,9 @@ export class SongController { @Post() @UsePipes(new ValidationPipe({ transform: true })) - async create(@Body() createSongDto: CreateSongDto): Promise { + async create( + @Body() createSongDto: CreateSongDto, + ): Promise { return this.songService.create(createSongDto); } diff --git a/backend/src/song/song.service.ts b/backend/src/song/song.service.ts index 3467f94..b263497 100644 --- a/backend/src/song/song.service.ts +++ b/backend/src/song/song.service.ts @@ -16,25 +16,29 @@ export class SongService { ) {} findAll(): Promise { - return this.songRepository.find({ - order: { - trackNumber: 'ASC', - }, - }).then((songs) => { - return songs; - }) - .catch((error) => { - return `There was a problem getting the list of songs: ${error}` - }); + return this.songRepository + .find({ + order: { + trackNumber: 'ASC', + }, + }) + .then((songs) => { + return songs; + }) + .catch((error) => { + return `There was a problem getting the list of songs: ${error}`; + }); } findOneById(id: number): Promise { - return this.songRepository.findOneBy({ id: id }).then((albums) => { - return albums; - }) - .catch((error) => { - return `There was a problem getting the song identified by ID ${id}: ${error}` - }); + return this.songRepository + .findOneBy({ id: id }) + .then((albums) => { + return albums; + }) + .catch((error) => { + return `There was a problem getting the song identified by ID ${id}: ${error}`; + }); } async create(createSongDto: CreateSongDto): Promise { @@ -47,18 +51,23 @@ export class SongService { song.duration = createSongDto.duration; song.trackNumber = createSongDto.trackNumber; song.album = album; - return this.songRepository.save(song).then((albums) => { - return albums; - }) - .catch((error) => { - return `There was a problem creating the song (${createSongDto.trackNumber} ${createSongDto.title} (${createSongDto.duration}s) on the Album ${album.title} by ${album.artist}): ${error}` - }); + return this.songRepository + .save(song) + .then((albums) => { + return albums; + }) + .catch((error) => { + return `There was a problem creating the song (${createSongDto.trackNumber} ${createSongDto.title} (${createSongDto.duration}s) on the Album ${album.title} by ${album.artist}): ${error}`; + }); } else { throw new Error(`Unable to find Album with ID ${createSongDto.albumId}`); } } - async update(id: number, updateSongDto: UpdateSongDto): Promise { + async update( + id: number, + updateSongDto: UpdateSongDto, + ): Promise { if (id == updateSongDto.id) { const album = await this.albumRepository.findOneBy({ id: updateSongDto.albumId, @@ -91,12 +100,13 @@ export class SongService { } async remove(id: number): Promise { - return await this.songRepository.delete(id) - .then((deleteResult) => { - return deleteResult; - }) - .catch((error) => { - return `There was a problem deleting the Song identified by ID ${id}: ${error}` - }); + return await this.songRepository + .delete(id) + .then((deleteResult) => { + return deleteResult; + }) + .catch((error) => { + return `There was a problem deleting the Song identified by ID ${id}: ${error}`; + }); } }