feat: genre and album populate and CRUD
This commit is contained in:
@@ -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;
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CreateAlbumDto {
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
artist?: number;
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CreateGenreDto {
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user