feat: add song crd and tests (#80)
Co-authored-by: Zoe Roux <zoe.roux@sdg.moe>
This commit is contained in:
7
back/.gitignore
vendored
7
back/.gitignore
vendored
@@ -32,4 +32,9 @@ lerna-debug.log*
|
|||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# Robots tests
|
||||||
|
log.html
|
||||||
|
output.xml
|
||||||
|
report.html
|
||||||
|
|||||||
61
back/prisma/migrations/20220924084638_/migration.sql
Normal file
61
back/prisma/migrations/20220924084638_/migration.sql
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Song" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"artistId" INTEGER NOT NULL,
|
||||||
|
"albumId" INTEGER,
|
||||||
|
"genreId" INTEGER NOT NULL,
|
||||||
|
"difficulties" JSONB NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Song_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Genre" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Genre_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Artist" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Artist_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Album" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Album_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Song_name_key" ON "Song"("name");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Song" ADD CONSTRAINT "Song_genreId_fkey" FOREIGN KEY ("genreId") REFERENCES "Genre"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Song" ADD CONSTRAINT "Song_albumId_fkey" FOREIGN KEY ("albumId") REFERENCES "Album"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
8
back/prisma/migrations/20220926080922_/migration.sql
Normal file
8
back/prisma/migrations/20220926080922_/migration.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Song" DROP CONSTRAINT "Song_genreId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Song" ALTER COLUMN "genreId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Song" ADD CONSTRAINT "Song_genreId_fkey" FOREIGN KEY ("genreId") REFERENCES "Genre"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
8
back/prisma/migrations/20220926082047_/migration.sql
Normal file
8
back/prisma/migrations/20220926082047_/migration.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Song" DROP CONSTRAINT "Song_artistId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Song" ALTER COLUMN "artistId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Song" ADD CONSTRAINT "Song_artistId_fkey" FOREIGN KEY ("artistId") REFERENCES "Artist"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
8
back/prisma/migrations/20220926084154_/migration.sql
Normal file
8
back/prisma/migrations/20220926084154_/migration.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `description` on the `Song` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Song" DROP COLUMN "description";
|
||||||
@@ -17,6 +17,39 @@ model User {
|
|||||||
LessonHistory LessonHistory[]
|
LessonHistory LessonHistory[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Song {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String @unique
|
||||||
|
artistId Int?
|
||||||
|
artist Artist? @relation(fields: [artistId], references: [id])
|
||||||
|
albumId Int?
|
||||||
|
album Album? @relation(fields: [albumId], references: [id])
|
||||||
|
genreId Int?
|
||||||
|
genre Genre? @relation(fields: [genreId], references: [id])
|
||||||
|
difficulties Json
|
||||||
|
}
|
||||||
|
|
||||||
|
model Genre {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
|
||||||
|
Song Song[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Artist {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
|
||||||
|
Song Song[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Album {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
|
||||||
|
Song Song[]
|
||||||
|
}
|
||||||
|
|
||||||
model Lesson {
|
model Lesson {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
|
|||||||
@@ -3,20 +3,20 @@ import { AppController } from './app.controller';
|
|||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
describe('AppController', () => {
|
describe('AppController', () => {
|
||||||
let appController: AppController;
|
let appController: AppController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const app: TestingModule = await Test.createTestingModule({
|
const app: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
appController = app.get<AppController>(AppController);
|
appController = app.get<AppController>(AppController);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('root', () => {
|
describe('root', () => {
|
||||||
it('should return "Hello World!"', () => {
|
it('should return "Hello World!"', () => {
|
||||||
expect(appController.getHello()).toBe('Hello World!');
|
expect(appController.getHello()).toBe('Hello World!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { AppService } from './app.service';
|
|||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(private readonly appService: AppService) {}
|
constructor(private readonly appService: AppService) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
getHello(): string {
|
getHello(): string {
|
||||||
return this.appService.getHello();
|
return this.appService.getHello();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { PrismaService } from './prisma/prisma.service';
|
|||||||
import { UsersModule } from './users/users.module';
|
import { UsersModule } from './users/users.module';
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import { SongModule } from './song/song.module';
|
||||||
import { LessonModule } from './lesson/lesson.module';
|
import { LessonModule } from './lesson/lesson.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UsersModule, PrismaModule, AuthModule, LessonModule],
|
imports: [UsersModule, PrismaModule, AuthModule, SongModule, LessonModule],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService, PrismaService],
|
providers: [AppService, PrismaService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
getHello(): string {
|
getHello(): string {
|
||||||
return 'Hello World!';
|
return 'Hello World!';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { AuthController } from './auth.controller';
|
import { AuthController } from './auth.controller';
|
||||||
|
|
||||||
describe('AuthController', () => {
|
describe('AuthController', () => {
|
||||||
let controller: AuthController;
|
let controller: AuthController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<AuthController>(AuthController);
|
controller = module.get<AuthController>(AuthController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
Delete,
|
Delete,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||||
@@ -62,7 +62,7 @@ export class AuthController {
|
|||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOkResponse({ description: 'Successfully deleted', type: User})
|
@ApiOkResponse({ description: 'Successfully deleted', type: User })
|
||||||
@ApiUnauthorizedResponse({ description: 'Invalid token' })
|
@ApiUnauthorizedResponse({ description: 'Invalid token' })
|
||||||
@Delete('me')
|
@Delete('me')
|
||||||
deleteSelf(@Request() req: any): Promise<User> {
|
deleteSelf(@Request() req: any): Promise<User> {
|
||||||
|
|||||||
@@ -10,16 +10,20 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
import { JwtStrategy } from './jwt.strategy';
|
import { JwtStrategy } from './jwt.strategy';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule, UsersModule, PassportModule,
|
imports: [
|
||||||
JwtModule.registerAsync({
|
ConfigModule,
|
||||||
imports: [ConfigModule],
|
UsersModule,
|
||||||
useFactory: async (configService: ConfigService) => ({
|
PassportModule,
|
||||||
secret: configService.get('JWT_SECRET'),
|
JwtModule.registerAsync({
|
||||||
signOptions: { expiresIn: '1h' },
|
imports: [ConfigModule],
|
||||||
}),
|
useFactory: async (configService: ConfigService) => ({
|
||||||
inject: [ConfigService],
|
secret: configService.get('JWT_SECRET'),
|
||||||
})],
|
signOptions: { expiresIn: '1h' },
|
||||||
providers: [AuthService, LocalStrategy, JwtStrategy],
|
}),
|
||||||
controllers: [AuthController]
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [AuthService, LocalStrategy, JwtStrategy],
|
||||||
|
controllers: [AuthController],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
describe('AuthService', () => {
|
describe('AuthService', () => {
|
||||||
let service: AuthService;
|
let service: AuthService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [AuthService],
|
providers: [AuthService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<AuthService>(AuthService);
|
service = module.get<AuthService>(AuthService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Constants {
|
export class Constants {
|
||||||
|
|
||||||
constructor(private configService: ConfigService) {}
|
constructor(private configService: ConfigService) {}
|
||||||
|
|
||||||
getSecret = () => {
|
getSecret = () => {
|
||||||
return this.configService.get("JWT_SECRET");
|
return this.configService.get('JWT_SECRET');
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsNotEmpty } from "class-validator";
|
import { IsNotEmpty } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class LoginDto {
|
export class LoginDto {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsNotEmpty } from "class-validator";
|
import { IsNotEmpty } from 'class-validator';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
export class RegisterDto {
|
export class RegisterDto {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default interface PayloadInterface {
|
export default interface PayloadInterface {
|
||||||
username: string;
|
username: string;
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
@@ -6,15 +5,15 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
constructor(private configService: ConfigService) {
|
constructor(private configService: ConfigService) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
ignoreExpiration: false,
|
ignoreExpiration: false,
|
||||||
secretOrKey: configService.get('JWT_SECRET'),
|
secretOrKey: configService.get('JWT_SECRET'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: any) {
|
async validate(payload: any) {
|
||||||
return { id: payload.id, username: payload.username };
|
return { id: payload.id, username: payload.username };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { LessonController } from './lesson.controller';
|
import { LessonController } from './lesson.controller';
|
||||||
|
|
||||||
describe('LessonController', () => {
|
describe('LessonController', () => {
|
||||||
let controller: LessonController;
|
let controller: LessonController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [LessonController],
|
controllers: [LessonController],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<LessonController>(LessonController);
|
controller = module.get<LessonController>(LessonController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { LessonService } from './lesson.service';
|
import { LessonService } from './lesson.service';
|
||||||
|
|
||||||
describe('LessonService', () => {
|
describe('LessonService', () => {
|
||||||
let service: LessonService;
|
let service: LessonService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [LessonService],
|
providers: [LessonService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<LessonService>(LessonService);
|
service = module.get<LessonService>(LessonService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import { PrismaService } from './prisma/prisma.service';
|
|||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
const prismaService = app.get(PrismaService);
|
const prismaService = app.get(PrismaService);
|
||||||
await prismaService.enableShutdownHooks(app);
|
await prismaService.enableShutdownHooks(app);
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Chromacase')
|
.setTitle('Chromacase')
|
||||||
.setDescription('The chromacase API')
|
.setDescription('The chromacase API')
|
||||||
.setVersion('1.0')
|
.setVersion('1.0')
|
||||||
.build();
|
.build();
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('api', app, document);
|
SwaggerModule.setup('api', app, document);
|
||||||
|
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [PrismaService],
|
providers: [PrismaService],
|
||||||
exports: [PrismaService]
|
exports: [PrismaService],
|
||||||
})
|
})
|
||||||
export class PrismaModule {}
|
export class PrismaModule {}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
describe('PrismaService', () => {
|
describe('PrismaService', () => {
|
||||||
let service: PrismaService;
|
let service: PrismaService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [PrismaService],
|
providers: [PrismaService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<PrismaService>(PrismaService);
|
service = module.get<PrismaService>(PrismaService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { PrismaClient } from '@prisma/client';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.$connect();
|
await this.$connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableShutdownHooks(app: INestApplication) {
|
async enableShutdownHooks(app: INestApplication) {
|
||||||
this.$on('beforeExit', async () => {
|
this.$on('beforeExit', async () => {
|
||||||
await app.close();
|
await app.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
back/src/song/dto/create-song.dto.ts
Normal file
12
back/src/song/dto/create-song.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateSongDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty()
|
||||||
|
difficulties: object;
|
||||||
|
}
|
||||||
18
back/src/song/song.controller.spec.ts
Normal file
18
back/src/song/song.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { SongController } from './song.controller';
|
||||||
|
|
||||||
|
describe('SongController', () => {
|
||||||
|
let controller: SongController;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [SongController],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<SongController>(SongController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
65
back/src/song/song.controller.ts
Normal file
65
back/src/song/song.controller.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
DefaultValuePipe,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
NotFoundException,
|
||||||
|
Param,
|
||||||
|
ParseIntPipe,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Req,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Plage } from 'src/models/plage';
|
||||||
|
import { CreateSongDto } from './dto/create-song.dto';
|
||||||
|
import { SongService } from './song.service';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { Prisma, Song } from '@prisma/client';
|
||||||
|
|
||||||
|
@Controller('song')
|
||||||
|
export class SongController {
|
||||||
|
constructor(private readonly songService: SongService) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async create(@Body() createSongDto: CreateSongDto) {
|
||||||
|
return await this.songService.createSong(createSongDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
async remove(@Param('id', ParseIntPipe) id: number) {
|
||||||
|
return await this.songService.deleteSong({ 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<Song>> {
|
||||||
|
try {
|
||||||
|
const ret = await this.songService.songs({
|
||||||
|
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) {
|
||||||
|
let res = await this.songService.song({ id });
|
||||||
|
|
||||||
|
if (res === null) throw new NotFoundException('Song not found');
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
back/src/song/song.module.ts
Normal file
11
back/src/song/song.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { SongService } from './song.service';
|
||||||
|
import { SongController } from './song.controller';
|
||||||
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [SongService],
|
||||||
|
controllers: [SongController],
|
||||||
|
})
|
||||||
|
export class SongModule {}
|
||||||
18
back/src/song/song.service.spec.ts
Normal file
18
back/src/song/song.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { SongService } from './song.service';
|
||||||
|
|
||||||
|
describe('SongService', () => {
|
||||||
|
let service: SongService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [SongService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<SongService>(SongService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
45
back/src/song/song.service.ts
Normal file
45
back/src/song/song.service.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma, Song } from '@prisma/client';
|
||||||
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SongService {
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async createSong(data: Prisma.SongCreateInput): Promise<Song> {
|
||||||
|
return this.prisma.song.create({
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async song(
|
||||||
|
songWhereUniqueInput: Prisma.SongWhereUniqueInput,
|
||||||
|
): Promise<Song | null> {
|
||||||
|
return this.prisma.song.findUnique({
|
||||||
|
where: songWhereUniqueInput,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async songs(params: {
|
||||||
|
skip?: number;
|
||||||
|
take?: number;
|
||||||
|
cursor?: Prisma.SongWhereUniqueInput;
|
||||||
|
where?: Prisma.SongWhereInput;
|
||||||
|
orderBy?: Prisma.SongOrderByWithRelationInput;
|
||||||
|
}): Promise<Song[]> {
|
||||||
|
const { skip, take, cursor, where, orderBy } = params;
|
||||||
|
return this.prisma.song.findMany({
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
cursor,
|
||||||
|
where,
|
||||||
|
orderBy,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSong(where: Prisma.SongWhereUniqueInput): Promise<Song> {
|
||||||
|
return this.prisma.song.delete({
|
||||||
|
where,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,18 +3,18 @@ import { UsersController } from './users.controller';
|
|||||||
import { UsersService } from './users.service';
|
import { UsersService } from './users.service';
|
||||||
|
|
||||||
describe('UsersController', () => {
|
describe('UsersController', () => {
|
||||||
let controller: UsersController;
|
let controller: UsersController;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<UsersController>(UsersController);
|
controller = module.get<UsersController>(UsersController);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
expect(controller).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { UsersController } from './users.controller';
|
|||||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
exports: [UsersService]
|
exports: [UsersService],
|
||||||
})
|
})
|
||||||
export class UsersModule {}
|
export class UsersModule {}
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { UsersService } from './users.service';
|
import { UsersService } from './users.service';
|
||||||
|
|
||||||
describe('UsersService', () => {
|
describe('UsersService', () => {
|
||||||
let service: UsersService;
|
let service: UsersService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [UsersService],
|
providers: [UsersService],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<UsersService>(UsersService);
|
service = module.get<UsersService>(UsersService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ import * as request from 'supertest';
|
|||||||
import { AppModule } from './../src/app.module';
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
describe('AppController (e2e)', () => {
|
describe('AppController (e2e)', () => {
|
||||||
let app: INestApplication;
|
let app: INestApplication;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
imports: [AppModule],
|
imports: [AppModule],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
app = moduleFixture.createNestApplication();
|
app = moduleFixture.createNestApplication();
|
||||||
await app.init();
|
await app.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
it('/ (GET)', () => {
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.get('/')
|
.get('/')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Hello World!');
|
.expect('Hello World!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
1
back/test/robot/env/lib64
vendored
Symbolic link
1
back/test/robot/env/lib64
vendored
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
lib
|
||||||
84
back/test/robot/songs/songs.robot
Normal file
84
back/test/robot/songs/songs.robot
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
*** Settings ***
|
||||||
|
Documentation Tests of the /song route.
|
||||||
|
... Ensures that the songs CRUD works corectly.
|
||||||
|
Resource ../rest.resource
|
||||||
|
|
||||||
|
|
||||||
|
*** Keywords ***
|
||||||
|
*** Test Cases ***
|
||||||
|
Create a song
|
||||||
|
[Documentation] Create a song
|
||||||
|
&{res}= POST /song {"name": "Mama mia", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
[Teardown] DELETE /song/${res.body.id}
|
||||||
|
|
||||||
|
Find a song
|
||||||
|
[Documentation] Create a song and find it
|
||||||
|
&{res}= POST /song {"name": "Mama mia", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
&{get}= GET /song/${res.body.id}
|
||||||
|
Output
|
||||||
|
Integer response status 200
|
||||||
|
Should Be Equal ${res.body} ${get.body}
|
||||||
|
[Teardown] DELETE /song/${res.body.id}
|
||||||
|
|
||||||
|
|
||||||
|
Find a song non existant
|
||||||
|
[Documentation] Find non existant song
|
||||||
|
&{get}= GET /song/9999
|
||||||
|
Integer response status 404
|
||||||
|
|
||||||
|
Find multiples songs
|
||||||
|
[Documentation] Create two songs and find them
|
||||||
|
&{res}= POST /song {"name": "Mama mia", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
&{res2}= POST /song {"name": "Here we go again", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
|
||||||
|
&{get}= GET /song
|
||||||
|
Output
|
||||||
|
Integer response status 200
|
||||||
|
Should Contain ${get.body.data} ${res.body}
|
||||||
|
Should Contain ${get.body.data} ${res2.body}
|
||||||
|
[Teardown] Run Keywords DELETE /song/${res.body.id}
|
||||||
|
... AND DELETE /song/${res2.body.id}
|
||||||
|
|
||||||
|
Find multiples songs filtered
|
||||||
|
[Documentation] Create two songs and find them
|
||||||
|
&{res}= POST /song {"name": "Mamamia", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
&{res2}= POST /song {"name": "Here we go again", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
|
||||||
|
&{get}= GET /song?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 /song/${res.body.id}
|
||||||
|
... AND DELETE /song/${res2.body.id}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Find multiples songs filtered by type
|
||||||
|
[Documentation] Create two songs and find them
|
||||||
|
&{res}= POST /song {"name": "Mamamia", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
&{res2}= POST /song {"name": "Here we go again", "difficulties": {}}
|
||||||
|
Output
|
||||||
|
Integer response status 201
|
||||||
|
|
||||||
|
&{get}= GET /song?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 /song/${res.body.id}
|
||||||
|
... AND DELETE /song/${res2.body.id}
|
||||||
Reference in New Issue
Block a user