Add lessons on the API. (#78)
This commit is contained in:
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = tab
|
||||
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
POSTGRES_USER=
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_NAME=
|
||||
POSTGRES_HOST=
|
||||
DATABASE_URL=
|
||||
JWT_SECRET=
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,4 +5,7 @@ pyvenv.cfg
|
||||
include
|
||||
.env
|
||||
prisma/migrations/*
|
||||
.vscode
|
||||
.vscode
|
||||
output.xml
|
||||
report.html
|
||||
log.html
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
FROM node:17
|
||||
WORKDIR /app
|
||||
COPY ./package.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD npx prisma generate ; npx prisma migrate dev ; npm run start:dev
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"plugins": [{
|
||||
"name": "@nestjs/swagger",
|
||||
"options": {
|
||||
"introspectComments": true,
|
||||
"dtoFileNameSuffix": []
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
35
back/package-lock.json
generated
35
back/package-lock.json
generated
@@ -27,7 +27,8 @@
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0"
|
||||
"rxjs": "^7.2.0",
|
||||
"swagger-ui-express": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^8.0.0",
|
||||
@@ -8194,6 +8195,25 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "4.14.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.14.0.tgz",
|
||||
"integrity": "sha512-TBzhheU15s+o54Cgk9qxuYcZMiqSm/SkvKnapoGHOF66kz0Y5aGjpzj5BT/vpBbn6rTPJ9tUYXQxuDWfsjiGMw=="
|
||||
},
|
||||
"node_modules/swagger-ui-express": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.5.0.tgz",
|
||||
"integrity": "sha512-DHk3zFvsxrkcnurGvQlAcLuTDacAVN1JHKDgcba/gr2NFRE4HGwP1YeHIXMiGznkWR4AeS7X5vEblNn4QljuNA==",
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": ">=4.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= v0.10.32"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-observable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||
@@ -15400,6 +15420,19 @@
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true
|
||||
},
|
||||
"swagger-ui-dist": {
|
||||
"version": "4.14.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.14.0.tgz",
|
||||
"integrity": "sha512-TBzhheU15s+o54Cgk9qxuYcZMiqSm/SkvKnapoGHOF66kz0Y5aGjpzj5BT/vpBbn6rTPJ9tUYXQxuDWfsjiGMw=="
|
||||
},
|
||||
"swagger-ui-express": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.5.0.tgz",
|
||||
"integrity": "sha512-DHk3zFvsxrkcnurGvQlAcLuTDacAVN1JHKDgcba/gr2NFRE4HGwP1YeHIXMiGznkWR4AeS7X5vEblNn4QljuNA==",
|
||||
"requires": {
|
||||
"swagger-ui-dist": ">=4.11.0"
|
||||
}
|
||||
},
|
||||
"symbol-observable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0"
|
||||
"rxjs": "^7.2.0",
|
||||
"swagger-ui-express": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^8.0.0",
|
||||
|
||||
26
back/prisma/migrations/20220924080202_/migration.sql
Normal file
26
back/prisma/migrations/20220924080202_/migration.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "DifficultyPoint" AS ENUM ('TwoHands', 'Rhythm', 'NoteCombo', 'Arpeggio', 'Distance', 'LeftHand', 'RightHand', 'LeadHandChange', 'ChordComplexity', 'ChordTiming', 'Length', 'PedalPoint', 'Precision');
|
||||
|
||||
-- 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 "Lesson" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"requiredLvl" INTEGER NOT NULL,
|
||||
"difficulyPoint" "DifficultyPoint" NOT NULL,
|
||||
|
||||
CONSTRAINT "Lesson_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
28
back/prisma/migrations/20220924081654_/migration.sql
Normal file
28
back/prisma/migrations/20220924081654_/migration.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `difficulyPoint` on the `Lesson` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `requiredLvl` on the `Lesson` table. All the data in the column will be lost.
|
||||
- Added the required column `mainSkill` to the `Lesson` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `requiredLevel` to the `Lesson` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Lesson" DROP COLUMN "difficulyPoint",
|
||||
DROP COLUMN "requiredLvl",
|
||||
ADD COLUMN "mainSkill" "DifficultyPoint" NOT NULL,
|
||||
ADD COLUMN "requiredLevel" INTEGER NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "LessonHistory" (
|
||||
"lessonID" INTEGER NOT NULL,
|
||||
"userID" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "LessonHistory_pkey" PRIMARY KEY ("lessonID","userID")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LessonHistory" ADD CONSTRAINT "LessonHistory_userID_fkey" FOREIGN KEY ("userID") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "LessonHistory" ADD CONSTRAINT "LessonHistory_lessonID_fkey" FOREIGN KEY ("lessonID") REFERENCES "Lesson"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
10
back/prisma/migrations/20220926065731_/migration.sql
Normal file
10
back/prisma/migrations/20220926065731_/migration.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `title` on the `Lesson` table. All the data in the column will be lost.
|
||||
- Added the required column `name` to the `Lesson` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Lesson" DROP COLUMN "title",
|
||||
ADD COLUMN "name" TEXT NOT NULL;
|
||||
3
back/prisma/migrations/migration_lock.toml
Normal file
3
back/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -10,8 +10,45 @@ datasource db {
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @default(autoincrement()) @id
|
||||
username String @unique
|
||||
password String
|
||||
email String
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
email String
|
||||
LessonHistory LessonHistory[]
|
||||
}
|
||||
|
||||
model Lesson {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String
|
||||
requiredLevel Int
|
||||
mainSkill Skill
|
||||
LessonHistory LessonHistory[]
|
||||
}
|
||||
|
||||
model LessonHistory {
|
||||
lesson Lesson @relation(fields: [lessonID], references: [id])
|
||||
lessonID Int
|
||||
user User @relation(fields: [userID], references: [id])
|
||||
userID Int
|
||||
|
||||
@@id([lessonID, userID])
|
||||
}
|
||||
|
||||
enum Skill {
|
||||
TwoHands
|
||||
Rhythm
|
||||
NoteCombo
|
||||
Arpeggio
|
||||
Distance
|
||||
LeftHand
|
||||
RightHand
|
||||
LeadHandChange
|
||||
ChordComplexity
|
||||
ChordTiming
|
||||
Length
|
||||
PedalPoint
|
||||
Precision
|
||||
|
||||
@@map("DifficultyPoint")
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ import { PrismaService } from './prisma/prisma.service';
|
||||
import { UsersModule } from './users/users.module';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { LessonModule } from './lesson/lesson.module';
|
||||
|
||||
@Module({
|
||||
imports: [UsersModule, PrismaModule, AuthModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, PrismaService],
|
||||
imports: [UsersModule, PrismaModule, AuthModule, LessonModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, PrismaService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { UsersService } from '../users/users.service';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import PayloadInterface from './interface/payload.interface';
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
||||
constructor(
|
||||
private userService: UsersService,
|
||||
private jwtService: JwtService
|
||||
private jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
async validateUser(username: string, password: string): Promise<PayloadInterface> {
|
||||
const user = await this.userService.user({username});
|
||||
async validateUser(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<PayloadInterface | null> {
|
||||
const user = await this.userService.user({ username });
|
||||
if (user && bcrypt.compareSync(password, user.password)) {
|
||||
return {
|
||||
username: user.username,
|
||||
id: user.id
|
||||
id: user.id,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@@ -26,7 +28,7 @@ export class AuthService {
|
||||
const payload = { username: user.username, id: user.id };
|
||||
const access_token = this.jwtService.sign(payload);
|
||||
return {
|
||||
access_token
|
||||
access_token,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import { Strategy } from 'passport-local';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
@@ -7,15 +6,18 @@ import PayloadInterface from './interface/payload.interface';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private authService: AuthService) {
|
||||
super();
|
||||
}
|
||||
constructor(private authService: AuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<PayloadInterface> {
|
||||
const user = await this.authService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
async validate(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<PayloadInterface> {
|
||||
const user = await this.authService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
18
back/src/lesson/lesson.controller.spec.ts
Normal file
18
back/src/lesson/lesson.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LessonController } from './lesson.controller';
|
||||
|
||||
describe('LessonController', () => {
|
||||
let controller: LessonController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [LessonController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<LessonController>(LessonController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
103
back/src/lesson/lesson.controller.ts
Normal file
103
back/src/lesson/lesson.controller.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Res,
|
||||
Query,
|
||||
Req,
|
||||
Request,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
DefaultValuePipe,
|
||||
BadRequestException,
|
||||
Post,
|
||||
Body,
|
||||
Delete,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Plage } from 'src/models/plage';
|
||||
import { LessonService } from './lesson.service';
|
||||
import { ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
|
||||
import { Prisma, Skill } from '@prisma/client';
|
||||
|
||||
export class Lesson {
|
||||
@ApiProperty()
|
||||
id: number;
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
@ApiProperty()
|
||||
description: string;
|
||||
@ApiProperty()
|
||||
requiredLevel: number;
|
||||
@ApiProperty()
|
||||
mainSkill: Skill;
|
||||
}
|
||||
|
||||
@ApiTags('lessons')
|
||||
@Controller('lesson')
|
||||
export class LessonController {
|
||||
constructor(private lessonService: LessonService) {}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Get all lessons',
|
||||
})
|
||||
@Get()
|
||||
async getAll(
|
||||
@Req() request: Request,
|
||||
@Query() filter: Prisma.LessonWhereInput,
|
||||
@Query('skip', new DefaultValuePipe(0), ParseIntPipe) skip: number,
|
||||
@Query('take', new DefaultValuePipe(20), ParseIntPipe) take: number,
|
||||
): Promise<Plage<Lesson>> {
|
||||
try {
|
||||
const ret = await this.lessonService.getAll({
|
||||
skip,
|
||||
take,
|
||||
where: {
|
||||
...filter,
|
||||
requiredLevel: filter.requiredLevel
|
||||
? +filter.requiredLevel
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
return new Plage(ret, request);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw new BadRequestException(null, e?.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Get a particular lessons',
|
||||
})
|
||||
@Get(':id')
|
||||
async get(@Param('id', ParseIntPipe) id: number): Promise<Lesson> {
|
||||
const ret = await this.lessonService.get(id);
|
||||
if (!ret) throw new NotFoundException();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Create a lessons',
|
||||
})
|
||||
@Post()
|
||||
async post(@Body() lesson: Lesson): Promise<Lesson> {
|
||||
try {
|
||||
return await this.lessonService.create(lesson);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw new BadRequestException(null, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation({
|
||||
summary: 'Delete a lessons',
|
||||
})
|
||||
@Delete(':id')
|
||||
async delete(@Param('id', ParseIntPipe) id: number): Promise<Lesson> {
|
||||
try {
|
||||
return await this.lessonService.delete(id);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw new BadRequestException(null, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
back/src/lesson/lesson.module.ts
Normal file
11
back/src/lesson/lesson.module.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||
import { LessonController } from './lesson.controller';
|
||||
import { LessonService } from './lesson.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [LessonController],
|
||||
providers: [LessonService],
|
||||
})
|
||||
export class LessonModule {}
|
||||
18
back/src/lesson/lesson.service.spec.ts
Normal file
18
back/src/lesson/lesson.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LessonService } from './lesson.service';
|
||||
|
||||
describe('LessonService', () => {
|
||||
let service: LessonService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [LessonService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LessonService>(LessonService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
41
back/src/lesson/lesson.service.ts
Normal file
41
back/src/lesson/lesson.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Lesson, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class LessonService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getAll(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.LessonWhereUniqueInput;
|
||||
where?: Prisma.LessonWhereInput;
|
||||
orderBy?: Prisma.LessonOrderByWithRelationInput;
|
||||
}): Promise<Lesson[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.lesson.findMany({
|
||||
skip,
|
||||
take,
|
||||
cursor,
|
||||
where,
|
||||
orderBy,
|
||||
});
|
||||
}
|
||||
|
||||
async get(id: number): Promise<Lesson | null> {
|
||||
return this.prisma.lesson.findFirst({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async create(lesson: Lesson): Promise<Lesson> {
|
||||
return this.prisma.lesson.create({ data: lesson });
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<Lesson> {
|
||||
return this.prisma.lesson.delete({ where: { id: id } });
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,21 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { PrismaService } from './prisma/prisma.service';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const prismaService = app.get(PrismaService);
|
||||
await prismaService.enableShutdownHooks(app)
|
||||
await prismaService.enableShutdownHooks(app);
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Chromacase')
|
||||
.setDescription('The chromacase API')
|
||||
.setVersion('1.0')
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
51
back/src/models/plage.ts
Normal file
51
back/src/models/plage.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Thanks to https://github.com/Arthi-chaud/Meelo/blob/master/src/pagination/models/paginated-response.ts
|
||||
*/
|
||||
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class Plage<T> {
|
||||
@ApiProperty()
|
||||
metadata: {
|
||||
this: string;
|
||||
next: string | null;
|
||||
previous: string | null;
|
||||
};
|
||||
@ApiProperty()
|
||||
data: T[];
|
||||
|
||||
constructor(data: T[], request: Request | any) {
|
||||
this.data = data;
|
||||
let take = Number(request.query['take'] ?? 20).valueOf();
|
||||
if (take == 0) take = 20;
|
||||
let skipped: number = Number(request.query['skip'] ?? 0).valueOf();
|
||||
if (skipped % take) {
|
||||
skipped += take - (skipped % take);
|
||||
}
|
||||
this.metadata = {
|
||||
this: this.buildUrl(request.path, request.query),
|
||||
next:
|
||||
data.length >= take
|
||||
? this.buildUrl(request.path, {
|
||||
...request.query,
|
||||
skip: skipped + take,
|
||||
})
|
||||
: null,
|
||||
previous: skipped
|
||||
? this.buildUrl(request.path, {
|
||||
...request.query,
|
||||
skip: Math.max(0, skipped - take),
|
||||
})
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
private buildUrl(route: string, queryParameters: any) {
|
||||
if (queryParameters.skip == 0) delete queryParameters.skip;
|
||||
const builtQueryParameters = new URLSearchParams(
|
||||
queryParameters,
|
||||
).toString();
|
||||
if (builtQueryParameters.length) return `${route}?${builtQueryParameters}`;
|
||||
return route;
|
||||
}
|
||||
}
|
||||
@@ -7,55 +7,54 @@ import * as bcrypt from 'bcryptjs';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
[x: string]: any;
|
||||
constructor(private prisma: PrismaService) {}
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async user(
|
||||
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
|
||||
): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: userWhereUniqueInput,
|
||||
});
|
||||
}
|
||||
async user(
|
||||
userWhereUniqueInput: Prisma.UserWhereUniqueInput,
|
||||
): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: userWhereUniqueInput,
|
||||
});
|
||||
}
|
||||
|
||||
async users(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.UserWhereUniqueInput;
|
||||
where?: Prisma.UserWhereInput;
|
||||
orderBy?: Prisma.UserOrderByWithRelationInput;
|
||||
}): Promise<User[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.user.findMany({
|
||||
skip,
|
||||
take,
|
||||
cursor,
|
||||
where,
|
||||
orderBy,
|
||||
});
|
||||
}
|
||||
async users(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.UserWhereUniqueInput;
|
||||
where?: Prisma.UserWhereInput;
|
||||
orderBy?: Prisma.UserOrderByWithRelationInput;
|
||||
}): Promise<User[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.user.findMany({
|
||||
skip,
|
||||
take,
|
||||
cursor,
|
||||
where,
|
||||
orderBy,
|
||||
});
|
||||
}
|
||||
|
||||
async createUser(data: Prisma.UserCreateInput): Promise<User> {
|
||||
data.password = await bcrypt.hash(data.password, 8)
|
||||
return this.prisma.user.create({
|
||||
data,
|
||||
});
|
||||
}
|
||||
async createUser(data: Prisma.UserCreateInput): Promise<User> {
|
||||
data.password = await bcrypt.hash(data.password, 8);
|
||||
return this.prisma.user.create({
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async updateUser(params: {
|
||||
where: Prisma.UserWhereUniqueInput;
|
||||
data: Prisma.UserUpdateInput;
|
||||
}): Promise<User> {
|
||||
const { where, data } = params;
|
||||
return this.prisma.user.update({
|
||||
data,
|
||||
where,
|
||||
});
|
||||
}
|
||||
async updateUser(params: {
|
||||
where: Prisma.UserWhereUniqueInput;
|
||||
data: Prisma.UserUpdateInput;
|
||||
}): Promise<User> {
|
||||
const { where, data } = params;
|
||||
return this.prisma.user.update({
|
||||
data,
|
||||
where,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
|
||||
return this.prisma.user.delete({
|
||||
where,
|
||||
});
|
||||
}
|
||||
async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
|
||||
return this.prisma.user.delete({
|
||||
where,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
80
back/test/robot/lesson/lesson.robot
Normal file
80
back/test/robot/lesson/lesson.robot
Normal file
@@ -0,0 +1,80 @@
|
||||
*** Settings ***
|
||||
Documentation Tests of the /lesson route.
|
||||
... Ensures that the lesson CRUD works corectly.
|
||||
|
||||
Resource ../rest.resource
|
||||
|
||||
|
||||
*** Test Cases ***
|
||||
Post a lesson
|
||||
[Documentation] Get a lesson
|
||||
&{res}= POST
|
||||
... /lesson
|
||||
... {"name": "toto", "requiredLevel": 3, "mainSkill": "TwoHands", "description": "What am i doing"}
|
||||
Output
|
||||
Integer response status 201
|
||||
[Teardown] DELETE /lesson/${res.body.id}
|
||||
|
||||
Get a lesson
|
||||
[Documentation] Get a lesson
|
||||
&{res}= POST
|
||||
... /lesson
|
||||
... {"name": "toto", "requiredLevel": 3, "mainSkill": "TwoHands", "description": "What am i doing"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{get}= GET /lesson/${res.body.id}
|
||||
Output
|
||||
Should Be Equal ${res.body} ${get.body}
|
||||
[Teardown] DELETE /lesson/${res.body.id}
|
||||
|
||||
Get a non-lesson
|
||||
[Documentation] Get a lesson
|
||||
&{get}= GET /lesson/toto
|
||||
Output
|
||||
Integer response status 400
|
||||
|
||||
Get a not-existing-lesson
|
||||
[Documentation] Get a lesson
|
||||
&{get}= GET /lesson/99999999
|
||||
Output
|
||||
Integer response status 404
|
||||
|
||||
Get all lessons
|
||||
[Documentation] Get a lesson
|
||||
&{res}= POST
|
||||
... /lesson
|
||||
... {"name": "toto", "requiredLevel": 3, "mainSkill": "TwoHands", "description": "What am i doing"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{res2}= POST
|
||||
... /lesson
|
||||
... {"name": "tata", "requiredLevel": 3, "mainSkill": "TwoHands", "description": "What am i doing"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{get}= GET /lesson
|
||||
Output
|
||||
Should Contain ${get.body.data} ${res.body}
|
||||
Should Contain ${get.body.data} ${res2.body}
|
||||
|
||||
[Teardown] Run Keywords DELETE /lesson/${res.body.id}
|
||||
... AND DELETE /lesson/${res2.body.id}
|
||||
|
||||
Get all lessons filtered
|
||||
[Documentation] Get a lesson
|
||||
&{res}= POST
|
||||
... /lesson
|
||||
... {"name": "toto", "requiredLevel": 3, "mainSkill": "TwoHands", "description": "What am i doing"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{res2}= POST
|
||||
... /lesson
|
||||
... {"name": "tata", "requiredLevel": 3, "mainSkill": "Distance", "description": "What am i doing"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{get}= GET /lesson?mainSkill=Distance
|
||||
Output
|
||||
Should Not Contain ${get.body.data} ${res.body}
|
||||
Should Contain ${get.body.data} ${res2.body}
|
||||
|
||||
[Teardown] Run Keywords DELETE /lesson/${res.body.id}
|
||||
... AND DELETE /lesson/${res2.body.id}
|
||||
@@ -1,14 +1,14 @@
|
||||
*** Settings ***
|
||||
Documentation Tests of the /users route.
|
||||
... Ensures that the users CRUD works corectly.
|
||||
|
||||
Resource ../rest.resource
|
||||
|
||||
|
||||
*** Keywords ***
|
||||
*** Test Cases ***
|
||||
Create a user
|
||||
[Documentation] Create a user
|
||||
POST /users {"username": "i-don-t-exist", "password": "pass", "email": "wow@gmail.com"}
|
||||
&{res}= POST /users {"username": "louis-boufon", "password": "pass", "email": "wow@gmail.com"}
|
||||
Output
|
||||
Integer response status 201
|
||||
[Teardown] DELETE /users/1
|
||||
[Teardown] DELETE /users/${res.body.id}
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
|
||||
12
node_modules/.yarn-integrity
generated
vendored
12
node_modules/.yarn-integrity
generated
vendored
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"systemParams": "linux-x64-108",
|
||||
"modulesFolders": [
|
||||
"node_modules"
|
||||
],
|
||||
"flags": [],
|
||||
"linkedModules": [],
|
||||
"topLevelPatterns": [],
|
||||
"lockfileEntries": {},
|
||||
"files": [],
|
||||
"artifacts": {}
|
||||
}
|
||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Chromacase",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
Reference in New Issue
Block a user