feat: genre and album populate and CRUD

This commit is contained in:
GitBluub
2022-10-25 17:14:47 +09:00
committed by Bluub
parent 5cef277f40
commit 6975144e35
20 changed files with 643 additions and 7 deletions
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Album" ADD COLUMN "artistId" INTEGER;
-- AddForeignKey
ALTER TABLE "Album" ADD CONSTRAINT "Album_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+3 -1
View File
@@ -43,12 +43,14 @@ model Artist {
name String @unique
Song Song[]
Album Album[]
}
model Album {
id Int @id @default(autoincrement())
name String @unique
artistId Int?
artist Artist? @relation(fields: [artistId], references: [id])
Song Song[]
}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AlbumController } from './album.controller';
describe('AlbumController', () => {
let controller: AlbumController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AlbumController],
}).compile();
controller = module.get<AlbumController>(AlbumController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+82
View File
@@ -0,0 +1,82 @@
import {
BadRequestException,
Body,
ConflictException,
Controller,
DefaultValuePipe,
Delete,
Get,
InternalServerErrorException,
NotFoundException,
Param,
ParseIntPipe,
Post,
Query,
Req,
StreamableFile,
} from '@nestjs/common';
import { Plage } from 'src/models/plage';
import { CreateAlbumDto } from './dto/create-album.dto';
import { AlbumService } from './album.service';
import { Request } from 'express';
import { Prisma, Album } from '@prisma/client';
import { createReadStream } from 'fs';
import { ApiTags } from '@nestjs/swagger';
@Controller('album')
@ApiTags('album')
export class AlbumController {
constructor(private readonly albumService: AlbumService) {}
@Post()
async create(@Body() createAlbumDto: CreateAlbumDto) {
try {
return await this.albumService.createAlbum({
...createAlbumDto,
artist: createAlbumDto.artist
? { connect: { id: createAlbumDto.artist } }
: undefined
});
} catch {
throw new ConflictException(
await this.albumService.album({ name: createAlbumDto.name }),
);
}
}
@Delete(':id')
async remove(@Param('id', ParseIntPipe) id: number) {
return await this.albumService.deleteAlbum({ id });
}
@Get()
async findAll(
@Req() req: Request,
@Query() filter: Prisma.AlbumWhereInput,
@Query('skip', new DefaultValuePipe(0), ParseIntPipe) skip: number,
@Query('take', new DefaultValuePipe(20), ParseIntPipe) take: number,
): Promise<Plage<Album>> {
try {
const ret = await this.albumService.albums({
skip,
take,
where: {
...filter,
id: filter.id ? +filter.id : undefined,
},
});
return new Plage(ret, req);
} catch (e) {
console.log(e);
throw new BadRequestException(null, e?.toString());
}
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
const res = await this.albumService.album({ id });
if (res === null) throw new NotFoundException('Album not found');
return res;
}
}
+11
View File
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from 'src/prisma/prisma.module';
import { AlbumController } from './album.controller';
import { AlbumService } from './album.service';
@Module({
imports: [PrismaModule],
controllers: [AlbumController],
providers: [AlbumService]
})
export class AlbumModule {}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AlbumService } from './album.service';
describe('AlbumService', () => {
let service: AlbumService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AlbumService],
}).compile();
service = module.get<AlbumService>(AlbumService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+45
View File
@@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { Prisma, Album } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
@Injectable()
export class AlbumService {
constructor(private prisma: PrismaService) {}
async createAlbum(data: Prisma.AlbumCreateInput): Promise<Album> {
return this.prisma.album.create({
data,
});
}
async album(
albumWhereUniqueInput: Prisma.AlbumWhereUniqueInput,
): Promise<Album | null> {
return this.prisma.album.findUnique({
where: albumWhereUniqueInput,
});
}
async albums(params: {
skip?: number;
take?: number;
cursor?: Prisma.AlbumWhereUniqueInput;
where?: Prisma.AlbumWhereInput;
orderBy?: Prisma.AlbumOrderByWithRelationInput;
}): Promise<Album[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.album.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async deleteAlbum(where: Prisma.AlbumWhereUniqueInput): Promise<Album> {
return this.prisma.album.delete({
where,
});
}
}
+11
View File
@@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
export class CreateAlbumDto {
@IsNotEmpty()
@ApiProperty()
name: string;
@ApiProperty()
artist?: number;
}
+5 -2
View File
@@ -9,10 +9,13 @@ import { SongModule } from './song/song.module';
import { LessonModule } from './lesson/lesson.module';
import { ArtistController } from './artist/artist.controller';
import { ArtistService } from './artist/artist.service';
import { GenreModule } from './genre/genre.module';
import { ArtistModule } from './artist/artist.module';
import { AlbumModule } from './album/album.module';
@Module({
imports: [UsersModule, PrismaModule, AuthModule, SongModule, LessonModule],
controllers: [AppController, ArtistController],
imports: [UsersModule, PrismaModule, AuthModule, SongModule, LessonModule, GenreModule, ArtistModule, AlbumModule],
controllers: [AppController],
providers: [AppService, PrismaService, ArtistService],
})
export class AppModule {}
+11
View File
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from 'src/prisma/prisma.module';
import { ArtistController } from './artist.controller';
import { ArtistService } from './artist.service';
@Module({
imports: [PrismaModule],
controllers: [ArtistController],
providers: [ArtistService]
})
export class ArtistModule {}
+8
View File
@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
export class CreateGenreDto {
@IsNotEmpty()
@ApiProperty()
name: string;
}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GenreController } from './genre.controller';
describe('GenreController', () => {
let controller: GenreController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [GenreController],
}).compile();
controller = module.get<GenreController>(GenreController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
+72
View File
@@ -0,0 +1,72 @@
import {
BadRequestException,
Body,
ConflictException,
Controller,
DefaultValuePipe,
Delete,
Get,
NotFoundException,
Param,
ParseIntPipe,
Post,
Query,
Req,
} from '@nestjs/common';
import { Plage } from 'src/models/plage';
import { CreateGenreDto } from './dto/create-genre.dto';
import { Request } from 'express';
import { GenreService } from './genre.service';
import { Prisma, Genre } from '@prisma/client';
import { ApiTags } from '@nestjs/swagger';
@Controller('genre')
@ApiTags('genre')
export class GenreController {
constructor(private readonly service: GenreService) {}
@Post()
async create(@Body() dto: CreateGenreDto) {
try {
return await this.service.create(dto);
} catch {
throw new ConflictException(await this.service.get({ name: dto.name }));
}
}
@Delete(':id')
async remove(@Param('id', ParseIntPipe) id: number) {
return await this.service.delete({ id });
}
@Get()
async findAll(
@Req() req: Request,
@Query() filter: Prisma.SongWhereInput,
@Query('skip', new DefaultValuePipe(0), ParseIntPipe) skip: number,
@Query('take', new DefaultValuePipe(20), ParseIntPipe) take: number,
): Promise<Plage<Genre>> {
try {
const ret = await this.service.list({
skip,
take,
where: {
...filter,
id: filter.id ? +filter.id : undefined,
},
});
return new Plage(ret, req);
} catch (e) {
console.log(e);
throw new BadRequestException(null, e?.toString());
}
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
const res = await this.service.get({ id });
if (res === null) throw new NotFoundException('Genre not found');
return res;
}
}
+11
View File
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PrismaModule } from 'src/prisma/prisma.module';
import { GenreController } from './genre.controller';
import { GenreService } from './genre.service';
@Module({
imports: [PrismaModule],
controllers: [GenreController],
providers: [GenreService]
})
export class GenreModule {}
+18
View File
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { GenreService } from './genre.service';
describe('GenreService', () => {
let service: GenreService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [GenreService],
}).compile();
service = module.get<GenreService>(GenreService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
+43
View File
@@ -0,0 +1,43 @@
import { Injectable } from '@nestjs/common';
import { Prisma, Genre } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
@Injectable()
export class GenreService {
constructor(private prisma: PrismaService) {}
async create(data: Prisma.GenreCreateInput): Promise<Genre> {
return this.prisma.genre.create({
data,
});
}
async get(where: Prisma.GenreWhereUniqueInput): Promise<Genre | null> {
return this.prisma.genre.findUnique({
where,
});
}
async list(params: {
skip?: number;
take?: number;
cursor?: Prisma.GenreWhereUniqueInput;
where?: Prisma.GenreWhereInput;
orderBy?: Prisma.GenreOrderByWithRelationInput;
}): Promise<Genre[]> {
const { skip, take, cursor, where, orderBy } = params;
return this.prisma.genre.findMany({
skip,
take,
cursor,
where,
orderBy,
});
}
async delete(where: Prisma.GenreWhereUniqueInput): Promise<Genre> {
return this.prisma.genre.delete({
where,
});
}
}
+129
View File
@@ -0,0 +1,129 @@
*** Settings ***
Documentation Tests of the /album route.
... Ensures that the album CRUD works corectly.
Resource ../rest.resource
*** Test Cases ***
Create a album
[Documentation] Create a album
&{res}= POST
... /album
... {"name": "Mama mia"}
Output
Integer response status 201
[Teardown] DELETE /album/${res.body.id}
Create a album with an artist
[Documentation] Create a album with an artist
&{artistRes}= POST
... /artist
... {"name": "Mama mia"}
&{res}= POST
... /album
... {"name": "Mama mia", "artist": ${artistRes.body.id}}
Output
Integer response status 201
[Teardown] Run Keywords DELETE /artist/${artistRes.body.id}
... AND DELETE /album/${res.body.id}
Duplicate a album
[Documentation] Duplicate a album
&{res}= POST
... /album
... {"name": "Mama mia"}
Output
Integer response status 201
&{res2}= POST
... /album
... {"name": "Mama mia"}
Output
Integer response status 409
Should Be Equal ${res.body.id} ${res2.body.id}
[Teardown] DELETE /album/${res.body.id}
Find a album
[Documentation] Create a album and find it
&{res}= POST
... /album
... {"name": "Mama mia"}
Output
Integer response status 201
&{get}= GET /album/${res.body.id}
Output
Integer response status 200
Should Be Equal ${res.body} ${get.body}
[Teardown] DELETE /album/${res.body.id}
Find a album non existant
[Documentation] Find non existant album
&{get}= GET /album/9999
Integer response status 404
Find multiples albums
[Documentation] Create two albums and find them
&{res}= POST
... /album
... {"name": "Mama mia"}
Output
Integer response status 201
&{res2}= POST
... /album
... {"name": "Toto"}
Output
Integer response status 201
&{get}= GET /album
Output
Integer response status 200
Should Contain ${get.body.data} ${res.body}
Should Contain ${get.body.data} ${res2.body}
[Teardown] Run Keywords DELETE /album/${res.body.id}
... AND DELETE /album/${res2.body.id}
Find multiples albums filtered
[Documentation] Create two albums and find them
&{res}= POST
... /album
... {"name": "Mamamia"}
Output
Integer response status 201
&{res2}= POST
... /album
... {"name": "jkgnsg"}
Output
Integer response status 201
&{get}= GET /album?name=Mamamia
Output
Integer response status 200
Should Contain ${get.body.data} ${res.body}
Should Not Contain ${get.body.data} ${res2.body}
[Teardown] Run Keywords DELETE /album/${res.body.id}
... AND DELETE /album/${res2.body.id}
Find multiples albums filtered by type
[Documentation] Create two albums and find them
&{res}= POST
... /album
... {"name": "Mama mia"}
Output
Integer response status 201
&{res2}= POST
... /album
... {"name": "kldngsd"}
Output
Integer response status 201
&{get}= GET /album?id=${res.body.id}
Output
Integer response status 200
Should Contain ${get.body.data} ${res.body}
Should Not Contain ${get.body.data} ${res2.body}
[Teardown] Run Keywords DELETE /album/${res.body.id}
... AND DELETE /album/${res2.body.id}
+113
View File
@@ -0,0 +1,113 @@
*** Settings ***
Documentation Tests of the /genre route.
... Ensures that the genre CRUD works corectly.
Resource ../rest.resource
*** Test Cases ***
Create a genre
[Documentation] Create a genre
&{res}= POST
... /genre
... {"name": "Mama mia"}
Output
Integer response status 201
[Teardown] DELETE /genre/${res.body.id}
Duplicate a genre
[Documentation] Duplicate a genre
&{res}= POST
... /genre
... {"name": "Mama mia"}
Output
Integer response status 201
&{res2}= POST
... /genre
... {"name": "Mama mia"}
Output
Integer response status 409
Should Be Equal ${res.body.id} ${res2.body.id}
[Teardown] DELETE /genre/${res.body.id}
Find a genre
[Documentation] Create a genre and find it
&{res}= POST
... /genre
... {"name": "Mama mia"}
Output
Integer response status 201
&{get}= GET /genre/${res.body.id}
Output
Integer response status 200
Should Be Equal ${res.body} ${get.body}
[Teardown] DELETE /genre/${res.body.id}
Find a genre non existant
[Documentation] Find non existant genre
&{get}= GET /genre/9999
Integer response status 404
Find multiples genres
[Documentation] Create two genres and find them
&{res}= POST
... /genre
... {"name": "Mama mia"}
Output
Integer response status 201
&{res2}= POST
... /genre
... {"name": "Toto"}
Output
Integer response status 201
&{get}= GET /genre
Output
Integer response status 200
Should Contain ${get.body.data} ${res.body}
Should Contain ${get.body.data} ${res2.body}
[Teardown] Run Keywords DELETE /genre/${res.body.id}
... AND DELETE /genre/${res2.body.id}
Find multiples genres filtered
[Documentation] Create two genres and find them
&{res}= POST
... /genre
... {"name": "Mamamia"}
Output
Integer response status 201
&{res2}= POST
... /genre
... {"name": "jkgnsg"}
Output
Integer response status 201
&{get}= GET /genre?name=Mamamia
Output
Integer response status 200
Should Contain ${get.body.data} ${res.body}
Should Not Contain ${get.body.data} ${res2.body}
[Teardown] Run Keywords DELETE /genre/${res.body.id}
... AND DELETE /genre/${res2.body.id}
Find multiples genres filtered by type
[Documentation] Create two genres and find them
&{res}= POST
... /genre
... {"name": "Mama mia"}
Output
Integer response status 201
&{res2}= POST
... /genre
... {"name": "kldngsd"}
Output
Integer response status 201
&{get}= GET /genre?id=${res.body.id}
Output
Integer response status 200
Should Contain ${get.body.data} ${res.body}
Should Not Contain ${get.body.data} ${res2.body}
[Teardown] Run Keywords DELETE /genre/${res.body.id}
... AND DELETE /genre/${res2.body.id}