Merge pull request 'Fully implemented addresses feature/105-addresses' (#107) from feature/105-addresses into dev
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #107
This commit is contained in:
Nicolai Ort 2021-01-19 15:37:35 +00:00
commit 5e368552ea
25 changed files with 1734 additions and 1497 deletions

View File

@ -6,4 +6,4 @@ DB_USER=unused
DB_PASSWORD=bla DB_PASSWORD=bla
DB_NAME=./test.sqlite DB_NAME=./test.sqlite
NODE_ENV=dev NODE_ENV=dev
POSTALCODE_COUNTRYCODE=null POSTALCODE_COUNTRYCODE=DE

View File

@ -29,7 +29,7 @@ export class RunnerOrganisationController {
@OpenAPI({ description: 'Lists all organisations. <br> This includes their address, contact and teams (if existing/associated).' }) @OpenAPI({ description: 'Lists all organisations. <br> This includes their address, contact and teams (if existing/associated).' })
async getAll() { async getAll() {
let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>(); let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>();
const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] }); const runners = await this.runnerOrganisationRepository.find({ relations: ['contact', 'teams'] });
runners.forEach(runner => { runners.forEach(runner => {
responseTeams.push(new ResponseRunnerOrganisation(runner)); responseTeams.push(new ResponseRunnerOrganisation(runner));
}); });
@ -43,7 +43,7 @@ export class RunnerOrganisationController {
@OnUndefined(RunnerOrganisationNotFoundError) @OnUndefined(RunnerOrganisationNotFoundError)
@OpenAPI({ description: 'Lists all information about the organisation whose id got provided.' }) @OpenAPI({ description: 'Lists all information about the organisation whose id got provided.' })
async getOne(@Param('id') id: number) { async getOne(@Param('id') id: number) {
let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] }); let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['contact', 'teams'] });
if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); } if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
return new ResponseRunnerOrganisation(runnerOrg); return new ResponseRunnerOrganisation(runnerOrg);
} }
@ -62,7 +62,7 @@ export class RunnerOrganisationController {
runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation); runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] })); return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['contact', 'teams'] }));
} }
@Put('/:id') @Put('/:id')
@ -84,7 +84,7 @@ export class RunnerOrganisationController {
await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation)); await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] })); return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['contact', 'teams'] }));
} }
@Delete('/:id') @Delete('/:id')
@ -98,7 +98,7 @@ export class RunnerOrganisationController {
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let organisation = await this.runnerOrganisationRepository.findOne({ id: id }); let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!organisation) { return null; } if (!organisation) { return null; }
let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] }); let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['contact', 'runners', 'teams'] });
if (!force) { if (!force) {
if (runnerOrganisation.teams.length != 0) { if (runnerOrganisation.teams.length != 0) {

View File

@ -1,24 +1,57 @@
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers'; import { BadRequestError } from 'routing-controllers';
/** /**
* Error to throw, when to provided address doesn't belong to the accepted types. * Error to throw when an address's postal code fails validation.
*/ */
export class AddressWrongTypeError extends NotAcceptableError { export class AddressPostalCodeInvalidError extends BadRequestError {
@IsString() @IsString()
name = "AddressWrongTypeError" name = "AddressPostalCodeInvalidError"
@IsString() @IsString()
message = "The address must be an existing address's id. \n You provided a object of another type." message = "The postal code you provided is invalid. \n Please check if your postal code follows the postal code validation guidelines."
} }
/** /**
* Error to throw, when a non-existent address get's loaded. * Error to throw when an non-empty address's first line isn't set.
*/ */
export class AddressNotFoundError extends NotFoundError { export class AddressFirstLineEmptyError extends BadRequestError {
@IsString() @IsString()
name = "AddressNotFoundError" name = "AddressFirstLineEmptyError"
@IsString() @IsString()
message = "The address you provided couldn't be located in the system. \n Please check your request." message = "You provided a empty first address line. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's postal code isn't set.
*/
export class AddressPostalCodeEmptyError extends BadRequestError {
@IsString()
name = "AddressPostalCodeEmptyError"
@IsString()
message = "You provided a empty postal code. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's city isn't set.
*/
export class AddressCityEmptyError extends BadRequestError {
@IsString()
name = "AddressCityEmptyError"
@IsString()
message = "You provided a empty city. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's country isn't set.
*/
export class AddressCountryEmptyError extends BadRequestError {
@IsString()
name = "AddressCountryEmptyError"
@IsString()
message = "You provided a empty country. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
} }

View File

@ -1,69 +0,0 @@
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
import { config } from '../../../config';
import { Address } from '../../entities/Address';
/**
* This classed is used to create a new Address entity from a json body (post request).
*/
export class CreateAddress {
/**
* The newaddress's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* The new address's first line.
* Containing the street and house number.
*/
@IsString()
@IsNotEmpty()
address1: string;
/**
* The new address's second line.
* Containing optional information.
*/
@IsString()
@IsOptional()
address2?: string;
/**
* The new address's postal code.
* This will get checked against the postal code syntax for the configured country.
*/
@IsString()
@IsNotEmpty()
@IsPostalCode(config.postalcode_validation_countrycode)
postalcode: string;
/**
* The new address's city.
*/
@IsString()
@IsNotEmpty()
city: string;
/**
* The new address's country.
*/
@IsString()
@IsNotEmpty()
country: string;
/**
* Creates a new Address entity from this.
*/
public async toEntity(): Promise<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;
}
}

View File

@ -1,5 +1,6 @@
import { IsBoolean, IsOptional } from 'class-validator'; import { IsBoolean, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors'; import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
import { Address } from '../../entities/Address';
import { Donor } from '../../entities/Donor'; import { Donor } from '../../entities/Donor';
import { CreateParticipant } from './CreateParticipant'; import { CreateParticipant } from './CreateParticipant';
@ -26,10 +27,10 @@ export class CreateDonor extends CreateParticipant {
newDonor.lastname = this.lastname; newDonor.lastname = this.lastname;
newDonor.phone = this.phone; newDonor.phone = this.phone;
newDonor.email = this.email; newDonor.email = this.email;
newDonor.address = await this.getAddress();
newDonor.receiptNeeded = this.receiptNeeded; newDonor.receiptNeeded = this.receiptNeeded;
newDonor.address = this.address;
if (this.receiptNeeded == true && this.address == null) { Address.validate(newDonor.address);
if (this.receiptNeeded == true && Address.isValidAddress(newDonor.address) == false) {
throw new DonorReceiptAddressNeededError() throw new DonorReceiptAddressNeededError()
} }

View File

@ -1,7 +1,5 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config'; import { config } from '../../../config';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address'; import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact'; import { GroupContact } from '../../entities/GroupContact';
@ -31,11 +29,11 @@ export class CreateGroupContact {
lastname: string; lastname: string;
/** /**
* The new contact's address's id. * The new contact's address.
*/ */
@IsInt()
@IsOptional() @IsOptional()
address?: number; @IsObject()
address?: Address;
/** /**
* The contact's phone number. * The contact's phone number.
@ -52,15 +50,6 @@ export class CreateGroupContact {
@IsEmail() @IsEmail()
email?: string; email?: string;
/**
* Gets the new contact's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
/** /**
* Creates a new Address entity from this. * Creates a new Address entity from this.
@ -72,7 +61,8 @@ export class CreateGroupContact {
contact.lastname = this.lastname; contact.lastname = this.lastname;
contact.email = this.email; contact.email = this.email;
contact.phone = this.phone; contact.phone = this.phone;
contact.address = await this.getAddress(); contact.address = this.address;
return null; Address.validate(contact.address);
return contact;
} }
} }

View File

@ -1,7 +1,5 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config'; import { config } from '../../../config';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address'; import { Address } from '../../entities/Address';
/** /**
@ -47,19 +45,9 @@ export abstract class CreateParticipant {
email?: string; email?: string;
/** /**
* The new participant's address's id. * The new participant's address.
*/ */
@IsInt()
@IsOptional() @IsOptional()
address?: number; @IsObject()
address?: Address;
/**
* Gets the new participant's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
} }

View File

@ -3,6 +3,7 @@ import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors'; import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors'; import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner'; import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup'; import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant'; import { CreateParticipant } from './CreateParticipant';
@ -30,7 +31,8 @@ export class CreateRunner extends CreateParticipant {
newRunner.phone = this.phone; newRunner.phone = this.phone;
newRunner.email = this.email; newRunner.email = this.email;
newRunner.group = await this.getGroup(); newRunner.group = await this.getGroup();
newRunner.address = await this.getAddress(); newRunner.address = this.address;
Address.validate(newRunner.address);
return newRunner; return newRunner;
} }

View File

@ -1,6 +1,4 @@
import { IsInt, IsOptional } from 'class-validator'; import { IsObject, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address'; import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation'; import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup'; import { CreateRunnerGroup } from './CreateRunnerGroup';
@ -10,21 +8,11 @@ import { CreateRunnerGroup } from './CreateRunnerGroup';
*/ */
export class CreateRunnerOrganisation extends CreateRunnerGroup { export class CreateRunnerOrganisation extends CreateRunnerGroup {
/** /**
* The new organisation's address's id. * The new organisation's address.
*/ */
@IsInt()
@IsOptional() @IsOptional()
address?: number; @IsObject()
address?: Address;
/**
* Gets the org's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
/** /**
* Creates a new RunnerOrganisation entity from this. * Creates a new RunnerOrganisation entity from this.
@ -34,7 +22,8 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup {
newRunnerOrganisation.name = this.name; newRunnerOrganisation.name = this.name;
newRunnerOrganisation.contact = await this.getContact(); newRunnerOrganisation.contact = await this.getContact();
newRunnerOrganisation.address = await this.getAddress(); newRunnerOrganisation.address = this.address;
Address.validate(newRunnerOrganisation.address);
return newRunnerOrganisation; return newRunnerOrganisation;
} }

View File

@ -1,5 +1,6 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator'; import { IsBoolean, IsInt, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors'; import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
import { Address } from '../../entities/Address';
import { Donor } from '../../entities/Donor'; import { Donor } from '../../entities/Donor';
import { CreateParticipant } from '../create/CreateParticipant'; import { CreateParticipant } from '../create/CreateParticipant';
@ -33,9 +34,10 @@ export class UpdateDonor extends CreateParticipant {
donor.phone = this.phone; donor.phone = this.phone;
donor.email = this.email; donor.email = this.email;
donor.receiptNeeded = this.receiptNeeded; donor.receiptNeeded = this.receiptNeeded;
donor.address = await this.getAddress(); if (!this.address) { donor.address.reset(); }
else { donor.address = this.address; }
if (this.receiptNeeded == true && this.address == null) { Address.validate(donor.address);
if (this.receiptNeeded == true && Address.isValidAddress(donor.address) == false) {
throw new DonorReceiptAddressNeededError() throw new DonorReceiptAddressNeededError()
} }

View File

@ -2,6 +2,7 @@ import { IsInt, IsPositive } from 'class-validator';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors'; import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner'; import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup'; import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from '../create/CreateParticipant'; import { CreateParticipant } from '../create/CreateParticipant';
@ -35,7 +36,9 @@ export class UpdateRunner extends CreateParticipant {
runner.phone = this.phone; runner.phone = this.phone;
runner.email = this.email; runner.email = this.email;
runner.group = await this.getGroup(); runner.group = await this.getGroup();
runner.address = await this.getAddress(); if (!this.address) { runner.address.reset(); }
else { runner.address = this.address; }
Address.validate(runner.address);
return runner; return runner;
} }

View File

@ -1,6 +1,4 @@
import { IsInt, IsOptional } from 'class-validator'; import { IsInt, IsObject, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address'; import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation'; import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from '../create/CreateRunnerGroup'; import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
@ -18,21 +16,11 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup {
id: number; id: number;
/** /**
* The updated organisation's address's id. * The updated organisation's address.
*/ */
@IsInt()
@IsOptional() @IsOptional()
address?: number; @IsObject()
address?: Address;
/**
* Loads the organisation's address based on it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
/** /**
* Updates a provided RunnerOrganisation entity based on this. * Updates a provided RunnerOrganisation entity based on this.
@ -41,7 +29,9 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup {
organisation.name = this.name; organisation.name = this.name;
organisation.contact = await this.getContact(); organisation.contact = await this.getContact();
organisation.address = await this.getAddress(); if (!this.address) { organisation.address.reset(); }
else { organisation.address = this.address; }
Address.validate(organisation.address);
return organisation; return organisation;
} }

View File

@ -1,36 +1,19 @@
import { import {
IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsPostalCode, IsPostalCode,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column } from "typeorm";
import ValidatorJS from 'validator';
import { config } from '../../config'; import { config } from '../../config';
import { IAddressUser } from './IAddressUser'; import { AddressCityEmptyError, AddressCountryEmptyError, AddressFirstLineEmptyError, AddressPostalCodeEmptyError, AddressPostalCodeInvalidError } from '../../errors/AddressErrors';
/** /**
* Defines the Address entity. * Defines the Address class.
* Implemented this way to prevent any formatting differences. * Implemented this way to prevent any formatting differences.
*/ */
@Entity()
export class Address { export class Address {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsInt()
id: number;
/**
* The address's description.
* Optional and mostly for UX.
*/
@Column({ nullable: true })
@IsString()
@IsOptional()
description?: string;
/** /**
* The address's first line. * The address's first line.
* Containing the street and house number. * Containing the street and house number.
@ -75,16 +58,36 @@ export class Address {
@IsNotEmpty() @IsNotEmpty()
country: string; country: string;
/** public reset() {
* Used to link the address to participants. this.address1 = null;
*/ this.address2 = null;
@OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true }) this.city = null;
addressUsers: IAddressUser[]; this.country = null;
this.postalcode = null;
}
/** /**
* Turns this entity into it's response class. * Checks if this is a valid address
*/ */
public toResponse() { public static isValidAddress(address: Address): Boolean {
return new Error("NotImplemented"); if (address == null) { return false; }
if (address.address1 == null || address.city == null || address.country == null || address.postalcode == null) { return false; }
if (ValidatorJS.isPostalCode(address.postalcode, config.postalcode_validation_countrycode) == false) { return false; }
return true;
}
/**
* This function validates addresses.
* This is a workaround for non-existant class validation for embedded entities.
* @param address The address that shall get validated.
*/
public static validate(address: Address) {
if (address == null) { return; }
if (address.address1 == null && address.city == null && address.country == null && address.postalcode == null) { return; }
if (address.address1 == null) { throw new AddressFirstLineEmptyError(); }
if (address.postalcode == null) { throw new AddressPostalCodeEmptyError(); }
if (address.city == null) { throw new AddressCityEmptyError(); }
if (address.country == null) { throw new AddressCountryEmptyError(); }
if (ValidatorJS.isPostalCode(address.postalcode.toString(), config.postalcode_validation_countrycode) == false) { throw new AddressPostalCodeInvalidError(); }
} }
} }

View File

@ -7,10 +7,9 @@ import {
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config'; import { config } from '../../config';
import { Address } from "./Address"; import { Address } from "./Address";
import { IAddressUser } from './IAddressUser';
import { RunnerGroup } from "./RunnerGroup"; import { RunnerGroup } from "./RunnerGroup";
/** /**
@ -18,7 +17,7 @@ import { RunnerGroup } from "./RunnerGroup";
* Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups. * Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
*/ */
@Entity() @Entity()
export class GroupContact implements IAddressUser { export class GroupContact {
/** /**
* Autogenerated unique id (primary key). * Autogenerated unique id (primary key).
*/ */
@ -55,7 +54,7 @@ export class GroupContact implements IAddressUser {
* This is a address object to prevent any formatting differences. * This is a address object to prevent any formatting differences.
*/ */
@IsOptional() @IsOptional()
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) @Column(type => Address)
address?: Address; address?: Address;
/** /**

View File

@ -1,20 +0,0 @@
import { Entity, ManyToOne, PrimaryColumn } from 'typeorm';
import { Address } from './Address';
/**
* The interface(tm) all entities using addresses have to implement.
* This is a abstract class, because apparently typeorm can't really work with interfaces :/
*/
@Entity()
export abstract class IAddressUser {
@PrimaryColumn()
id: number;
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
address?: Address
/**
* Turns this entity into it's response class.
*/
public abstract toResponse();
}

View File

@ -7,11 +7,10 @@ import {
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { Column, Entity, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { config } from '../../config'; import { config } from '../../config';
import { ResponseParticipant } from '../responses/ResponseParticipant'; import { ResponseParticipant } from '../responses/ResponseParticipant';
import { Address } from "./Address"; import { Address } from "./Address";
import { IAddressUser } from './IAddressUser';
/** /**
* Defines the Participant entity. * Defines the Participant entity.
@ -19,7 +18,7 @@ import { IAddressUser } from './IAddressUser';
*/ */
@Entity() @Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } }) @TableInheritance({ column: { name: "type", type: "varchar" } })
export abstract class Participant implements IAddressUser { export abstract class Participant {
/** /**
* Autogenerated unique id (primary key). * Autogenerated unique id (primary key).
*/ */
@ -55,7 +54,7 @@ export abstract class Participant implements IAddressUser {
* The participant's address. * The participant's address.
* This is a address object to prevent any formatting differences. * This is a address object to prevent any formatting differences.
*/ */
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) @Column(type => Address)
address?: Address; address?: Address;
/** /**

View File

@ -1,8 +1,7 @@
import { IsInt, IsOptional } from "class-validator"; import { IsInt, IsOptional } from "class-validator";
import { ChildEntity, ManyToOne, OneToMany } from "typeorm"; import { ChildEntity, Column, OneToMany } from "typeorm";
import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation'; import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation';
import { Address } from './Address'; import { Address } from './Address';
import { IAddressUser } from './IAddressUser';
import { Runner } from './Runner'; import { Runner } from './Runner';
import { RunnerGroup } from "./RunnerGroup"; import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam"; import { RunnerTeam } from "./RunnerTeam";
@ -12,13 +11,13 @@ import { RunnerTeam } from "./RunnerTeam";
* This usually is a school, club or company. * This usually is a school, club or company.
*/ */
@ChildEntity() @ChildEntity()
export class RunnerOrganisation extends RunnerGroup implements IAddressUser { export class RunnerOrganisation extends RunnerGroup {
/** /**
* The organisations's address. * The organisations's address.
*/ */
@IsOptional() @IsOptional()
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) @Column(type => Address)
address?: Address; address?: Address;
/** /**

View File

@ -1,4 +1,5 @@
import { IsInt, IsString } from "class-validator"; import { IsInt, IsObject, IsOptional, IsString } from "class-validator";
import { Address } from '../entities/Address';
import { Participant } from '../entities/Participant'; import { Participant } from '../entities/Participant';
/** /**
@ -41,6 +42,13 @@ export abstract class ResponseParticipant {
@IsString() @IsString()
email?: string; email?: string;
/**
* The participant's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/** /**
* Creates a ResponseParticipant object from a participant. * Creates a ResponseParticipant object from a participant.
* @param participant The participant the response shall be build for. * @param participant The participant the response shall be build for.
@ -52,5 +60,6 @@ export abstract class ResponseParticipant {
this.lastname = participant.lastname; this.lastname = participant.lastname;
this.phone = participant.phone; this.phone = participant.phone;
this.email = participant.email; this.email = participant.email;
this.address = participant.address;
} }
} }

View File

@ -29,16 +29,6 @@ describe('POST /api/donors with errors', () => {
expect(res2.status).toEqual(400); expect(res2.status).toEqual(400);
expect(res2.headers['content-type']).toContain("application/json") expect(res2.headers['content-type']).toContain("application/json")
}); });
it('creating a new donor with a invalid address should return 404', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"middlename": "middle",
"lastname": "last",
"address": 99999999999999999999999999
}, axios_config);
expect(res2.status).toEqual(404);
expect(res2.headers['content-type']).toContain("application/json")
});
it('creating a new donor with a invalid phone number should return 400', async () => { it('creating a new donor with a invalid phone number should return 400', async () => {
const res2 = await axios.post(base + '/api/donors', { const res2 = await axios.post(base + '/api/donors', {
"firstname": "first", "firstname": "first",

View File

@ -60,7 +60,7 @@ describe('Update donor without address but receiptNeeded=true should fail', () =
it('creating a new donor with only needed params should return 200', async () => { it('creating a new donor with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', { const res2 = await axios.post(base + '/api/donors', {
"firstname": "first", "firstname": "first",
"lastname": "last", "lastname": "testtest",
}, axios_config); }, axios_config);
added_donor = res2.data; added_donor = res2.data;
expect(res2.status).toEqual(200); expect(res2.status).toEqual(200);

View File

@ -56,7 +56,13 @@ describe('adding + getting from all orgs', () => {
expect(added_org).toEqual({ expect(added_org).toEqual({
"name": "test123", "name": "test123",
"contact": null, "contact": null,
"address": null, "address": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"postalcode": null,
},
"teams": [] "teams": []
}) })
}); });
@ -83,7 +89,13 @@ describe('adding + getting explicitly', () => {
expect(added_org2).toEqual({ expect(added_org2).toEqual({
"name": "test123", "name": "test123",
"contact": null, "contact": null,
"address": null, "address": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"postalcode": null,
},
"teams": [] "teams": []
}) })
}); });

View File

@ -44,7 +44,13 @@ describe('adding + deletion (successfull)', () => {
expect(added_org2).toEqual({ expect(added_org2).toEqual({
"name": "test123", "name": "test123",
"contact": null, "contact": null,
"address": null, "address": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"postalcode": null,
},
"teams": [] "teams": []
}); });
}); });
@ -121,7 +127,13 @@ describe('adding + deletion with teams still existing (with force)', () => {
expect(added_org2).toEqual({ expect(added_org2).toEqual({
"name": "test123", "name": "test123",
"contact": null, "contact": null,
"address": null "address": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"postalcode": null,
},
}); });
}); });
it('check if org really was deleted', async () => { it('check if org really was deleted', async () => {

View File

@ -19,30 +19,36 @@ describe('adding + updating name', () => {
let added_org_id let added_org_id
let added_org let added_org
it('creating a new org with just a name should return 200', async () => { it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', { const res = await axios.post(base + '/api/organisations', {
"name": "test123" "name": "test123"
}, axios_config); }, axios_config);
added_org = res1.data added_org = res.data
added_org_id = added_org.id; added_org_id = added_org.id;
expect(res1.status).toEqual(200); expect(res.status).toEqual(200);
expect(res1.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
}); });
it('update org', async () => { it('update org', async () => {
const res2 = await axios.put(base + '/api/organisations/' + added_org_id, { const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id, "id": added_org_id,
"name": "testlelele", "name": "testlelele",
"contact": null, "contact": null,
"address": null, "address": null,
}, axios_config); }, axios_config);
expect(res2.status).toEqual(200); expect(res.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
let added_org2 = res2.data let added_org2 = res.data
added_org_id = added_org2.id; added_org_id = added_org2.id;
delete added_org2.id delete added_org2.id
expect(added_org2).toEqual({ expect(added_org2).toEqual({
"name": "testlelele", "name": "testlelele",
"contact": null, "contact": null,
"address": null, "address": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"postalcode": null,
},
"teams": [] "teams": []
}) })
}); });
@ -52,22 +58,326 @@ describe('adding + try updating id (should return 406)', () => {
let added_org_id let added_org_id
let added_org let added_org
it('creating a new org with just a name should return 200', async () => { it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', { const res = await axios.post(base + '/api/organisations', {
"name": "test123" "name": "test123"
}, axios_config); }, axios_config);
added_org = res1.data added_org = res.data
added_org_id = added_org.id; added_org_id = added_org.id;
expect(res1.status).toEqual(200); expect(res.status).toEqual(200);
expect(res1.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
}); });
it('update org', async () => { it('update org', async () => {
const res2 = await axios.put(base + '/api/organisations/' + added_org_id, { const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id + 1, "id": added_org_id + 1,
"name": "testlelele", "name": "testlelele",
"contact": null, "contact": null,
"address": null, "address": null,
}, axios_config); }, axios_config);
expect(res2.status).toEqual(406); expect(res.status).toEqual(406);
expect(res2.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('adding + updateing address valid)', () => {
let added_org_id
let added_org
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
"name": "test123"
}, axios_config);
added_org = res.data
added_org_id = added_org.id;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('adding address to org should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test1",
"address2": null,
"city": "Herzogenaurach",
"country": "Burkina Faso",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test1",
"address2": null,
"city": "Herzogenaurach",
"country": "Burkina Faso",
"postalcode": "90174"
},
"teams": []
});
});
it('updateing address\'s first line should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": null,
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": null,
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": "90174"
},
"teams": []
});
});
it('updateing address\'s second line should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": "90174"
},
"teams": []
});
});
it('updateing address\'s city should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "Kaya",
"country": "Burkina Faso",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "Kaya",
"country": "Burkina Faso",
"postalcode": "90174"
},
"teams": []
});
});
it('updateing address\'s country should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "Kaya",
"country": "Germany",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "Kaya",
"country": "Germany",
"postalcode": "90174"
},
"teams": []
});
});
it('updateing address\'s postal code should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "Kaya",
"country": "Germany",
"postalcode": "91065"
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test2",
"address2": "Test3",
"city": "Kaya",
"country": "Germany",
"postalcode": "91065"
},
"teams": []
});
});
it('removing org\'s should return 200', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"postalcode": null
},
"teams": []
});
});
});
// ---------------
describe('adding + updateing address invalid)', () => {
let added_org_id
let added_org
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
"name": "test123"
}, axios_config);
added_org = res.data
added_org_id = added_org.id;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('adding address to org w/o address1 should return 400', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": null,
"address2": null,
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json");
});
it('adding address to org w/o city should return 400', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test1",
"address2": null,
"city": null,
"country": "Burkina Faso",
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json");
});
it('adding address to org w/o country should return 400', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test1",
"address2": null,
"city": "TestCity",
"country": null,
"postalcode": "90174"
}
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json");
});
it('adding address to org w/o postal code should return 400', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test1",
"address2": null,
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": null
}
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json");
});
it('adding address to org w/ invalid postal code should return 400', async () => {
const res = await axios.put(base + '/api/organisations/' + added_org_id, {
"id": added_org_id,
"name": "testlelele",
"contact": null,
"address": {
"address1": "Test1",
"address2": null,
"city": "TestCity",
"country": "Burkina Faso",
"postalcode": "-1"
}
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json");
}); });
}); });

View File

@ -123,7 +123,6 @@ describe('add+update parent org (valid)', () => {
let updated_team = res4.data; let updated_team = res4.data;
expect(res4.status).toEqual(200); expect(res4.status).toEqual(200);
expect(res4.headers['content-type']).toContain("application/json") expect(res4.headers['content-type']).toContain("application/json")
delete added_org2.address;
delete added_org2.contact; delete added_org2.contact;
delete added_org2.teams; delete added_org2.teams;
expect(updated_team.parentGroup).toEqual(added_org2) expect(updated_team.parentGroup).toEqual(added_org2)

View File

@ -44,7 +44,6 @@ describe('Update runner name after adding', () => {
expect(res3.status).toEqual(200); expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json") expect(res3.headers['content-type']).toContain("application/json")
updated_runner = res3.data; updated_runner = res3.data;
delete added_org.address;
delete added_org.contact; delete added_org.contact;
delete added_org.teams; delete added_org.teams;
runnercopy.group = added_org; runnercopy.group = added_org;
@ -56,7 +55,6 @@ describe('Update runner group after adding', () => {
let added_org_id; let added_org_id;
let added_org_2; let added_org_2;
let added_runner; let added_runner;
let updated_runner;
it('creating a new org with just a name should return 200', async () => { it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', { const res1 = await axios.post(base + '/api/organisations', {
"name": "test123" "name": "test123"
@ -81,7 +79,6 @@ describe('Update runner group after adding', () => {
"name": "test123" "name": "test123"
}, axios_config); }, axios_config);
added_org_2 = res3.data added_org_2 = res3.data
delete added_org_2.address;
delete added_org_2.contact; delete added_org_2.contact;
delete added_org_2.teams; delete added_org_2.teams;
expect(res3.status).toEqual(200); expect(res3.status).toEqual(200);
@ -92,8 +89,7 @@ describe('Update runner group after adding', () => {
const res3 = await axios.put(base + '/api/runners/' + added_runner.id, added_runner, axios_config); const res3 = await axios.put(base + '/api/runners/' + added_runner.id, added_runner, axios_config);
expect(res3.status).toEqual(200); expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json") expect(res3.headers['content-type']).toContain("application/json")
updated_runner = res3.data expect(res3.data.group).toEqual(added_org_2);
expect(updated_runner.group).toEqual(added_org_2);
}); });
}); });
// --------------- // ---------------