diff --git a/package.json b/package.json index 61ecdd4..c8a2473 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "consola": "^2.15.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", + "csvtojson": "^2.0.10", "dotenv": "^8.2.0", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", @@ -47,6 +48,7 @@ }, "devDependencies": { "@types/cors": "^2.8.8", + "@types/csvtojson": "^1.1.5", "@types/express": "^4.17.9", "@types/jest": "^26.0.16", "@types/jsonwebtoken": "^8.5.0", diff --git a/src/controllers/ImportController.ts b/src/controllers/ImportController.ts new file mode 100644 index 0000000..232df6f --- /dev/null +++ b/src/controllers/ImportController.ts @@ -0,0 +1,101 @@ +import csv from 'csvtojson'; +import { Body, ContentType, Controller, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { RunnerGroupNeededError } from '../errors/RunnerErrors'; +import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; +import RawBodyMiddleware from '../middlewares/RawBody'; +import { ImportRunner } from '../models/actions/ImportRunner'; +import { ResponseRunner } from '../models/responses/ResponseRunner'; +import { RunnerController } from './RunnerController'; + +@Controller() +//@Authorized("IMPORT:read") +export class ImportController { + private runnerController: RunnerController; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.runnerController = new RunnerController(); + } + + @Post('/runners/import') + @ContentType("application/json") + @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 }) + @OpenAPI({ description: "Create new runners from json and insert them (or their teams) into the provided group" }) + async postJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @QueryParam("group") groupID: number) { + if (!groupID) { throw new RunnerGroupNeededError(); } + let responseRunners: ResponseRunner[] = new Array(); + for await (let runner of importRunners) { + responseRunners.push(await this.runnerController.post(await runner.toCreateRunner(groupID))); + } + return responseRunners; + } + + @Post('/organisations/:id/import') + @ContentType("application/json") + @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 }) + @OpenAPI({ description: "Create new runners from json and insert them (or their teams) into the provided org" }) + async postOrgsJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @Param('id') id: number) { + return await this.postJSON(importRunners, id) + } + + @Post('/teams/:id/import') + @ContentType("application/json") + @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 }) + @OpenAPI({ description: "Create new runners from json and insert them into the provided team" }) + async postTeamsJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @Param('id') id: number) { + return await this.postJSON(importRunners, id) + } + + @Post('/runners/import/csv') + @ContentType("application/json") + @UseBefore(RawBodyMiddleware) + @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 }) + @OpenAPI({ description: "Create new runners from csv and insert them (or their teams) into the provided group" }) + async postCSV(@Req() request: any, @QueryParam("group") groupID: number) { + let csvParse = await csv({ delimiter: [",", ";"], trim: true }).fromString(request.rawBody.toString()); + let importRunners: ImportRunner[] = new Array(); + for await (let runner of csvParse) { + let newImportRunner = new ImportRunner(); + newImportRunner.firstname = runner.firstname; + newImportRunner.middlename = runner.middlename; + newImportRunner.lastname = runner.lastname; + if (runner.class === undefined) { newImportRunner.team = runner.team; } + else { newImportRunner.class = runner.class; } + importRunners.push(newImportRunner); + } + return await this.postJSON(importRunners, groupID); + } + + @Post('/organisations/:id/import/csv') + @ContentType("application/json") + @UseBefore(RawBodyMiddleware) + @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 }) + @OpenAPI({ description: "Create new runners from csv and insert them (or their teams) into the provided org" }) + async postOrgsCSV(@Req() request: any, @Param("id") id: number) { + return await this.postCSV(request, id); + } + + @Post('/teams/:id/import/csv') + @ContentType("application/json") + @UseBefore(RawBodyMiddleware) + @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 }) + @OpenAPI({ description: "Create new runners from csv and insert them into the provided team" }) + async postTeamsCSV(@Req() request: any, @Param("id") id: number) { + return await this.postCSV(request, id); + } +} \ No newline at end of file diff --git a/src/middlewares/RawBody.ts b/src/middlewares/RawBody.ts new file mode 100644 index 0000000..58ff67d --- /dev/null +++ b/src/middlewares/RawBody.ts @@ -0,0 +1,27 @@ +import { Request, Response } from 'express'; + +const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => { + const body = [] + req.on('data', chunk => { + body.push(chunk) + }) + req.on('end', () => { + const rawBody = Buffer.concat(body) + req['rawBody'] = rawBody + /* + switch (req.header('content-type')) { + case 'application/json': + req.body = JSON.parse(rawBody.toString()) + break + // add more body parsing if needs be + default: + } + */ + next() + }) + req.on('error', () => { + res.sendStatus(400) + }) +} + +export default RawBodyMiddleware \ No newline at end of file diff --git a/src/models/actions/ImportRunner.ts b/src/models/actions/ImportRunner.ts new file mode 100644 index 0000000..3f5d97e --- /dev/null +++ b/src/models/actions/ImportRunner.ts @@ -0,0 +1,82 @@ +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { RunnerGroupNeededError } from '../../errors/RunnerErrors'; +import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisationErrors'; +import { RunnerGroup } from '../entities/RunnerGroup'; +import { RunnerOrganisation } from '../entities/RunnerOrganisation'; +import { RunnerTeam } from '../entities/RunnerTeam'; +import { CreateRunner } from './CreateRunner'; + +export class ImportRunner { + + /** + * The new runner's first name. + */ + @IsString() + @IsNotEmpty() + firstname: string; + + /** + * The new runner's middle name. + * Optional. + */ + @IsString() + @IsOptional() + middlename?: string; + + /** + * The new runner's last name. + */ + @IsString() + @IsNotEmpty() + lastname: string; + + /** + * The new runner's class (if not provided otherwise). + */ + @IsString() + @IsOptional() + team?: string; + + @IsOptional() + @IsString() + public set class(value: string) { + this.team = value; + } + + public async toCreateRunner(groupID: number): Promise { + let newRunner: CreateRunner = new CreateRunner(); + + newRunner.firstname = this.firstname; + newRunner.middlename = this.middlename; + newRunner.lastname = this.lastname; + newRunner.group = (await this.getGroup(groupID)).id; + + return newRunner; + } + + public async getGroup(groupID: number): Promise { + if (this.team === undefined && groupID === undefined) { + throw new RunnerGroupNeededError(); + } + + let team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: groupID }); + if (team) { return team; } + + let org = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: groupID }); + if (!org) { + throw new RunnerOrganisationNotFoundError(); + } + if (this.team === undefined) { return org; } + + team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ name: this.team, parentGroup: org }); + if (!team) { + let newRunnerTeam: RunnerTeam = new RunnerTeam(); + newRunnerTeam.name = this.team; + newRunnerTeam.parentGroup = org; + team = await getConnectionManager().get().getRepository(RunnerTeam).save(newRunnerTeam); + } + + return team; + } +} \ No newline at end of file