diff --git a/ormconfig.ts b/ormconfig.ts index 852ff37..377a7e8 100644 --- a/ormconfig.ts +++ b/ormconfig.ts @@ -1,12 +1,12 @@ -import { config } from 'dotenv-safe'; -config(); - -export default { - type: process.env.DB_TYPE, - host: process.env.DB_HOST, - port: process.env.DB_PORT, - username: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - entities: ["src/models/entities/*.ts"] -}; +import { config } from 'dotenv'; +config(); + +export default { + type: process.env.DB_TYPE, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + username: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + entities: ["src/models/entities/*.ts"] +}; diff --git a/package.json b/package.json index 35cecc2..b59be18 100644 --- a/package.json +++ b/package.json @@ -1,80 +1,81 @@ -{ - "name": "@lfk/backend", - "version": "1.0.0", - "main": "src/app.ts", - "repository": "https://git.odit.services/lfk/backend", - "author": { - "name": "ODIT.Services", - "email": "info@odit.services", - "url": "https://odit.services" - }, - "contributors": [ - { - "name": "Philipp Dormann", - "email": "philipp@philippdormann.de", - "url": "https://philippdormann.de" - }, - { - "name": "Nicolai Ort", - "email": "info@nicolai-ort.com", - "url": "https://nicolai-ort.com" - } - ], - "license": "CC-BY-NC-SA-4.0", - "dependencies": { - "argon2": "^0.27.0", - "body-parser": "^1.19.0", - "class-transformer": "^0.3.1", - "class-validator": "^0.12.2", - "class-validator-jsonschema": "^2.0.3", - "consola": "^2.15.0", - "cors": "^2.8.5", - "express": "^4.17.1", - "helmet": "^4.2.0", - "jsonwebtoken": "^8.5.1", - "multer": "^1.4.2", - "mysql": "^2.18.1", - "pg": "^8.5.1", - "reflect-metadata": "^0.1.13", - "routing-controllers": "^0.9.0-alpha.6", - "routing-controllers-openapi": "^2.1.0", - "swagger-ui-express": "^4.1.5", - "typeorm": "^0.2.29", - "typeorm-routing-controllers-extensions": "^0.2.0", - "uuid": "^8.3.1" - }, - "devDependencies": { - "@types/cors": "^2.8.8", - "@types/dotenv-safe": "^8.1.1", - "@types/express": "^4.17.9", - "@types/jest": "^26.0.16", - "@types/jsonwebtoken": "^8.5.0", - "@types/multer": "^1.4.4", - "@types/node": "^14.14.9", - "@types/swagger-ui-express": "^4.1.2", - "@types/uuid": "^8.3.0", - "axios": "^0.21.0", - "dotenv-safe": "^8.2.0", - "jest": "^26.6.3", - "nodemon": "^2.0.6", - "sqlite3": "^5.0.0", - "ts-jest": "^26.4.4", - "ts-node": "^9.0.0", - "typedoc": "^0.19.2", - "typescript": "^4.1.2" - }, - "scripts": { - "dev": "nodemon src/app.ts", - "build": "tsc", - "docs": "typedoc --out docs src", - "test": "jest", - "test:watch": "jest --watchAll" - }, - "nodemonConfig": { - "ignore": [ - "src/tests/*", - "docs/*" - ], - "delay": "2500" - } +{ + "name": "@lfk/backend", + "version": "1.0.0", + "main": "src/app.ts", + "repository": "https://git.odit.services/lfk/backend", + "author": { + "name": "ODIT.Services", + "email": "info@odit.services", + "url": "https://odit.services" + }, + "contributors": [ + { + "name": "Philipp Dormann", + "email": "philipp@philippdormann.de", + "url": "https://philippdormann.de" + }, + { + "name": "Nicolai Ort", + "email": "info@nicolai-ort.com", + "url": "https://nicolai-ort.com" + } + ], + "license": "CC-BY-NC-SA-4.0", + "dependencies": { + "argon2": "^0.27.0", + "body-parser": "^1.19.0", + "class-transformer": "^0.3.1", + "class-validator": "^0.12.2", + "class-validator-jsonschema": "^2.0.3", + "consola": "^2.15.0", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "helmet": "^4.2.0", + "jsonwebtoken": "^8.5.1", + "multer": "^1.4.2", + "mysql": "^2.18.1", + "pg": "^8.5.1", + "reflect-metadata": "^0.1.13", + "routing-controllers": "^0.9.0-alpha.6", + "routing-controllers-openapi": "^2.1.0", + "swagger-ui-express": "^4.1.5", + "typeorm": "^0.2.29", + "typeorm-routing-controllers-extensions": "^0.2.0", + "uuid": "^8.3.1" + }, + "devDependencies": { + "@types/cors": "^2.8.8", + "@types/dotenv": "^8.2.0", + "@types/express": "^4.17.9", + "@types/jest": "^26.0.16", + "@types/jsonwebtoken": "^8.5.0", + "@types/multer": "^1.4.4", + "@types/node": "^14.14.9", + "@types/swagger-ui-express": "^4.1.2", + "@types/uuid": "^8.3.0", + "axios": "^0.21.0", + "dotenv-safe": "^8.2.0", + "jest": "^26.6.3", + "nodemon": "^2.0.6", + "sqlite3": "^5.0.0", + "ts-jest": "^26.4.4", + "ts-node": "^9.0.0", + "typedoc": "^0.19.2", + "typescript": "^4.1.2" + }, + "scripts": { + "dev": "nodemon src/app.ts", + "build": "tsc", + "docs": "typedoc --out docs src", + "test": "jest", + "test:watch": "jest --watchAll" + }, + "nodemonConfig": { + "ignore": [ + "src/tests/*", + "docs/*" + ], + "delay": "2500" + } } \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index fd3dc8e..f11c23c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,26 +1,31 @@ -import consola from "consola"; -import "reflect-metadata"; -import { createExpressServer } from "routing-controllers"; -import authchecker from "./authchecker"; -import { config } from './config'; -import loaders from "./loaders/index"; -import { ErrorHandler } from './middlewares/ErrorHandler'; - -const app = createExpressServer({ - authorizationChecker: authchecker, - middlewares: [ErrorHandler], - development: config.development, - cors: true, - routePrefix: "/api", - controllers: [__dirname + "/controllers/*.ts"], -}); - -async function main() { - await loaders(app); - app.listen(config.internal_port, () => { - consola.success( - `⚡️[server]: Server is running at http://localhost:${config.internal_port}` - ); - }); -} -main(); +import consola from "consola"; +import "reflect-metadata"; +import { createExpressServer } from "routing-controllers"; +import authchecker from "./authchecker"; +import { config, e as errors } from './config'; +import loaders from "./loaders/index"; +import { ErrorHandler } from './middlewares/ErrorHandler'; + +const app = createExpressServer({ + authorizationChecker: authchecker, + middlewares: [ErrorHandler], + development: config.development, + cors: true, + routePrefix: "/api", + controllers: [__dirname + "/controllers/*.ts"], +}); + +async function main() { + await loaders(app); + app.listen(config.internal_port, () => { + consola.success( + `⚡️[server]: Server is running at http://localhost:${config.internal_port}` + ); + }); +} +if (errors === 0) { + main(); +} else { + console.log("error"); + // something's wrong +} diff --git a/src/config.ts b/src/config.ts index 5f54ce3..c530ec1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,22 @@ -import * as dotenvSafe from "dotenv-safe"; -dotenvSafe.config(); -export const config = { - internal_port: process.env.APP_PORT || 4010, - development: process.env.NODE_ENV === "production", - jwt_secret: process.env.JWT_SECRET || "secretjwtsecret" -} \ No newline at end of file +import { config as configDotenv } from 'dotenv'; +configDotenv(); +export const config = { + internal_port: parseInt(process.env.APP_PORT) || 4010, + development: process.env.NODE_ENV === "production", + jwt_secret: process.env.JWT_SECRET || "secretjwtsecret", + phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ" +} +let errors = 0 +if (typeof config.internal_port !== "number") { + errors++ +} +if (typeof config.phone_validation_countrycode !== "string") { + errors++ +} +if (config.phone_validation_countrycode.length !== 2) { + errors++ +} +if (typeof config.development !== "boolean") { + errors++ +} +export let e = errors \ No newline at end of file diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 0a6ea34..c8dcf3d 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -1,99 +1,102 @@ -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; -import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions'; -import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; -import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; -import { CreateRunner } from '../models/actions/CreateRunner'; -import { Runner } from '../models/entities/Runner'; -import { ResponseRunner } from '../models/responses/ResponseRunner'; - -@JsonController('/runners') -//@Authorized('RUNNERS:read') -export class RunnerController { - private runnerRepository: Repository; - - /** - * Gets the repository of this controller's model/entity. - */ - constructor() { - this.runnerRepository = getConnectionManager().get().getRepository(Runner); - } - - @Get() - @ResponseSchema(ResponseRunner, { isArray: true }) - @OpenAPI({ description: 'Lists all runners.' }) - async getAll() { - let responseRunners: ResponseRunner[] = new Array(); - const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] }); - console.log(runners); - runners.forEach(runner => { - responseRunners.push(new ResponseRunner(runner)); - }); - return responseRunners; - } - - @Get('/:id') - @ResponseSchema(ResponseRunner) - @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - @OnUndefined(RunnerNotFoundError) - @OpenAPI({ description: 'Returns a runner of a specified id (if it exists)' }) - async getOne(@Param('id') id: number) { - let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }) - if (!runner) { throw new RunnerNotFoundError(); } - return new ResponseRunner(runner); - } - - @Post() - @ResponseSchema(ResponseRunner) - @ResponseSchema(RunnerGroupNeededError) - @ResponseSchema(RunnerGroupNotFoundError) - @OpenAPI({ description: 'Create a new runner object (id will be generated automagicly).' }) - async post(@Body({ validate: true }) createRunner: CreateRunner) { - let runner; - try { - runner = await createRunner.toRunner(); - } catch (error) { - throw error; - } - - runner = await this.runnerRepository.save(runner) - return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] })); - } - - @Put('/:id') - @ResponseSchema(ResponseRunner) - @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - @ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update a runner object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() runner: Runner) { - let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }); - - if (!oldRunner) { - throw new RunnerNotFoundError(); - } - - if (oldRunner.id != runner.id) { - throw new RunnerIdsNotMatchingError(); - } - - await this.runnerRepository.update(oldRunner, runner); - return new ResponseRunner(runner); - } - - @Delete('/:id') - @ResponseSchema(ResponseRunner) - @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) - async remove(@EntityFromParam('id') runner: Runner, @QueryParam("force") force: boolean) { - if (!runner) { throw new RunnerNotFoundError(); } - const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }); - - if (!runner) { - throw new RunnerNotFoundError(); - } - - await this.runnerRepository.delete(runner); - return new ResponseRunner(responseRunner); - } -} +import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; +import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; +import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; +import { CreateRunner } from '../models/actions/CreateRunner'; +import { Runner } from '../models/entities/Runner'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; +import { ResponseRunner } from '../models/responses/ResponseRunner'; + +@JsonController('/runners') +//@Authorized('RUNNERS:read') +export class RunnerController { + private runnerRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.runnerRepository = getConnectionManager().get().getRepository(Runner); + } + + @Get() + @ResponseSchema(ResponseRunner, { isArray: true }) + @OpenAPI({ description: 'Lists all runners.' }) + async getAll() { + let responseRunners: ResponseRunner[] = new Array(); + const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] }); + console.log(runners); + runners.forEach(runner => { + responseRunners.push(new ResponseRunner(runner)); + }); + return responseRunners; + } + + @Get('/:id') + @ResponseSchema(ResponseRunner) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @OnUndefined(RunnerNotFoundError) + @OpenAPI({ description: 'Returns a runner of a specified id (if it exists)' }) + async getOne(@Param('id') id: number) { + let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }) + if (!runner) { throw new RunnerNotFoundError(); } + return new ResponseRunner(runner); + } + + @Post() + @ResponseSchema(ResponseRunner) + @ResponseSchema(RunnerGroupNeededError) + @ResponseSchema(RunnerGroupNotFoundError) + @OpenAPI({ description: 'Create a new runner object (id will be generated automagicly).' }) + async post(@Body({ validate: true }) createRunner: CreateRunner) { + let runner; + try { + runner = await createRunner.toRunner(); + } catch (error) { + throw error; + } + + runner = await this.runnerRepository.save(runner) + return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] })); + } + + @Put('/:id') + @ResponseSchema(ResponseRunner) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update a runner object (id can't be changed)." }) + async put(@Param('id') id: number, @EntityFromBody() runner: Runner) { + let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }); + + if (!oldRunner) { + throw new RunnerNotFoundError(); + } + + if (oldRunner.id != runner.id) { + throw new RunnerIdsNotMatchingError(); + } + + await this.runnerRepository.update(oldRunner, runner); + return new ResponseRunner(runner); + } + + @Delete('/:id') + @ResponseSchema(ResponseRunner) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let runner = await this.runnerRepository.findOne({ id: id }); + if (!runner) { return null; } + const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }); + + if (!runner) { + throw new RunnerNotFoundError(); + } + + await this.runnerRepository.delete(runner); + return new ResponseRunner(responseRunner); + } +} diff --git a/src/controllers/RunnerOrganisationController.ts b/src/controllers/RunnerOrganisationController.ts index a0914e3..338e21a 100644 --- a/src/controllers/RunnerOrganisationController.ts +++ b/src/controllers/RunnerOrganisationController.ts @@ -1,121 +1,124 @@ -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; -import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions'; -import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors'; -import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation'; -import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; -import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation'; -import { RunnerController } from './RunnerController'; -import { RunnerTeamController } from './RunnerTeamController'; - - -@JsonController('/organisations') -//@Authorized('RUNNERS:read') -export class RunnerOrganisationController { - private runnerOrganisationRepository: Repository; - - /** - * Gets the repository of this controller's model/entity. - */ - constructor() { - this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation); - } - - @Get() - @ResponseSchema(ResponseRunnerOrganisation, { isArray: true }) - @OpenAPI({ description: 'Lists all runnerOrganisations.' }) - async getAll() { - let responseTeams: ResponseRunnerOrganisation[] = new Array(); - const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] }); - console.log(runners); - runners.forEach(runner => { - responseTeams.push(new ResponseRunnerOrganisation(runner)); - }); - return responseTeams; - } - - @Get('/:id') - @ResponseSchema(ResponseRunnerOrganisation) - @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) - @OnUndefined(RunnerOrganisationNotFoundError) - @OpenAPI({ description: 'Returns a runnerOrganisation of a specified id (if it exists)' }) - async getOne(@Param('id') id: number) { - let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] }); - if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); } - return new ResponseRunnerOrganisation(runnerOrg); - } - - @Post() - @ResponseSchema(ResponseRunnerOrganisation) - @OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' }) - async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) { - let runnerOrganisation; - try { - runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation(); - } catch (error) { - throw error; - } - - runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation); - - return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] })); - } - - @Put('/:id') - @ResponseSchema(ResponseRunnerOrganisation) - @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) - @ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() runnerOrganisation: RunnerOrganisation) { - let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id }); - - if (!oldRunnerOrganisation) { - throw new RunnerOrganisationNotFoundError(); - } - - if (oldRunnerOrganisation.id != runnerOrganisation.id) { - throw new RunnerOrganisationIdsNotMatchingError(); - } - - await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation); - - runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }); - return new ResponseRunnerOrganisation(runnerOrganisation); - } - - @Delete('/:id') - @ResponseSchema(ResponseRunnerOrganisation) - @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) - @ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 }) - @ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 }) - @OpenAPI({ description: 'Delete a specified runnerOrganisation (if it exists).' }) - async remove(@EntityFromParam('id') organisation: RunnerOrganisation, @QueryParam("force") force: boolean) { - if (!organisation) { throw new RunnerOrganisationNotFoundError() } - let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] }); - - if (!force) { - if (runnerOrganisation.teams.length != 0) { - throw new RunnerOrganisationHasTeamsError(); - } - } - const teamController = new RunnerTeamController() - for (let team of runnerOrganisation.teams) { - await teamController.remove(team, true); - } - - if (!force) { - if (runnerOrganisation.runners.length != 0) { - throw new RunnerOrganisationHasRunnersError(); - } - } - const runnerController = new RunnerController() - for (let runner of runnerOrganisation.runners) { - await runnerController.remove(runner, true); - } - - const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation); - await this.runnerOrganisationRepository.delete(organisation); - return responseOrganisation; - } -} +import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; +import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors'; +import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation'; +import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; +import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation'; +import { RunnerController } from './RunnerController'; +import { RunnerTeamController } from './RunnerTeamController'; + + +@JsonController('/organisations') +//@Authorized('RUNNERS:read') +export class RunnerOrganisationController { + private runnerOrganisationRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation); + } + + @Get() + @ResponseSchema(ResponseRunnerOrganisation, { isArray: true }) + @OpenAPI({ description: 'Lists all runnerOrganisations.' }) + async getAll() { + let responseTeams: ResponseRunnerOrganisation[] = new Array(); + const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] }); + console.log(runners); + runners.forEach(runner => { + responseTeams.push(new ResponseRunnerOrganisation(runner)); + }); + return responseTeams; + } + + @Get('/:id') + @ResponseSchema(ResponseRunnerOrganisation) + @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) + @OnUndefined(RunnerOrganisationNotFoundError) + @OpenAPI({ description: 'Returns a runnerOrganisation of a specified id (if it exists)' }) + async getOne(@Param('id') id: number) { + let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] }); + if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); } + return new ResponseRunnerOrganisation(runnerOrg); + } + + @Post() + @ResponseSchema(ResponseRunnerOrganisation) + @OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' }) + async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) { + let runnerOrganisation; + try { + runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation(); + } catch (error) { + throw error; + } + + runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation); + + return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] })); + } + + @Put('/:id') + @ResponseSchema(ResponseRunnerOrganisation) + @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." }) + async put(@Param('id') id: number, @EntityFromBody() runnerOrganisation: RunnerOrganisation) { + let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id }); + + if (!oldRunnerOrganisation) { + throw new RunnerOrganisationNotFoundError(); + } + + if (oldRunnerOrganisation.id != runnerOrganisation.id) { + throw new RunnerOrganisationIdsNotMatchingError(); + } + + await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation); + + runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }); + return new ResponseRunnerOrganisation(runnerOrganisation); + } + + @Delete('/:id') + @ResponseSchema(ResponseRunnerOrganisation) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 }) + @ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete a specified runnerOrganisation (if it exists).' }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let organisation = await this.runnerOrganisationRepository.findOne({ id: id }); + if (!organisation) { return null; } + let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] }); + + if (!force) { + if (runnerOrganisation.teams.length != 0) { + throw new RunnerOrganisationHasTeamsError(); + } + } + const teamController = new RunnerTeamController() + for (let team of runnerOrganisation.teams) { + await teamController.remove(team.id, true); + } + + if (!force) { + if (runnerOrganisation.runners.length != 0) { + throw new RunnerOrganisationHasRunnersError(); + } + } + const runnerController = new RunnerController() + for (let runner of runnerOrganisation.runners) { + await runnerController.remove(runner.id, true); + } + + const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation); + await this.runnerOrganisationRepository.delete(organisation); + return responseOrganisation; + } +} diff --git a/src/controllers/RunnerTeamController.ts b/src/controllers/RunnerTeamController.ts index be44aac..9558c3a 100644 --- a/src/controllers/RunnerTeamController.ts +++ b/src/controllers/RunnerTeamController.ts @@ -1,110 +1,113 @@ -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; -import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions'; -import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; -import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam'; -import { RunnerTeam } from '../models/entities/RunnerTeam'; -import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam'; -import { RunnerController } from './RunnerController'; - - -@JsonController('/teams') -//@Authorized('RUNNERS:read') -export class RunnerTeamController { - private runnerTeamRepository: Repository; - - /** - * Gets the repository of this controller's model/entity. - */ - constructor() { - this.runnerTeamRepository = getConnectionManager().get().getRepository(RunnerTeam); - } - - @Get() - @ResponseSchema(ResponseRunnerTeam, { isArray: true }) - @OpenAPI({ description: 'Lists all runnerTeams.' }) - async getAll() { - let responseTeams: ResponseRunnerTeam[] = new Array(); - const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] }); - console.log(runners); - runners.forEach(runner => { - responseTeams.push(new ResponseRunnerTeam(runner)); - }); - return responseTeams; - } - - @Get('/:id') - @ResponseSchema(ResponseRunnerTeam) - @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) - @OnUndefined(RunnerTeamNotFoundError) - @OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' }) - async getOne(@Param('id') id: number) { - let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); - if (!runnerTeam) { throw new RunnerTeamNotFoundError(); } - return new ResponseRunnerTeam(runnerTeam); - } - - @Post() - @ResponseSchema(ResponseRunnerTeam) - @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) - async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) { - let runnerTeam; - try { - runnerTeam = await createRunnerTeam.toRunnerTeam(); - } catch (error) { - throw error; - } - - runnerTeam = await this.runnerTeamRepository.save(runnerTeam); - runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] }); - - return new ResponseRunnerTeam(runnerTeam); - } - - @Put('/:id') - @ResponseSchema(ResponseRunnerTeam) - @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) - @ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() runnerTeam: RunnerTeam) { - let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); - - if (!oldRunnerTeam) { - throw new RunnerTeamNotFoundError(); - } - - if (oldRunnerTeam.id != runnerTeam.id) { - throw new RunnerTeamIdsNotMatchingError(); - } - - await this.runnerTeamRepository.update(oldRunnerTeam, runnerTeam); - - runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] }); - return new ResponseRunnerTeam(runnerTeam); - } - - @Delete('/:id') - @ResponseSchema(ResponseRunnerTeam) - @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) - @ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 }) - @OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' }) - async remove(@EntityFromParam('id') team: RunnerTeam, @QueryParam("force") force: boolean) { - if (!team) { throw new RunnerTeamNotFoundError(); } - let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] }); - - if (!force) { - if (runnerTeam.runners.length != 0) { - throw new RunnerTeamHasRunnersError(); - } - } - const runnerController = new RunnerController() - for (let runner of runnerTeam.runners) { - await runnerController.remove(runner, true); - } - - const responseTeam = new ResponseRunnerTeam(runnerTeam); - await this.runnerTeamRepository.delete(team); - return responseTeam; - } -} +import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; +import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; +import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam'; +import { RunnerTeam } from '../models/entities/RunnerTeam'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; +import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam'; +import { RunnerController } from './RunnerController'; + + +@JsonController('/teams') +//@Authorized('RUNNERS:read') +export class RunnerTeamController { + private runnerTeamRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.runnerTeamRepository = getConnectionManager().get().getRepository(RunnerTeam); + } + + @Get() + @ResponseSchema(ResponseRunnerTeam, { isArray: true }) + @OpenAPI({ description: 'Lists all runnerTeams.' }) + async getAll() { + let responseTeams: ResponseRunnerTeam[] = new Array(); + const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] }); + console.log(runners); + runners.forEach(runner => { + responseTeams.push(new ResponseRunnerTeam(runner)); + }); + return responseTeams; + } + + @Get('/:id') + @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) + @OnUndefined(RunnerTeamNotFoundError) + @OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' }) + async getOne(@Param('id') id: number) { + let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); + if (!runnerTeam) { throw new RunnerTeamNotFoundError(); } + return new ResponseRunnerTeam(runnerTeam); + } + + @Post() + @ResponseSchema(ResponseRunnerTeam) + @OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' }) + async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) { + let runnerTeam; + try { + runnerTeam = await createRunnerTeam.toRunnerTeam(); + } catch (error) { + throw error; + } + + runnerTeam = await this.runnerTeamRepository.save(runnerTeam); + runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] }); + + return new ResponseRunnerTeam(runnerTeam); + } + + @Put('/:id') + @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." }) + async put(@Param('id') id: number, @EntityFromBody() runnerTeam: RunnerTeam) { + let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); + + if (!oldRunnerTeam) { + throw new RunnerTeamNotFoundError(); + } + + if (oldRunnerTeam.id != runnerTeam.id) { + throw new RunnerTeamIdsNotMatchingError(); + } + + await this.runnerTeamRepository.update(oldRunnerTeam, runnerTeam); + + runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] }); + return new ResponseRunnerTeam(runnerTeam); + } + + @Delete('/:id') + @ResponseSchema(ResponseRunnerTeam) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let team = await this.runnerTeamRepository.findOne({ id: id }); + if (!team) { return null; } + let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] }); + + if (!force) { + if (runnerTeam.runners.length != 0) { + throw new RunnerTeamHasRunnersError(); + } + } + const runnerController = new RunnerController() + for (let runner of runnerTeam.runners) { + await runnerController.remove(runner.id, true); + } + + const responseTeam = new ResponseRunnerTeam(runnerTeam); + await this.runnerTeamRepository.delete(team); + return responseTeam; + } +} diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index bf5fb71..e17e098 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -1,10 +1,11 @@ import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors"; import { CreateTrack } from '../models/actions/CreateTrack'; import { Track } from '../models/entities/Track'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseTrack } from '../models/responses/ResponseTrack'; @JsonController('/tracks') @@ -74,10 +75,12 @@ export class TrackController { @Delete('/:id') @ResponseSchema(ResponseTrack) - @ResponseSchema(TrackNotFoundError, { statusCode: 404 }) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @OnUndefined(204) @OpenAPI({ description: "Delete a specified track (if it exists)." }) - async remove(@EntityFromParam('id') track: Track) { - if (!track) { throw new TrackNotFoundError(); } + async remove(@Param("id") id: number) { + let track = await this.trackRepository.findOne({ id: id }); + if (!track) { return null; } await this.trackRepository.delete(track); return new ResponseTrack(track); diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index b5550a2..a94ac71 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -1,85 +1,88 @@ -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; -import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions'; -import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors'; -import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; -import { CreateUser } from '../models/actions/CreateUser'; -import { User } from '../models/entities/User'; - - -@JsonController('/users') -export class UserController { - private userRepository: Repository; - - /** - * Gets the repository of this controller's model/entity. - */ - constructor() { - this.userRepository = getConnectionManager().get().getRepository(User); - } - - @Get() - @ResponseSchema(User, { isArray: true }) - @OpenAPI({ description: 'Lists all users.' }) - getAll() { - return this.userRepository.find(); - } - - @Get('/:id') - @ResponseSchema(User) - @ResponseSchema(UserNotFoundError, { statusCode: 404 }) - @OnUndefined(UserNotFoundError) - @OpenAPI({ description: 'Returns a user of a specified id (if it exists)' }) - getOne(@Param('id') id: number) { - return this.userRepository.findOne({ id: id }); - } - - @Post() - @ResponseSchema(User) - @ResponseSchema(UserGroupNotFoundError) - @OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' }) - async post(@Body({ validate: true }) createUser: CreateUser) { - let user; - try { - user = await createUser.toUser(); - } catch (error) { - throw error; - } - - return this.userRepository.save(user); - } - - @Put('/:id') - @ResponseSchema(User) - @ResponseSchema(UserNotFoundError, { statusCode: 404 }) - @ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update a user object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() user: User) { - let oldUser = await this.userRepository.findOne({ id: id }); - - if (!oldUser) { - throw new UserNotFoundError(); - } - - if (oldUser.id != user.id) { - throw new UserIdsNotMatchingError(); - } - - await this.userRepository.update(oldUser, user); - return user; - } - - @Delete('/:id') - @ResponseSchema(User) - @ResponseSchema(UserNotFoundError, { statusCode: 404 }) - @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) - async remove(@EntityFromParam('id') user: User) { - if (!user) { - throw new UserNotFoundError(); - } - - await this.userRepository.delete(user); - return user; - } -} +import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; +import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors'; +import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; +import { CreateUser } from '../models/actions/CreateUser'; +import { User } from '../models/entities/User'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; + + +@JsonController('/users') +export class UserController { + private userRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.userRepository = getConnectionManager().get().getRepository(User); + } + + @Get() + @ResponseSchema(User, { isArray: true }) + @OpenAPI({ description: 'Lists all users.' }) + getAll() { + return this.userRepository.find(); + } + + @Get('/:id') + @ResponseSchema(User) + @ResponseSchema(UserNotFoundError, { statusCode: 404 }) + @OnUndefined(UserNotFoundError) + @OpenAPI({ description: 'Returns a user of a specified id (if it exists)' }) + getOne(@Param('id') id: number) { + return this.userRepository.findOne({ id: id }); + } + + @Post() + @ResponseSchema(User) + @ResponseSchema(UserGroupNotFoundError) + @OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' }) + async post(@Body({ validate: true }) createUser: CreateUser) { + let user; + try { + user = await createUser.toUser(); + } catch (error) { + throw error; + } + + return this.userRepository.save(user); + } + + @Put('/:id') + @ResponseSchema(User) + @ResponseSchema(UserNotFoundError, { statusCode: 404 }) + @ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update a user object (id can't be changed)." }) + async put(@Param('id') id: number, @EntityFromBody() user: User) { + let oldUser = await this.userRepository.findOne({ id: id }); + + if (!oldUser) { + throw new UserNotFoundError(); + } + + if (oldUser.id != user.id) { + throw new UserIdsNotMatchingError(); + } + + await this.userRepository.update(oldUser, user); + return user; + } + + @Delete('/:id') + @ResponseSchema(User) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) + async remove(@Param("id") id: number) { + let user = await this.userRepository.findOne({ id: id }); + if (!user) { + return null; + } + + await this.userRepository.delete(user); + return user; + } +} diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts index 89ef3b9..8a90d94 100644 --- a/src/controllers/UserGroupController.ts +++ b/src/controllers/UserGroupController.ts @@ -1,84 +1,87 @@ -import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; -import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnectionManager, Repository } from 'typeorm'; -import { EntityFromBody, EntityFromParam } from 'typeorm-routing-controllers-extensions'; -import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors'; -import { CreateUserGroup } from '../models/actions/CreateUserGroup'; -import { UserGroup } from '../models/entities/UserGroup'; - - -@JsonController('/usergroups') -export class UserGroupController { - private userGroupsRepository: Repository; - - /** - * Gets the repository of this controller's model/entity. - */ - constructor() { - this.userGroupsRepository = getConnectionManager().get().getRepository(UserGroup); - } - - @Get() - @ResponseSchema(UserGroup, { isArray: true }) - @OpenAPI({ description: 'Lists all usergroups.' }) - getAll() { - return this.userGroupsRepository.find(); - } - - @Get('/:id') - @ResponseSchema(UserGroup) - @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) - @OnUndefined(UserGroupNotFoundError) - @OpenAPI({ description: 'Returns a usergroup of a specified id (if it exists)' }) - getOne(@Param('id') id: number) { - return this.userGroupsRepository.findOne({ id: id }); - } - - @Post() - @ResponseSchema(UserGroup) - @ResponseSchema(UserGroupNotFoundError) - @OpenAPI({ description: 'Create a new usergroup object (id will be generated automagicly).' }) - async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) { - let userGroup; - try { - userGroup = await createUserGroup.toUserGroup(); - } catch (error) { - throw error; - } - - return this.userGroupsRepository.save(userGroup); - } - - @Put('/:id') - @ResponseSchema(UserGroup) - @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) - @ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update a usergroup object (id can't be changed)." }) - async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) { - let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }); - - if (!oldUserGroup) { - throw new UserGroupNotFoundError() - } - - if (oldUserGroup.id != userGroup.id) { - throw new UserGroupIdsNotMatchingError(); - } - - await this.userGroupsRepository.update(oldUserGroup, userGroup); - return userGroup; - } - - @Delete('/:id') - @ResponseSchema(UserGroup) - @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) - @OpenAPI({ description: 'Delete a specified usergroup (if it exists).' }) - async remove(@EntityFromParam('id') group: UserGroup) { - if (!group) { - throw new UserGroupNotFoundError(); - } - - await this.userGroupsRepository.delete(group); - return group; - } -} +import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; +import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors'; +import { CreateUserGroup } from '../models/actions/CreateUserGroup'; +import { UserGroup } from '../models/entities/UserGroup'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; + + +@JsonController('/usergroups') +export class UserGroupController { + private userGroupsRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.userGroupsRepository = getConnectionManager().get().getRepository(UserGroup); + } + + @Get() + @ResponseSchema(UserGroup, { isArray: true }) + @OpenAPI({ description: 'Lists all usergroups.' }) + getAll() { + return this.userGroupsRepository.find(); + } + + @Get('/:id') + @ResponseSchema(UserGroup) + @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) + @OnUndefined(UserGroupNotFoundError) + @OpenAPI({ description: 'Returns a usergroup of a specified id (if it exists)' }) + getOne(@Param('id') id: number) { + return this.userGroupsRepository.findOne({ id: id }); + } + + @Post() + @ResponseSchema(UserGroup) + @ResponseSchema(UserGroupNotFoundError) + @OpenAPI({ description: 'Create a new usergroup object (id will be generated automagicly).' }) + async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) { + let userGroup; + try { + userGroup = await createUserGroup.toUserGroup(); + } catch (error) { + throw error; + } + + return this.userGroupsRepository.save(userGroup); + } + + @Put('/:id') + @ResponseSchema(UserGroup) + @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) + @ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update a usergroup object (id can't be changed)." }) + async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) { + let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }); + + if (!oldUserGroup) { + throw new UserGroupNotFoundError() + } + + if (oldUserGroup.id != userGroup.id) { + throw new UserGroupIdsNotMatchingError(); + } + + await this.userGroupsRepository.update(oldUserGroup, userGroup); + return userGroup; + } + + @Delete('/:id') + @ResponseSchema(UserGroup) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete a specified usergroup (if it exists).' }) + async remove(@Param("id") id: number) { + let group = await this.userGroupsRepository.findOne({ id: id }); + if (!group) { + return null; + } + + await this.userGroupsRepository.delete(group); + return group; + } +} diff --git a/src/models/actions/CreateGroupContact.ts b/src/models/actions/CreateGroupContact.ts index 73f43d0..5f49879 100644 --- a/src/models/actions/CreateGroupContact.ts +++ b/src/models/actions/CreateGroupContact.ts @@ -1,83 +1,84 @@ -import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; -import { getConnectionManager } from 'typeorm'; -import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; -import { Address } from '../entities/Address'; -import { GroupContact } from '../entities/GroupContact'; - -export class CreateGroupContact { - /** - * The contact's first name. - */ - @IsNotEmpty() - @IsString() - firstname: string; - - /** - * The contact's middle name. - * Optional - */ - @IsOptional() - @IsString() - middlename?: string; - - /** - * The contact's last name. - */ - @IsNotEmpty() - @IsString() - lastname: string; - - /** - * The contact's address. - * Optional - */ - @IsInt() - @IsOptional() - address?: number; - - /** - * The contact's phone number. - * Optional - */ - @IsOptional() - @IsPhoneNumber("DE") - phone?: string; - - /** - * The contact's email address. - * Optional - */ - @IsOptional() - @IsEmail() - email?: string; - - /** - * Get's this participant's address from this.address. - */ - public async getAddress(): Promise
{ - if (this.address === undefined) { - return null; - } - if (!isNaN(this.address)) { - let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address }); - if (!address) { throw new AddressNotFoundError; } - return address; - } - - throw new AddressWrongTypeError; - } - - /** - * Creates a Address object based on this. - */ - public async toGroupContact(): Promise { - let contact: GroupContact = new GroupContact(); - contact.firstname = this.firstname; - contact.middlename = this.middlename; - contact.lastname = this.lastname; - contact.email = this.email; - contact.phone = this.phone; - contact.address = await this.getAddress(); - return null; - } +import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { config } from '../../config'; +import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; +import { Address } from '../entities/Address'; +import { GroupContact } from '../entities/GroupContact'; + +export class CreateGroupContact { + /** + * The contact's first name. + */ + @IsNotEmpty() + @IsString() + firstname: string; + + /** + * The contact's middle name. + * Optional + */ + @IsOptional() + @IsString() + middlename?: string; + + /** + * The contact's last name. + */ + @IsNotEmpty() + @IsString() + lastname: string; + + /** + * The contact's address. + * Optional + */ + @IsInt() + @IsOptional() + address?: number; + + /** + * The contact's phone number. + * Optional + */ + @IsOptional() + @IsPhoneNumber(config.phone_validation_countrycode) + phone?: string; + + /** + * The contact's email address. + * Optional + */ + @IsOptional() + @IsEmail() + email?: string; + + /** + * Get's this participant's address from this.address. + */ + public async getAddress(): Promise
{ + if (this.address === undefined) { + return null; + } + if (!isNaN(this.address)) { + let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address }); + if (!address) { throw new AddressNotFoundError; } + return address; + } + + throw new AddressWrongTypeError; + } + + /** + * Creates a Address object based on this. + */ + public async toGroupContact(): Promise { + let contact: GroupContact = new GroupContact(); + contact.firstname = this.firstname; + contact.middlename = this.middlename; + contact.lastname = this.lastname; + contact.email = this.email; + contact.phone = this.phone; + contact.address = await this.getAddress(); + return null; + } } \ No newline at end of file diff --git a/src/models/actions/CreateParticipant.ts b/src/models/actions/CreateParticipant.ts index 00c8783..2847f13 100644 --- a/src/models/actions/CreateParticipant.ts +++ b/src/models/actions/CreateParticipant.ts @@ -1,71 +1,72 @@ -import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; -import { getConnectionManager } from 'typeorm'; -import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; -import { Address } from '../entities/Address'; - -export abstract class CreateParticipant { - /** - * The new participant's first name. - */ - @IsString() - @IsNotEmpty() - firstname: string; - - /** - * The new participant's middle name. - * Optional. - */ - @IsString() - @IsNotEmpty() - middlename?: string; - - /** - * The new participant's last name. - */ - @IsString() - @IsNotEmpty() - lastname: string; - - /** - * The new participant's phone number. - * Optional. - */ - @IsString() - @IsOptional() - @IsPhoneNumber("ZZ") - phone?: string; - - /** - * The new participant's e-mail address. - * Optional. - */ - @IsString() - @IsOptional() - @IsEmail() - email?: string; - - /** - * The new participant's address. - * Must be of type number (address id), createAddress (new address) or address (existing address) - * Optional. - */ - @IsInt() - @IsOptional() - address?: number; - - /** - * Get's this participant's address from this.address. - */ - public async getAddress(): Promise
{ - if (this.address === undefined) { - return null; - } - if (!isNaN(this.address)) { - let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address }); - if (!address) { throw new AddressNotFoundError; } - return address; - } - - throw new AddressWrongTypeError; - } +import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { config } from '../../config'; +import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; +import { Address } from '../entities/Address'; + +export abstract class CreateParticipant { + /** + * The new participant's first name. + */ + @IsString() + @IsNotEmpty() + firstname: string; + + /** + * The new participant's middle name. + * Optional. + */ + @IsString() + @IsNotEmpty() + middlename?: string; + + /** + * The new participant's last name. + */ + @IsString() + @IsNotEmpty() + lastname: string; + + /** + * The new participant's phone number. + * Optional. + */ + @IsString() + @IsOptional() + @IsPhoneNumber(config.phone_validation_countrycode) + phone?: string; + + /** + * The new participant's e-mail address. + * Optional. + */ + @IsString() + @IsOptional() + @IsEmail() + email?: string; + + /** + * The new participant's address. + * Must be of type number (address id), createAddress (new address) or address (existing address) + * Optional. + */ + @IsInt() + @IsOptional() + address?: number; + + /** + * Get's this participant's address from this.address. + */ + public async getAddress(): Promise
{ + if (this.address === undefined) { + return null; + } + if (!isNaN(this.address)) { + let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address }); + if (!address) { throw new AddressNotFoundError; } + return address; + } + + throw new AddressWrongTypeError; + } } \ No newline at end of file diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/CreateUser.ts index ff6771a..48858f4 100644 --- a/src/models/actions/CreateUser.ts +++ b/src/models/actions/CreateUser.ts @@ -1,119 +1,120 @@ -import * as argon2 from "argon2"; -import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; -import { getConnectionManager } from 'typeorm'; -import * as uuid from 'uuid'; -import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; -import { UserGroupNotFoundError } from '../../errors/UserGroupErrors'; -import { User } from '../entities/User'; -import { UserGroup } from '../entities/UserGroup'; - -export class CreateUser { - /** - * The new user's first name. - */ - @IsString() - firstname: string; - - /** - * The new user's middle name. - * Optinal. - */ - @IsString() - @IsOptional() - middlename?: string; - - /** - * The new user's last name. - */ - @IsString() - lastname: string; - - /** - * The new user's username. - * You have to provide at least one of: {email, username}. - */ - @IsOptional() - @IsString() - username?: string; - - /** - * The new user's email address. - * You have to provide at least one of: {email, username}. - */ - @IsEmail() - @IsString() - @IsOptional() - email?: string; - - /** - * The new user's phone number. - * Optional - */ - @IsPhoneNumber("ZZ") - @IsOptional() - phone?: string; - - /** - * The new user's password. - * This will of course not be saved in plaintext :) - */ - @IsString() - password: string; - - /** - * The new user's groups' id(s). - * You can provide either one groupId or an array of groupIDs. - * Optional. - */ - @IsOptional() - groupId?: number[] | number - - //TODO: ProfilePics - - /** - * Converts this to a User Entity. - */ - public async toUser(): Promise { - let newUser: User = new User(); - - if (this.email === undefined && this.username === undefined) { - throw new UsernameOrEmailNeededError(); - } - - if (this.groupId) { - if (!Array.isArray(this.groupId)) { - this.groupId = [this.groupId] - } - const groupIDs: number[] = this.groupId - let errors = 0 - const validateusergroups = async () => { - let foundgroups = [] - for (const g of groupIDs) { - const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g }); - if (found.length === 0) { - errors++ - } else { - foundgroups.push(found[0]) - } - } - newUser.groups = foundgroups - } - await validateusergroups() - if (errors !== 0) { - throw new UserGroupNotFoundError(); - } - } - - newUser.email = this.email - newUser.username = this.username - newUser.firstname = this.firstname - newUser.middlename = this.middlename - newUser.lastname = this.lastname - newUser.uuid = uuid.v4() - newUser.phone = this.phone - newUser.password = await argon2.hash(this.password + newUser.uuid); - //TODO: ProfilePics - - return newUser; - } +import * as argon2 from "argon2"; +import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import * as uuid from 'uuid'; +import { config } from '../../config'; +import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; +import { UserGroupNotFoundError } from '../../errors/UserGroupErrors'; +import { User } from '../entities/User'; +import { UserGroup } from '../entities/UserGroup'; + +export class CreateUser { + /** + * The new user's first name. + */ + @IsString() + firstname: string; + + /** + * The new user's middle name. + * Optinal. + */ + @IsString() + @IsOptional() + middlename?: string; + + /** + * The new user's last name. + */ + @IsString() + lastname: string; + + /** + * The new user's username. + * You have to provide at least one of: {email, username}. + */ + @IsOptional() + @IsString() + username?: string; + + /** + * The new user's email address. + * You have to provide at least one of: {email, username}. + */ + @IsEmail() + @IsString() + @IsOptional() + email?: string; + + /** + * The new user's phone number. + * Optional + */ + @IsPhoneNumber(config.phone_validation_countrycode) + @IsOptional() + phone?: string; + + /** + * The new user's password. + * This will of course not be saved in plaintext :) + */ + @IsString() + password: string; + + /** + * The new user's groups' id(s). + * You can provide either one groupId or an array of groupIDs. + * Optional. + */ + @IsOptional() + groupId?: number[] | number + + //TODO: ProfilePics + + /** + * Converts this to a User Entity. + */ + public async toUser(): Promise { + let newUser: User = new User(); + + if (this.email === undefined && this.username === undefined) { + throw new UsernameOrEmailNeededError(); + } + + if (this.groupId) { + if (!Array.isArray(this.groupId)) { + this.groupId = [this.groupId] + } + const groupIDs: number[] = this.groupId + let errors = 0 + const validateusergroups = async () => { + let foundgroups = [] + for (const g of groupIDs) { + const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g }); + if (found.length === 0) { + errors++ + } else { + foundgroups.push(found[0]) + } + } + newUser.groups = foundgroups + } + await validateusergroups() + if (errors !== 0) { + throw new UserGroupNotFoundError(); + } + } + + newUser.email = this.email + newUser.username = this.username + newUser.firstname = this.firstname + newUser.middlename = this.middlename + newUser.lastname = this.lastname + newUser.uuid = uuid.v4() + newUser.phone = this.phone + newUser.password = await argon2.hash(this.password + newUser.uuid); + //TODO: ProfilePics + + return newUser; + } } \ No newline at end of file diff --git a/src/models/entities/GroupContact.ts b/src/models/entities/GroupContact.ts index c7c8f18..d2dd367 100644 --- a/src/models/entities/GroupContact.ts +++ b/src/models/entities/GroupContact.ts @@ -1,82 +1,83 @@ -import { - IsEmail, - IsInt, - IsNotEmpty, - IsOptional, - IsPhoneNumber, - - IsString -} from "class-validator"; -import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; -import { Address } from "./Address"; -import { RunnerGroup } from "./RunnerGroup"; - -/** - * Defines a group's contact. -*/ -@Entity() -export class GroupContact { - /** - * Autogenerated unique id (primary key). - */ - @PrimaryGeneratedColumn() - @IsInt() - id: number; - - /** - * The contact's first name. - */ - @Column() - @IsNotEmpty() - @IsString() - firstname: string; - - /** - * The contact's middle name. - * Optional - */ - @Column({ nullable: true }) - @IsOptional() - @IsString() - middlename?: string; - - /** - * The contact's last name. - */ - @Column() - @IsNotEmpty() - @IsString() - lastname: string; - - /** - * The contact's address. - * Optional - */ - @IsOptional() - @ManyToOne(() => Address, address => address.participants, { nullable: true }) - address?: Address; - - /** - * The contact's phone number. - * Optional - */ - @Column({ nullable: true }) - @IsOptional() - @IsPhoneNumber("DE") - phone?: string; - - /** - * The contact's email address. - * Optional - */ - @Column({ nullable: true }) - @IsOptional() - @IsEmail() - email?: string; - - /** - * Used to link contacts to groups. - */ - @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true }) - groups: RunnerGroup[]; +import { + IsEmail, + IsInt, + IsNotEmpty, + IsOptional, + IsPhoneNumber, + + IsString +} from "class-validator"; +import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { config } from '../../config'; +import { Address } from "./Address"; +import { RunnerGroup } from "./RunnerGroup"; + +/** + * Defines a group's contact. +*/ +@Entity() +export class GroupContact { + /** + * Autogenerated unique id (primary key). + */ + @PrimaryGeneratedColumn() + @IsInt() + id: number; + + /** + * The contact's first name. + */ + @Column() + @IsNotEmpty() + @IsString() + firstname: string; + + /** + * The contact's middle name. + * Optional + */ + @Column({ nullable: true }) + @IsOptional() + @IsString() + middlename?: string; + + /** + * The contact's last name. + */ + @Column() + @IsNotEmpty() + @IsString() + lastname: string; + + /** + * The contact's address. + * Optional + */ + @IsOptional() + @ManyToOne(() => Address, address => address.participants, { nullable: true }) + address?: Address; + + /** + * The contact's phone number. + * Optional + */ + @Column({ nullable: true }) + @IsOptional() + @IsPhoneNumber(config.phone_validation_countrycode) + phone?: string; + + /** + * The contact's email address. + * Optional + */ + @Column({ nullable: true }) + @IsOptional() + @IsEmail() + email?: string; + + /** + * Used to link contacts to groups. + */ + @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true }) + groups: RunnerGroup[]; } \ No newline at end of file diff --git a/src/models/entities/Participant.ts b/src/models/entities/Participant.ts index 2e40f5d..6f3554a 100644 --- a/src/models/entities/Participant.ts +++ b/src/models/entities/Participant.ts @@ -1,82 +1,83 @@ -import { - IsEmail, - IsInt, - IsNotEmpty, - IsOptional, - IsPhoneNumber, - - IsString -} from "class-validator"; -import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; -import { Address } from "./Address"; -import { Donation } from "./Donation"; - -/** - * Defines the participant interface. -*/ -@Entity() -@TableInheritance({ column: { name: "type", type: "varchar" } }) -export abstract class Participant { - /** - * Autogenerated unique id (primary key). - */ - @PrimaryGeneratedColumn() - @IsInt() - id: number; - - /** - * The participant's first name. - */ - @Column() - @IsNotEmpty() - @IsString() - firstname: string; - - /** - * The participant's middle name. - * Optional - */ - @Column({ nullable: true }) - @IsOptional() - @IsString() - middlename?: string; - - /** - * The participant's last name. - */ - @Column() - @IsNotEmpty() - @IsString() - lastname: string; - - /** - * The participant's address. - * Optional - */ - @ManyToOne(() => Address, address => address.participants, { nullable: true }) - address?: Address; - - /** - * The participant's phone number. - * Optional - */ - @Column({ nullable: true }) - @IsOptional() - @IsPhoneNumber("DE") - phone?: string; - - /** - * The participant's email address. - * Optional - */ - @Column({ nullable: true }) - @IsOptional() - @IsEmail() - email?: string; - - /** - * Used to link the participant as the donor of a donation. - */ - @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) - donations: Donation[]; +import { + IsEmail, + IsInt, + IsNotEmpty, + IsOptional, + IsPhoneNumber, + + IsString +} from "class-validator"; +import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; +import { config } from '../../config'; +import { Address } from "./Address"; +import { Donation } from "./Donation"; + +/** + * Defines the participant interface. +*/ +@Entity() +@TableInheritance({ column: { name: "type", type: "varchar" } }) +export abstract class Participant { + /** + * Autogenerated unique id (primary key). + */ + @PrimaryGeneratedColumn() + @IsInt() + id: number; + + /** + * The participant's first name. + */ + @Column() + @IsNotEmpty() + @IsString() + firstname: string; + + /** + * The participant's middle name. + * Optional + */ + @Column({ nullable: true }) + @IsOptional() + @IsString() + middlename?: string; + + /** + * The participant's last name. + */ + @Column() + @IsNotEmpty() + @IsString() + lastname: string; + + /** + * The participant's address. + * Optional + */ + @ManyToOne(() => Address, address => address.participants, { nullable: true }) + address?: Address; + + /** + * The participant's phone number. + * Optional + */ + @Column({ nullable: true }) + @IsOptional() + @IsPhoneNumber(config.phone_validation_countrycode) + phone?: string; + + /** + * The participant's email address. + * Optional + */ + @Column({ nullable: true }) + @IsOptional() + @IsEmail() + email?: string; + + /** + * Used to link the participant as the donor of a donation. + */ + @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) + donations: Donation[]; } \ No newline at end of file diff --git a/src/models/entities/User.ts b/src/models/entities/User.ts index 8e29e27..ebc62b1 100644 --- a/src/models/entities/User.ts +++ b/src/models/entities/User.ts @@ -1,5 +1,6 @@ import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator"; import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { config } from '../../config'; import { Permission } from './Permission'; import { UserAction } from './UserAction'; import { UserGroup } from './UserGroup'; @@ -35,7 +36,7 @@ export class User { */ @Column({ nullable: true }) @IsOptional() - @IsPhoneNumber("ZZ") + @IsPhoneNumber(config.phone_validation_countrycode) phone?: string; /** diff --git a/src/models/responses/ResponseEmpty.ts b/src/models/responses/ResponseEmpty.ts new file mode 100644 index 0000000..1bb5e13 --- /dev/null +++ b/src/models/responses/ResponseEmpty.ts @@ -0,0 +1,6 @@ + +/** + * Defines a empty response object +*/ +export class ResponseEmpty { +} diff --git a/test.sqlite-journal b/test.sqlite-journal new file mode 100644 index 0000000..f0894c4 Binary files /dev/null and b/test.sqlite-journal differ