Add populate script and artist api
This commit is contained in:
11
back/package-lock.json
generated
11
back/package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/passport": "^1.0.9",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
@@ -3099,6 +3100,11 @@
|
||||
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.13.2",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz",
|
||||
@@ -11552,6 +11558,11 @@
|
||||
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
|
||||
"dev": true
|
||||
},
|
||||
"class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
|
||||
},
|
||||
"class-validator": {
|
||||
"version": "0.13.2",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/passport": "^1.0.9",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
|
||||
10
back/prisma/migrations/20221023094610_/migration.sql
Normal file
10
back/prisma/migrations/20221023094610_/migration.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `midiPath` to the `Song` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `musicXmlPath` to the `Song` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Song" ADD COLUMN "midiPath" TEXT NOT NULL,
|
||||
ADD COLUMN "musicXmlPath" TEXT NOT NULL;
|
||||
8
back/prisma/migrations/20221024161556_dev/migration.sql
Normal file
8
back/prisma/migrations/20221024161556_dev/migration.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[name]` on the table `Album` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Album_name_key" ON "Album"("name");
|
||||
12
back/prisma/migrations/20221024170544_/migration.sql
Normal file
12
back/prisma/migrations/20221024170544_/migration.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[name]` on the table `Artist` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[name]` on the table `Genre` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Artist_name_key" ON "Artist"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Genre_name_key" ON "Genre"("name");
|
||||
@@ -1,87 +1,89 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
email String
|
||||
LessonHistory LessonHistory[]
|
||||
id Int @id @default(autoincrement())
|
||||
username String @unique
|
||||
password String
|
||||
email String
|
||||
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
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
midiPath String
|
||||
musicXmlPath String
|
||||
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
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
|
||||
Song Song[]
|
||||
Song Song[]
|
||||
}
|
||||
|
||||
model Artist {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
|
||||
Song Song[]
|
||||
Song Song[]
|
||||
}
|
||||
|
||||
model Album {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
|
||||
Song Song[]
|
||||
Song Song[]
|
||||
}
|
||||
|
||||
model Lesson {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String
|
||||
requiredLevel Int
|
||||
mainSkill Skill
|
||||
LessonHistory LessonHistory[]
|
||||
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
|
||||
lesson Lesson @relation(fields: [lessonID], references: [id])
|
||||
lessonID Int
|
||||
user User @relation(fields: [userID], references: [id])
|
||||
userID Int
|
||||
|
||||
@@id([lessonID, userID])
|
||||
@@id([lessonID, userID])
|
||||
}
|
||||
|
||||
enum Skill {
|
||||
TwoHands
|
||||
Rhythm
|
||||
NoteCombo
|
||||
Arpeggio
|
||||
Distance
|
||||
LeftHand
|
||||
RightHand
|
||||
LeadHandChange
|
||||
ChordComplexity
|
||||
ChordTiming
|
||||
Length
|
||||
PedalPoint
|
||||
Precision
|
||||
TwoHands
|
||||
Rhythm
|
||||
NoteCombo
|
||||
Arpeggio
|
||||
Distance
|
||||
LeftHand
|
||||
RightHand
|
||||
LeadHandChange
|
||||
ChordComplexity
|
||||
ChordTiming
|
||||
Length
|
||||
PedalPoint
|
||||
Precision
|
||||
|
||||
@@map("DifficultyPoint")
|
||||
@@map("DifficultyPoint")
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ import { PrismaModule } from './prisma/prisma.module';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { SongModule } from './song/song.module';
|
||||
import { LessonModule } from './lesson/lesson.module';
|
||||
import { ArtistController } from './artist/artist.controller';
|
||||
import { ArtistService } from './artist/artist.service';
|
||||
|
||||
@Module({
|
||||
imports: [UsersModule, PrismaModule, AuthModule, SongModule, LessonModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService, PrismaService],
|
||||
controllers: [AppController, ArtistController],
|
||||
providers: [AppService, PrismaService, ArtistService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
18
back/src/artist/artist.controller.spec.ts
Normal file
18
back/src/artist/artist.controller.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ArtistController } from './artist.controller';
|
||||
|
||||
describe('ArtistController', () => {
|
||||
let controller: ArtistController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ArtistController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ArtistController>(ArtistController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
70
back/src/artist/artist.controller.ts
Normal file
70
back/src/artist/artist.controller.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
ConflictException,
|
||||
Controller,
|
||||
DefaultValuePipe,
|
||||
Delete,
|
||||
Get,
|
||||
NotFoundException,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { Plage } from 'src/models/plage';
|
||||
import { CreateArtistDto } from './dto/create-artist.dto';
|
||||
import { Request } from 'express';
|
||||
import { ArtistService } from './artist.service';
|
||||
import { Prisma, Artist } from '@prisma/client';
|
||||
|
||||
@Controller('artist')
|
||||
export class ArtistController {
|
||||
constructor(private readonly service: ArtistService) {}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateArtistDto) {
|
||||
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<Artist>> {
|
||||
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('Artist not found');
|
||||
return res;
|
||||
}
|
||||
}
|
||||
18
back/src/artist/artist.service.spec.ts
Normal file
18
back/src/artist/artist.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ArtistService } from './artist.service';
|
||||
|
||||
describe('ArtistService', () => {
|
||||
let service: ArtistService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [ArtistService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<ArtistService>(ArtistService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
43
back/src/artist/artist.service.ts
Normal file
43
back/src/artist/artist.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, Artist } from '@prisma/client';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ArtistService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async create(data: Prisma.ArtistCreateInput): Promise<Artist> {
|
||||
return this.prisma.artist.create({
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async get(where: Prisma.ArtistWhereUniqueInput): Promise<Artist | null> {
|
||||
return this.prisma.artist.findUnique({
|
||||
where,
|
||||
});
|
||||
}
|
||||
|
||||
async list(params: {
|
||||
skip?: number;
|
||||
take?: number;
|
||||
cursor?: Prisma.ArtistWhereUniqueInput;
|
||||
where?: Prisma.ArtistWhereInput;
|
||||
orderBy?: Prisma.ArtistOrderByWithRelationInput;
|
||||
}): Promise<Artist[]> {
|
||||
const { skip, take, cursor, where, orderBy } = params;
|
||||
return this.prisma.artist.findMany({
|
||||
skip,
|
||||
take,
|
||||
cursor,
|
||||
where,
|
||||
orderBy,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(where: Prisma.ArtistWhereUniqueInput): Promise<Artist> {
|
||||
return this.prisma.artist.delete({
|
||||
where,
|
||||
});
|
||||
}
|
||||
}
|
||||
8
back/src/artist/dto/create-artist.dto.ts
Normal file
8
back/src/artist/dto/create-artist.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CreateArtistDto {
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { PrismaService } from './prisma/prisma.service';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
@@ -16,6 +17,7 @@ async function bootstrap() {
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
await app.listen(3000);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -9,4 +9,21 @@ export class CreateSongDto {
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
difficulties: object;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
midiPath: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ApiProperty()
|
||||
musicXmlPath: string;
|
||||
|
||||
@ApiProperty()
|
||||
artist?: number;
|
||||
|
||||
@ApiProperty()
|
||||
album?: number;
|
||||
|
||||
@ApiProperty()
|
||||
genre?: number;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
ConflictException,
|
||||
Controller,
|
||||
DefaultValuePipe,
|
||||
Delete,
|
||||
@@ -11,20 +12,57 @@ import {
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
StreamableFile,
|
||||
} 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';
|
||||
import { createReadStream } from 'fs';
|
||||
|
||||
@Controller('song')
|
||||
export class SongController {
|
||||
constructor(private readonly songService: SongService) {}
|
||||
|
||||
@Get(':id/midi')
|
||||
async getMidi(@Param('id', ParseIntPipe) id: number) {
|
||||
const song = await this.songService.song({ id });
|
||||
if (!song) throw new NotFoundException('Song not found');
|
||||
|
||||
const file = createReadStream(song.midiPath);
|
||||
return new StreamableFile(file);
|
||||
}
|
||||
|
||||
@Get(':id/musicXml')
|
||||
async getMusicXml(@Param('id', ParseIntPipe) id: number) {
|
||||
const song = await this.songService.song({ id });
|
||||
if (!song) throw new NotFoundException('Song not found');
|
||||
|
||||
const file = createReadStream(song.midiPath);
|
||||
return new StreamableFile(file);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() createSongDto: CreateSongDto) {
|
||||
return await this.songService.createSong(createSongDto);
|
||||
try {
|
||||
return await this.songService.createSong({
|
||||
...createSongDto,
|
||||
artist: createSongDto.artist
|
||||
? { connect: { id: createSongDto.artist } }
|
||||
: undefined,
|
||||
album: createSongDto.album
|
||||
? { connect: { id: createSongDto.album } }
|
||||
: undefined,
|
||||
genre: createSongDto.genre
|
||||
? { connect: { id: createSongDto.genre } }
|
||||
: undefined,
|
||||
});
|
||||
} catch {
|
||||
throw new ConflictException(
|
||||
await this.songService.song({ name: createSongDto.name }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@@ -57,7 +95,7 @@ export class SongController {
|
||||
|
||||
@Get(':id')
|
||||
async findOne(@Param('id', ParseIntPipe) id: number) {
|
||||
let res = await this.songService.song({ id });
|
||||
const res = await this.songService.song({ id });
|
||||
|
||||
if (res === null) throw new NotFoundException('Song not found');
|
||||
return res;
|
||||
|
||||
@@ -1,84 +1,108 @@
|
||||
*** Settings ***
|
||||
Documentation Tests of the /song route.
|
||||
... Ensures that the songs CRUD works corectly.
|
||||
Resource ../rest.resource
|
||||
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": {}}
|
||||
&{res}= POST
|
||||
... /song
|
||||
... {"name": "Mama mia", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
[Teardown] DELETE /song/${res.body.id}
|
||||
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": {}}
|
||||
&{res}= POST
|
||||
... /song
|
||||
... {"name": "Mama mia", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{get}= GET /song/${res.body.id}
|
||||
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}
|
||||
|
||||
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
|
||||
&{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": {}}
|
||||
&{res}= POST
|
||||
... /song
|
||||
... {"name": "Mama mia", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{res2}= POST /song {"name": "Here we go again", "difficulties": {}}
|
||||
Output
|
||||
Integer response status 201
|
||||
Integer response status 201
|
||||
&{res2}= POST
|
||||
... /song
|
||||
... {"name": "Toto", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
|
||||
&{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}
|
||||
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": {}}
|
||||
&{res}= POST
|
||||
... /song
|
||||
... {"name": "Mamamia", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{res2}= POST /song {"name": "Here we go again", "difficulties": {}}
|
||||
Integer response status 201
|
||||
&{res2}= POST
|
||||
... /song
|
||||
... {"name": "jkgnsg", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
Integer response status 201
|
||||
|
||||
&{get}= GET /song?name=Mamamia
|
||||
&{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}
|
||||
|
||||
|
||||
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": {}}
|
||||
&{res}= POST
|
||||
... /song
|
||||
... {"name": "Mama mia", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
&{res2}= POST /song {"name": "Here we go again", "difficulties": {}}
|
||||
Integer response status 201
|
||||
&{res2}= POST
|
||||
... /song
|
||||
... {"name": "kldngsd", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
Integer response status 201
|
||||
|
||||
&{get}= GET /song?id=${res.body.id}
|
||||
&{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}
|
||||
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}
|
||||
|
||||
Get midi file
|
||||
&{res}= POST
|
||||
... /song
|
||||
... {"name": "Mama mia", "difficulties": {}, "midiPath": "/musics/Beethoven-125-4.midi", "musicXmlPath": "/musics/Beethoven-125-4.mxl"}
|
||||
Output
|
||||
Integer response status 201
|
||||
GET /song/${res.body.id}/midi
|
||||
Output
|
||||
[Teardown] DELETE /song/${res.body.id}
|
||||
|
||||
@@ -8,6 +8,7 @@ services:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./back:/app
|
||||
- ./musics:/musics
|
||||
depends_on:
|
||||
- "db"
|
||||
env_file:
|
||||
|
||||
@@ -7,6 +7,8 @@ services:
|
||||
- "db"
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./musics:/musics
|
||||
db:
|
||||
container_name: db
|
||||
image: postgres:alpine3.14
|
||||
|
||||
21
musics/Beethoven-125-4/Beethoven-125-4.ini
Normal file
21
musics/Beethoven-125-4/Beethoven-125-4.ini
Normal file
@@ -0,0 +1,21 @@
|
||||
[Metadata]
|
||||
Name=Symphony No 9 in D Minor
|
||||
Artist=Beethoven
|
||||
Genre=Classical
|
||||
Album=
|
||||
|
||||
[Difficulties]
|
||||
TwoHands=0
|
||||
Rhythm=4
|
||||
NoteCombo=0
|
||||
Arpeggio=6
|
||||
Distance=0
|
||||
LeftHand=2
|
||||
RightHand=1
|
||||
LeadHandChange=0
|
||||
ChordComplexity=0
|
||||
ChordTiming=0
|
||||
Length=1
|
||||
PedalPoint=0
|
||||
Precision=10
|
||||
|
||||
BIN
musics/Beethoven-125-4/Beethoven-125-4.midi
Normal file
BIN
musics/Beethoven-125-4/Beethoven-125-4.midi
Normal file
Binary file not shown.
BIN
musics/Beethoven-125-4/Beethoven-125-4.mxl
Normal file
BIN
musics/Beethoven-125-4/Beethoven-125-4.mxl
Normal file
Binary file not shown.
49
musics/populate.py
Executable file
49
musics/populate.py
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import requests
|
||||
import glob
|
||||
from configparser import ConfigParser
|
||||
|
||||
url = os.environ.get("API_URL")
|
||||
|
||||
def getOrCreateArtist(name):
|
||||
res = requests.post(f"{url}/artist", json={
|
||||
"name": name,
|
||||
})
|
||||
out = res.json()
|
||||
print(out)
|
||||
return out["id"]
|
||||
|
||||
def populateFile(path, midi, mxl):
|
||||
config = ConfigParser()
|
||||
config.read(path)
|
||||
metadata = config["Metadata"];
|
||||
dificulties = dict(config["Difficulties"])
|
||||
print(f"Populating {metadata['Name']}")
|
||||
res = requests.post(f"{url}/song", json={
|
||||
"name": metadata["Name"],
|
||||
"midiPath": midi,
|
||||
"musicXmlPath": mxl,
|
||||
"difficulties": dificulties,
|
||||
"artist": getOrCreateArtist(metadata["Artist"]),
|
||||
# "album": metadata["Album"],
|
||||
# "genre": metadata["Genre"],
|
||||
})
|
||||
print(res.json())
|
||||
|
||||
|
||||
def main():
|
||||
global url
|
||||
if url == None:
|
||||
url = "http://localhost:3000"
|
||||
print("Searching for files...")
|
||||
for file in glob.glob("**/*.ini", recursive=True):
|
||||
file = os.path.abspath(file)
|
||||
print(f"File found: {file}")
|
||||
path = os.path.splitext(file)[0]
|
||||
populateFile(file, path + ".midi", path + ".mxl")
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
Reference in New Issue
Block a user