diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 798d3c6..14658da 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -1,11 +1,11 @@ -import { JsonController, Param, Body, Get, Post, Put, Delete, OnUndefined } from 'routing-controllers'; +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 { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { Runner } from '../models/entities/Runner'; import { RunnerGroupNeededError, RunnerGroupNotFoundError, RunnerIdsNotMatchingError, RunnerNotFoundError, RunnerOnlyOneGroupAllowedError } from '../errors/RunnerErrors'; import { CreateRunner } from '../models/creation/CreateRunner'; - +import { Runner } from '../models/entities/Runner'; +import { ResponseRunner } from '../models/responses/ResponseRunner'; @JsonController('/runners') //@Authorized('RUNNERS:read') @@ -20,23 +20,29 @@ export class RunnerController { } @Get() - @ResponseSchema(Runner, { isArray: true }) + @ResponseSchema(ResponseRunner, { isArray: true }) @OpenAPI({ description: 'Lists all runners.' }) - getAll() { - return this.runnerRepository.find(); + 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(Runner) + @ResponseSchema(ResponseRunner) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @OnUndefined(RunnerNotFoundError) @OpenAPI({ description: 'Returns a runner of a specified id (if it exists)' }) - getOne(@Param('id') id: number) { - return this.runnerRepository.findOne({ id: id }); + async getOne(@Param('id') id: number) { + return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })); } @Post() - @ResponseSchema(Runner) + @ResponseSchema(ResponseRunner) @ResponseSchema(RunnerOnlyOneGroupAllowedError) @ResponseSchema(RunnerGroupNeededError) @ResponseSchema(RunnerGroupNotFoundError) @@ -49,11 +55,11 @@ export class RunnerController { return error; } - return this.runnerRepository.save(runner); + return new ResponseRunner(await this.runnerRepository.save(runner)); } @Put('/:id') - @ResponseSchema(Runner) + @ResponseSchema(ResponseRunner) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 }) @OpenAPI({ description: "Update a runner object (id can't be changed)." }) @@ -69,14 +75,14 @@ export class RunnerController { } await this.runnerRepository.update(oldRunner, runner); - return runner; + return new ResponseRunner(runner); } @Delete('/:id') - @ResponseSchema(Runner) + @ResponseSchema(ResponseRunner) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @OpenAPI({ description: 'Delete a specified runner (if it exists).' }) - async remove(@Param('id') id: number) { + async remove(@Param('id') id: number, @QueryParam("force") force: boolean) { let runner = await this.runnerRepository.findOne({ id: id }); if (!runner) { @@ -84,6 +90,6 @@ export class RunnerController { } await this.runnerRepository.delete(runner); - return runner; + return new ResponseRunner(runner); } } diff --git a/src/controllers/RunnerOrganisationController.ts b/src/controllers/RunnerOrganisationController.ts index 8033c9b..bed6eec 100644 --- a/src/controllers/RunnerOrganisationController.ts +++ b/src/controllers/RunnerOrganisationController.ts @@ -1,14 +1,18 @@ -import { JsonController, Param, Body, Get, Post, Put, Delete, OnUndefined } from 'routing-controllers'; +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 { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; -import { RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors'; +import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors'; import { CreateRunnerOrganisation } from '../models/creation/CreateRunnerOrganisation'; -import { RunnerGroup } from '../models/entities/RunnerGroup'; +import { Runner } from '../models/entities/Runner'; +import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; +import { RunnerTeam } from '../models/entities/RunnerTeam'; +import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation'; +import { RunnerController } from './RunnerController'; +import { RunnerTeamController } from './RunnerTeamController'; -@JsonController('/organisations') +@JsonController('/organisation') //@Authorized('RUNNERS:read') export class RunnerOrganisationController { private runnerOrganisationRepository: Repository; @@ -21,23 +25,29 @@ export class RunnerOrganisationController { } @Get() - @ResponseSchema(RunnerOrganisation, { isArray: true }) + @ResponseSchema(ResponseRunnerOrganisation, { isArray: true }) @OpenAPI({ description: 'Lists all runnerOrganisations.' }) - getAll() { - return this.runnerOrganisationRepository.find(); + 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(RunnerOrganisation) + @ResponseSchema(ResponseRunnerOrganisation) @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) @OnUndefined(RunnerOrganisationNotFoundError) @OpenAPI({ description: 'Returns a runnerOrganisation of a specified id (if it exists)' }) - getOne(@Param('id') id: number) { - return this.runnerOrganisationRepository.findOne({ id: id }); + async getOne(@Param('id') id: number) { + return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] })); } @Post() - @ResponseSchema(RunnerOrganisation) + @ResponseSchema(ResponseRunnerOrganisation) @OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' }) async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) { let runnerOrganisation; @@ -47,16 +57,19 @@ export class RunnerOrganisationController { return error; } - return this.runnerOrganisationRepository.save(runnerOrganisation); + runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation); + runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }); + + return new ResponseRunnerOrganisation(runnerOrganisation); } @Put('/:id') - @ResponseSchema(RunnerOrganisation) + @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 }); + let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] }); if (!oldRunnerOrganisation) { throw new RunnerOrganisationNotFoundError(); @@ -67,21 +80,47 @@ export class RunnerOrganisationController { } await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation); - return runnerOrganisation; + + runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }); + return new ResponseRunnerOrganisation(runnerOrganisation); } @Delete('/:id') - @ResponseSchema(RunnerOrganisation) + @ResponseSchema(ResponseRunnerOrganisation) @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 }) @OpenAPI({ description: 'Delete a specified runnerOrganisation (if it exists).' }) - async remove(@Param('id') id: number) { - let runnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id }); + async remove(@Param('id') id: number, @QueryParam("force") force: boolean) { + let runnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] }); if (!runnerOrganisation) { throw new RunnerOrganisationNotFoundError(); } - await this.runnerOrganisationRepository.delete(runnerOrganisation); - return runnerOrganisation; + let runners: Runner[] = await runnerOrganisation.getRunners() + if (!force) { + if (runners.length != 0) { + throw new RunnerOrganisationHasRunnersError(); + } + } + const runnerController = new RunnerController() + runners.forEach(runner => { + runnerController.remove(runner.id, true) + }); + + let teams: RunnerTeam[] = await runnerOrganisation.getTeams() + if (!force) { + if (teams.length != 0) { + throw new RunnerOrganisationHasTeamsError(); + } + } + const teamController = new RunnerTeamController() + teams.forEach(team => { + teamController.remove(team.id, true) + }); + + const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation); + await this.runnerOrganisationRepository.delete({ id: runnerOrganisation.id }); + return responseOrganisation; } } diff --git a/src/controllers/RunnerTeamController.ts b/src/controllers/RunnerTeamController.ts new file mode 100644 index 0000000..6103083 --- /dev/null +++ b/src/controllers/RunnerTeamController.ts @@ -0,0 +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 } from 'typeorm-routing-controllers-extensions'; +import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; +import { CreateRunnerTeam } from '../models/creation/CreateRunnerTeam'; +import { Runner } from '../models/entities/Runner'; +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) { + return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] })); + } + + @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) { + return 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(@Param('id') id: number, @QueryParam("force") force: boolean) { + let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); + + if (!runnerTeam) { + throw new RunnerTeamNotFoundError(); + } + + let runners: Runner[] = await runnerTeam.getRunners() + if (!force) { + if (runners.length != 0) { + throw new RunnerTeamHasRunnersError(); + } + } + const runnerController = new RunnerController() + runners.forEach(runner => { + runnerController.remove(runner.id, true) + }); + + const responseTeam = new ResponseRunnerTeam(runnerTeam); + await this.runnerTeamRepository.delete({ id: runnerTeam.id }); + return responseTeam; + } +} diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index 869f1be..ae451be 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -1,11 +1,11 @@ -import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError, Authorized } from 'routing-controllers'; +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 { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { Track } from '../models/entities/Track'; -import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors"; import { CreateTrack } from '../models/creation/CreateTrack'; +import { Track } from '../models/entities/Track'; +import { ResponseTrack } from '../models/responses/ResponseTrack'; @JsonController('/tracks') //@Authorized("TRACKS:read") @@ -20,33 +20,38 @@ export class TrackController { } @Get() - @ResponseSchema(Track, { isArray: true }) + @ResponseSchema(ResponseTrack, { isArray: true }) @OpenAPI({ description: "Lists all tracks." }) - getAll() { - return this.trackRepository.find(); + async getAll() { + let responseTracks: ResponseTrack[] = new Array(); + const tracks = await this.trackRepository.find(); + tracks.forEach(track => { + responseTracks.push(new ResponseTrack(track)); + }); + return responseTracks; } @Get('/:id') - @ResponseSchema(Track) + @ResponseSchema(ResponseTrack) @ResponseSchema(TrackNotFoundError, { statusCode: 404 }) @OnUndefined(TrackNotFoundError) @OpenAPI({ description: "Returns a track of a specified id (if it exists)" }) - getOne(@Param('id') id: number) { - return this.trackRepository.findOne({ id: id }); + async getOne(@Param('id') id: number) { + return new ResponseTrack(await this.trackRepository.findOne({ id: id })); } @Post() - @ResponseSchema(Track) + @ResponseSchema(ResponseTrack) @OpenAPI({ description: "Create a new track object (id will be generated automagicly)." }) - post( + async post( @Body({ validate: true }) track: CreateTrack ) { - return this.trackRepository.save(track.toTrack()); + return new ResponseTrack(await this.trackRepository.save(track.toTrack())); } @Put('/:id') - @ResponseSchema(Track) + @ResponseSchema(ResponseTrack) @ResponseSchema(TrackNotFoundError, { statusCode: 404 }) @ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 }) @OpenAPI({ description: "Update a track object (id can't be changed)." }) @@ -62,11 +67,11 @@ export class TrackController { } await this.trackRepository.update(oldTrack, track); - return track; + return new ResponseTrack(track); } @Delete('/:id') - @ResponseSchema(Track) + @ResponseSchema(ResponseTrack) @ResponseSchema(TrackNotFoundError, { statusCode: 404 }) @OpenAPI({ description: "Delete a specified track (if it exists)." }) async remove(@Param('id') id: number) { @@ -77,6 +82,6 @@ export class TrackController { } await this.trackRepository.delete(track); - return track; + return new ResponseTrack(track); } } diff --git a/src/errors/ParticipantErrors.ts b/src/errors/ParticipantErrors.ts new file mode 100644 index 0000000..de672d8 --- /dev/null +++ b/src/errors/ParticipantErrors.ts @@ -0,0 +1,18 @@ +import { IsString } from 'class-validator'; +import { NotAcceptableError, NotFoundError } from 'routing-controllers'; + +export class ParticipantOnlyOneAddressAllowedError extends NotAcceptableError { + @IsString() + name = "ParticipantOnlyOneAddressAllowedError" + + @IsString() + message = "Participant's can only have one address! \n You provided an id and address object.." +} + +export class ParticipantAddressNotFoundError extends NotFoundError { + @IsString() + name = "ParticipantAddressNotFoundError" + + @IsString() + message = "The address you provided couldn't be located in the system. \n Please check your request." +} \ No newline at end of file diff --git a/src/errors/RunnerOrganisationErrors.ts b/src/errors/RunnerOrganisationErrors.ts index f846ffa..1dfb68e 100644 --- a/src/errors/RunnerOrganisationErrors.ts +++ b/src/errors/RunnerOrganisationErrors.ts @@ -1,8 +1,8 @@ -import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError } from 'routing-controllers'; -import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; +import { IsString } from 'class-validator'; +import { NotAcceptableError, NotFoundError } from 'routing-controllers'; /** - * Error to throw when a runner couldn't be found. + * Error to throw when a runner organisation couldn't be found. * Implemented this ways to work with the json-schema conversion for openapi. */ export class RunnerOrganisationNotFoundError extends NotFoundError { @@ -14,9 +14,9 @@ export class RunnerOrganisationNotFoundError extends NotFoundError { } /** - * Error to throw when two runners' ids don't match. + * Error to throw when two runner organisations' ids don't match. * Usually occurs when a user tries to change a runner's id. - * Implemented this ways to work with the json-schema conversion for openapi. + * Implemented this way to work with the json-schema conversion for openapi. */ export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError { @IsString() @@ -24,4 +24,28 @@ export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError { @IsString() message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed" +} + +/** + * Error to throw when a organisation still has runners associated. + * Implemented this waysto work with the json-schema conversion for openapi. + */ +export class RunnerOrganisationHasRunnersError extends NotAcceptableError { + @IsString() + name = "RunnerOrganisationHasRunnersError" + + @IsString() + message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query." +} + +/** + * Error to throw when a organisation still has runners associated. + * Implemented this waysto work with the json-schema conversion for openapi. + */ +export class RunnerOrganisationHasTeamsError extends NotAcceptableError { + @IsString() + name = "RunnerOrganisationHasTeamsError" + + @IsString() + message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query." } \ No newline at end of file diff --git a/src/errors/RunnerTeamErrors.ts b/src/errors/RunnerTeamErrors.ts new file mode 100644 index 0000000..a482c26 --- /dev/null +++ b/src/errors/RunnerTeamErrors.ts @@ -0,0 +1,39 @@ +import { IsString } from 'class-validator'; +import { NotAcceptableError, NotFoundError } from 'routing-controllers'; + +/** + * Error to throw when a runner team couldn't be found. + * Implemented this ways to work with the json-schema conversion for openapi. + */ +export class RunnerTeamNotFoundError extends NotFoundError { + @IsString() + name = "RunnerTeamNotFoundError" + + @IsString() + message = "RunnerTeam not found!" +} + +/** + * Error to throw when two runner teams' ids don't match. + * Usually occurs when a user tries to change a runner's id. + * Implemented this way to work with the json-schema conversion for openapi. + */ +export class RunnerTeamIdsNotMatchingError extends NotAcceptableError { + @IsString() + name = "RunnerTeamIdsNotMatchingError" + + @IsString() + message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed" +} + +/** + * Error to throw when a team still has runners associated. + * Implemented this waysto work with the json-schema conversion for openapi. + */ +export class RunnerTeamHasRunnersError extends NotAcceptableError { + @IsString() + name = "RunnerTeamHasRunnersError" + + @IsString() + message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams ass `?force` to your query." +} \ No newline at end of file diff --git a/src/models/creation/CreateAddress.ts b/src/models/creation/CreateAddress.ts new file mode 100644 index 0000000..ef78410 --- /dev/null +++ b/src/models/creation/CreateAddress.ts @@ -0,0 +1,64 @@ +import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator'; +import { Address } from '../entities/Address'; + +export class CreateAddress { + /** + * The address's description. + */ + @IsString() + @IsOptional() + description?: string; + + /** + * The address's first line. + * Containing the street and house number. + */ + @IsString() + @IsNotEmpty() + address1: string; + + /** + * The address's second line. + * Containing optional information. + */ + @IsString() + @IsOptional() + address2?: string; + + /** + * The address's postal code. + */ + @IsString() + @IsNotEmpty() + @IsPostalCode("DE") + postalcode: string; + + /** + * The address's city. + */ + @IsString() + @IsNotEmpty() + city: string; + + /** + * The address's country. + */ + @IsString() + @IsNotEmpty() + country: string; + + /** + * Creates a Address object based on this. + */ + public toAddress(): Address { + let newAddress: Address = new Address(); + + newAddress.address1 = this.address1; + newAddress.address2 = this.address2; + newAddress.postalcode = this.postalcode; + newAddress.city = this.city; + newAddress.country = this.country; + + return newAddress; + } +} \ No newline at end of file diff --git a/src/models/creation/CreateParticipant.ts b/src/models/creation/CreateParticipant.ts new file mode 100644 index 0000000..8436186 --- /dev/null +++ b/src/models/creation/CreateParticipant.ts @@ -0,0 +1,83 @@ +import { IsEmail, IsInt, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { ParticipantOnlyOneAddressAllowedError } from '../../errors/ParticipantErrors'; +import { Address } from '../entities/Address'; +import { CreateAddress } from './CreateAddress'; + +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's id. + * Optional - please provide either addressId or address. + */ + @IsInt() + @IsOptional() + addressId?: number; + + /** + * The new participant's address. + * Optional - please provide either addressId or address. + */ + @IsObject() + @IsOptional() + address?: CreateAddress; + + /** + * Creates a Participant entity from this. + */ + public async getAddress(): Promise
{ + let address: Address; + + if (this.addressId !== undefined && this.address !== undefined) { + throw new ParticipantOnlyOneAddressAllowedError + } + if (this.addressId === undefined && this.address === undefined) { + return null; + } + + if (this.addressId) { + return await getConnectionManager().get().getRepository(Address).findOne({ id: this.addressId }); + } + + return this.address.toAddress(); + } +} \ No newline at end of file diff --git a/src/models/creation/CreateRunner.ts b/src/models/creation/CreateRunner.ts index 178c5fb..b34ec9d 100644 --- a/src/models/creation/CreateRunner.ts +++ b/src/models/creation/CreateRunner.ts @@ -1,34 +1,52 @@ -import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsPositive, IsString } from 'class-validator'; +import { IsInt, IsOptional } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { RunnerGroupNeededError, RunnerGroupNotFoundError, RunnerOnlyOneGroupAllowedError } from '../../errors/RunnerErrors'; import { Runner } from '../entities/Runner'; -import { getConnectionManager, Repository } from 'typeorm'; -import { group } from 'console'; -import { RunnerOnlyOneGroupAllowedError, RunnerGroupNeededError, RunnerGroupNotFoundError } from '../../errors/RunnerErrors'; +import { RunnerGroup } from '../entities/RunnerGroup'; import { RunnerOrganisation } from '../entities/RunnerOrganisation'; import { RunnerTeam } from '../entities/RunnerTeam'; -import { RunnerGroup } from '../entities/RunnerGroup'; -import { Address } from 'cluster'; +import { CreateParticipant } from './CreateParticipant'; -export class CreateRunner { - @IsString() - firstname: string; - @IsString() - middlename?: string; - @IsString() - lastname: string; - @IsString() - phone?: string; - @IsString() - email?: string; +export class CreateRunner extends CreateParticipant { + + /** + * The new runner's team's id. + * Either provide this or his organisation's id. + */ @IsInt() @IsOptional() - teamId?: number + teamId?: number; + + /** + * The new runner's organisation's id. + * Either provide this or his teams's id. + */ @IsInt() @IsOptional() - orgId?: number + orgId?: number; + /** + * Creates a Runner entity from this. + */ public async toRunner(): Promise { let newRunner: Runner = new Runner(); + newRunner.firstname = this.firstname; + newRunner.middlename = this.middlename; + newRunner.lastname = this.lastname; + newRunner.phone = this.phone; + newRunner.email = this.email; + newRunner.group = await this.getGroup(); + newRunner.address = await this.getAddress(); + + return newRunner; + } + + /** + * Manages all the different ways a group can be provided. + */ + public async getGroup(): Promise { + let group: RunnerGroup; if (this.teamId !== undefined && this.orgId !== undefined) { throw new RunnerOnlyOneGroupAllowedError(); } @@ -37,22 +55,14 @@ export class CreateRunner { } if (this.teamId) { - newRunner.group = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: this.teamId }); + group = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: this.teamId }); } if (this.orgId) { - newRunner.group = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.orgId }); + group = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.orgId }); } - if (!newRunner.group) { + if (!group) { throw new RunnerGroupNotFoundError(); } - - newRunner.firstname = this.firstname; - newRunner.middlename = this.middlename; - newRunner.lastname = this.lastname; - newRunner.phone = this.phone; - newRunner.email = this.email; - - console.log(newRunner) - return newRunner; + return group; } } \ No newline at end of file diff --git a/src/models/creation/CreateRunnerOrganisation.ts b/src/models/creation/CreateRunnerOrganisation.ts index f054e5a..ff4ec65 100644 --- a/src/models/creation/CreateRunnerOrganisation.ts +++ b/src/models/creation/CreateRunnerOrganisation.ts @@ -1,10 +1,17 @@ -import { IsString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; import { RunnerOrganisation } from '../entities/RunnerOrganisation'; export class CreateRunnerOrganisation { + /** + * The Organisation's name. + */ @IsString() + @IsNotEmpty() name: string; + /** + * Creates a RunnerOrganisation entity from this. + */ public async toRunnerOrganisation(): Promise { let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation(); diff --git a/src/models/creation/CreateRunnerTeam.ts b/src/models/creation/CreateRunnerTeam.ts new file mode 100644 index 0000000..101a516 --- /dev/null +++ b/src/models/creation/CreateRunnerTeam.ts @@ -0,0 +1,36 @@ +import { IsInt, IsNotEmpty, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisationErrors'; +import { RunnerOrganisation } from '../entities/RunnerOrganisation'; +import { RunnerTeam } from '../entities/RunnerTeam'; + +export class CreateRunnerTeam { + /** + * The teams's name. + */ + @IsString() + @IsNotEmpty() + name: string; + + /** + * The team's parent group (organisation). + */ + @IsInt() + @IsNotEmpty() + parentId: number + + /** + * Creates a RunnerTeam entity from this. + */ + public async toRunnerTeam(): Promise { + let newRunnerTeam: RunnerTeam = new RunnerTeam(); + + newRunnerTeam.name = this.name; + newRunnerTeam.parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentId }); + if (!newRunnerTeam.parentGroup) { + throw new RunnerOrganisationNotFoundError(); + } + + return newRunnerTeam; + } +} \ No newline at end of file diff --git a/src/models/entities/DistanceDonation.ts b/src/models/entities/DistanceDonation.ts index b54c642..d5610d7 100644 --- a/src/models/entities/DistanceDonation.ts +++ b/src/models/entities/DistanceDonation.ts @@ -1,5 +1,5 @@ -import { Entity, Column, ManyToOne, ChildEntity } from "typeorm"; -import { IsInt, IsNotEmpty, IsPositive, } from "class-validator"; +import { IsInt, IsNotEmpty, IsPositive } from "class-validator"; +import { ChildEntity, Column, ManyToOne } from "typeorm"; import { Donation } from "./Donation"; import { Runner } from "./Runner"; @@ -29,10 +29,17 @@ export class DistanceDonation extends Donation { * The exact implementation may differ for each type of donation. */ @IsInt() - public get amount(): number { + public get amount() { + return this.getAmount(); + } + + /** + * The function that calculates the amount based on the runner object's distance. + */ + public async getAmount(): Promise { let calculatedAmount = -1; try { - calculatedAmount = this.amountPerDistance * this.runner.distance; + calculatedAmount = this.amountPerDistance * await this.runner.distance(); } catch (error) { throw error; } diff --git a/src/models/entities/Donation.ts b/src/models/entities/Donation.ts index c4ab699..2609a98 100644 --- a/src/models/entities/Donation.ts +++ b/src/models/entities/Donation.ts @@ -1,10 +1,9 @@ -import { PrimaryGeneratedColumn, Column, ManyToOne, Entity, TableInheritance } from "typeorm"; import { IsInt, IsNotEmpty, - IsOptional, - IsPositive, + IsOptional } from "class-validator"; +import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { Participant } from "./Participant"; /** @@ -32,5 +31,5 @@ export abstract class Donation { * The donation's amount in cents (or whatever your currency's smallest unit is.). * The exact implementation may differ for each type of donation. */ - abstract amount: number; + abstract amount: number | Promise; } \ No newline at end of file diff --git a/src/models/entities/Runner.ts b/src/models/entities/Runner.ts index e4d3ff7..7a8d0ad 100644 --- a/src/models/entities/Runner.ts +++ b/src/models/entities/Runner.ts @@ -1,9 +1,9 @@ -import { Entity, Column, OneToMany, ManyToOne, ChildEntity } from "typeorm"; -import { IsInt, IsNotEmpty, } from "class-validator"; -import { Participant } from "./Participant"; -import { RunnerGroup } from "./RunnerGroup"; +import { IsInt, IsNotEmpty } from "class-validator"; +import { ChildEntity, getConnectionManager, ManyToOne, OneToMany } from "typeorm"; import { DistanceDonation } from "./DistanceDonation"; +import { Participant } from "./Participant"; import { RunnerCard } from "./RunnerCard"; +import { RunnerGroup } from "./RunnerGroup"; import { Scan } from "./Scan"; /** @@ -36,9 +36,25 @@ export class Runner extends Participant { @OneToMany(() => Scan, scan => scan.runner, { nullable: true }) scans: Scan[]; - @IsInt() - public get distance(): number { - return this.scans.filter(scan => scan.valid === true).reduce((sum, current) => sum + current.distance, 0); + /** + * Returns all scans associated with this runner. + */ + public async getScans(): Promise { + return await getConnectionManager().get().getRepository(Scan).find({ runner: this }); } -} + /** + * Returns all valid scans associated with this runner. + */ + public async getValidScans(): Promise { + return (await this.getScans()).filter(scan => { scan.valid === true }); + } + + /** + * Returns the total distance ran by this runner. + */ + @IsInt() + public async distance(): Promise { + return await (await this.getValidScans()).reduce((sum, current) => sum + current.distance, 0); + } +} \ No newline at end of file diff --git a/src/models/entities/RunnerGroup.ts b/src/models/entities/RunnerGroup.ts index 8142754..2033d88 100644 --- a/src/models/entities/RunnerGroup.ts +++ b/src/models/entities/RunnerGroup.ts @@ -1,13 +1,12 @@ -import { PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, Entity, TableInheritance } from "typeorm"; import { IsInt, IsNotEmpty, IsOptional, - IsString, + IsString } from "class-validator"; +import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { GroupContact } from "./GroupContact"; import { Runner } from "./Runner"; -import { RunnerTeam } from "./RunnerTeam"; /** * Defines the runnerGroup interface. @@ -44,4 +43,6 @@ export abstract class RunnerGroup { */ @OneToMany(() => Runner, runner => runner.group, { nullable: true }) runners: Runner[]; + + public abstract getRunners(); } \ No newline at end of file diff --git a/src/models/entities/RunnerOrganisation.ts b/src/models/entities/RunnerOrganisation.ts index b0a7220..bfdfe49 100644 --- a/src/models/entities/RunnerOrganisation.ts +++ b/src/models/entities/RunnerOrganisation.ts @@ -1,7 +1,8 @@ -import { Entity, Column, ManyToOne, OneToMany, ChildEntity } from "typeorm"; -import { IsOptional, } from "class-validator"; -import { RunnerGroup } from "./RunnerGroup"; +import { IsOptional } from "class-validator"; +import { ChildEntity, getConnectionManager, ManyToOne, OneToMany } from "typeorm"; import { Address } from "./Address"; +import { Runner } from './Runner'; +import { RunnerGroup } from "./RunnerGroup"; import { RunnerTeam } from "./RunnerTeam"; /** @@ -23,4 +24,27 @@ export class RunnerOrganisation extends RunnerGroup { */ @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true }) teams: RunnerTeam[]; + + + /** + * Returns all runners associated with this organisation or it's teams. + */ + public async getRunners() { + let runners: Runner[] = new Array(); + const teams = await this.getTeams(); + + await teams.forEach(async team => { + runners.push(... await team.getRunners()); + }); + await runners.push(... await getConnectionManager().get().getRepository(Runner).find({ group: this })); + + return runners; + } + + /** + * Returns all teams associated with this organisation. + */ + public async getTeams() { + return await getConnectionManager().get().getRepository(RunnerTeam).find({ parentGroup: this }); + } } \ No newline at end of file diff --git a/src/models/entities/RunnerTeam.ts b/src/models/entities/RunnerTeam.ts index a9fb2f0..44a047a 100644 --- a/src/models/entities/RunnerTeam.ts +++ b/src/models/entities/RunnerTeam.ts @@ -1,5 +1,6 @@ -import { Entity, Column, ManyToOne, ChildEntity } from "typeorm"; import { IsNotEmpty } from "class-validator"; +import { ChildEntity, getConnectionManager, ManyToOne } from "typeorm"; +import { Runner } from './Runner'; import { RunnerGroup } from "./RunnerGroup"; import { RunnerOrganisation } from "./RunnerOrganisation"; @@ -16,4 +17,11 @@ export class RunnerTeam extends RunnerGroup { @IsNotEmpty() @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true }) parentGroup?: RunnerOrganisation; + + /** + * Returns all runners associated with this team. + */ + public async getRunners() { + return await getConnectionManager().get().getRepository(Runner).find({ group: this }); + } } \ No newline at end of file diff --git a/src/models/responses/ResponseParticipant.ts b/src/models/responses/ResponseParticipant.ts new file mode 100644 index 0000000..22ec2a3 --- /dev/null +++ b/src/models/responses/ResponseParticipant.ts @@ -0,0 +1,61 @@ +import { + IsInt, + + + + IsString +} from "class-validator"; +import { Participant } from '../entities/Participant'; + +/** + * Defines a participant response. +*/ +export abstract class ResponseParticipant { + /** + * Autogenerated unique id (primary key). + */ + @IsInt() + id: number;; + + /** + * The participant's first name. + */ + @IsString() + firstname: string; + + /** + * The participant's middle name. + * Optional. + */ + @IsString() + middlename?: string; + + /** + * The participant's last name. + */ + @IsString() + lastname: string; + + /** + * The participant's phone number. + * Optional. + */ + @IsString() + phone?: string; + + /** + * The participant's e-mail address. + * Optional. + */ + @IsString() + email?: string; + + public constructor(participant: Participant) { + this.id = participant.id; + this.firstname = participant.firstname; + this.middlename = participant.middlename; + this.lastname = participant.lastname; + this.phone = participant.phone; + this.email = participant.email; + } +} diff --git a/src/models/responses/ResponseRunner.ts b/src/models/responses/ResponseRunner.ts new file mode 100644 index 0000000..01327c2 --- /dev/null +++ b/src/models/responses/ResponseRunner.ts @@ -0,0 +1,32 @@ +import { + IsInt, + IsObject +} from "class-validator"; +import { Runner } from '../entities/Runner'; +import { RunnerGroup } from '../entities/RunnerGroup'; +import { ResponseParticipant } from './ResponseParticipant'; + +/** + * Defines RunnerTeam's response class. +*/ +export class ResponseRunner extends ResponseParticipant { + + /** + * The runner's currently ran distance in meters. + * Optional. + */ + @IsInt() + distance: number; + + /** + * The runner's group. + */ + @IsObject() + group: RunnerGroup; + + public constructor(runner: Runner) { + super(runner); + this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0); + this.group = runner.group; + } +} diff --git a/src/models/responses/ResponseRunnerGroup.ts b/src/models/responses/ResponseRunnerGroup.ts new file mode 100644 index 0000000..922f141 --- /dev/null +++ b/src/models/responses/ResponseRunnerGroup.ts @@ -0,0 +1,55 @@ +import { + IsInt, + + + + IsNotEmpty, + + + + IsObject, + + + + IsOptional, + + + + IsString +} from "class-validator"; +import { GroupContact } from '../entities/GroupContact'; +import { RunnerGroup } from '../entities/RunnerGroup'; + +/** + * Defines a track of given length. +*/ +export abstract class ResponseRunnerGroup { + /** + * Autogenerated unique id (primary key). + */ + @IsInt() + @IsNotEmpty() + id: number;; + + /** + * The groups's name. + */ + @IsString() + @IsNotEmpty() + name: string; + + + /** + * The group's contact. + * Optional. + */ + @IsObject() + @IsOptional() + contact?: GroupContact; + + public constructor(group: RunnerGroup) { + this.id = group.id; + this.name = group.name; + this.contact = group.contact; + } +} diff --git a/src/models/responses/ResponseRunnerOrganisation.ts b/src/models/responses/ResponseRunnerOrganisation.ts new file mode 100644 index 0000000..2aecb75 --- /dev/null +++ b/src/models/responses/ResponseRunnerOrganisation.ts @@ -0,0 +1,37 @@ +import { + IsArray, + IsNotEmpty, + IsObject +} from "class-validator"; +import { Address } from '../entities/Address'; +import { RunnerOrganisation } from '../entities/RunnerOrganisation'; +import { RunnerTeam } from '../entities/RunnerTeam'; +import { ResponseRunnerGroup } from './ResponseRunnerGroup'; + +/** + * Defines RunnerOrgs's response class. +*/ +export class ResponseRunnerOrganisation extends ResponseRunnerGroup { + + /** + * The orgs's address. + * Optional. + */ + @IsObject() + @IsNotEmpty() + address?: Address; + + /** + * The orgs associated teams. + */ + @IsObject() + @IsArray() + teams: RunnerTeam[]; + + + public constructor(org: RunnerOrganisation) { + super(org); + this.address = org.address; + this.teams = org.teams; + } +} diff --git a/src/models/responses/ResponseRunnerTeam.ts b/src/models/responses/ResponseRunnerTeam.ts new file mode 100644 index 0000000..b26e2c2 --- /dev/null +++ b/src/models/responses/ResponseRunnerTeam.ts @@ -0,0 +1,26 @@ +import { + IsNotEmpty, + IsObject +} from "class-validator"; +import { RunnerOrganisation } from '../entities/RunnerOrganisation'; +import { RunnerTeam } from '../entities/RunnerTeam'; +import { ResponseRunnerGroup } from './ResponseRunnerGroup'; + +/** + * Defines RunnerTeam's response class. +*/ +export class ResponseRunnerTeam extends ResponseRunnerGroup { + + /** + * The team's parent group (organisation). + * Optional. + */ + @IsObject() + @IsNotEmpty() + parentGroup: RunnerOrganisation; + + public constructor(team: RunnerTeam) { + super(team); + this.parentGroup = team.parentGroup; + } +} diff --git a/src/models/responses/ResponseTrack.ts b/src/models/responses/ResponseTrack.ts new file mode 100644 index 0000000..ce1d74d --- /dev/null +++ b/src/models/responses/ResponseTrack.ts @@ -0,0 +1,35 @@ +import { + IsInt, + + IsString +} from "class-validator"; +import { Track } from '../entities/Track'; + +/** + * Defines a track of given length. +*/ +export class ResponseTrack { + /** + * Autogenerated unique id (primary key). + */ + @IsInt() + id: number;; + + /** + * The track's name. + */ + @IsString() + name: string; + + /** + * The track's length/distance in meters. + */ + @IsInt() + distance: number; + + public constructor(track: Track) { + this.id = track.id; + this.name = track.name; + this.distance = track.distance; + } +}