From 3e7190e279181c5f99d890ca141489b24908b904 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 15:56:03 +0100 Subject: [PATCH 01/27] Added barebones contact controller from donor-controller ref #104 --- src/controllers/ContactController.ts | 107 +++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/controllers/ContactController.ts diff --git a/src/controllers/ContactController.ts b/src/controllers/ContactController.ts new file mode 100644 index 0000000..75a9c26 --- /dev/null +++ b/src/controllers/ContactController.ts @@ -0,0 +1,107 @@ +import { JsonController } from 'routing-controllers'; +import { OpenAPI } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { GroupContact } from '../models/entities/GroupContact'; + +@JsonController('/contacts') +@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) +export class ContactController { + private contactRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.contactRepository = getConnectionManager().get().getRepository(GroupContact); + } + + // @Get() + // @Authorized("DONOR:GET") + // @ResponseSchema(ResponseDonor, { isArray: true }) + // @OpenAPI({ description: 'Lists all contact.
This includes the contact\'s current donation amount.' }) + // async getAll() { + // let responseDonors: ResponseDonor[] = new Array(); + // const contacts = await this.contactRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); + // contacts.forEach(contact => { + // responseDonors.push(new ResponseDonor(contact)); + // }); + // return responseDonors; + // } + + // @Get('/:id') + // @Authorized("DONOR:GET") + // @ResponseSchema(ResponseDonor) + // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + // @OnUndefined(DonorNotFoundError) + // @OpenAPI({ description: 'Lists all information about the contact whose id got provided.
This includes the contact\'s current donation amount.' }) + // async getOne(@Param('id') id: number) { + // let contact = await this.contactRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }) + // if (!contact) { throw new DonorNotFoundError(); } + // return new ResponseDonor(contact); + // } + + // @Post() + // @Authorized("DONOR:CREATE") + // @ResponseSchema(ResponseDonor) + // @OpenAPI({ description: 'Create a new contact.' }) + // async post(@Body({ validate: true }) createRunner: CreateDonor) { + // let contact; + // try { + // contact = await createRunner.toEntity(); + // } catch (error) { + // throw error; + // } + + // contact = await this.contactRepository.save(contact) + // return new ResponseDonor(await this.contactRepository.findOne(contact, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); + // } + + // @Put('/:id') + // @Authorized("DONOR:UPDATE") + // @ResponseSchema(ResponseDonor) + // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + // @ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 }) + // @OpenAPI({ description: "Update the contact whose id you provided.
Please remember that ids can't be changed." }) + // async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateDonor) { + // let oldDonor = await this.contactRepository.findOne({ id: id }); + + // if (!oldDonor) { + // throw new DonorNotFoundError(); + // } + + // if (oldDonor.id != contact.id) { + // throw new DonorIdsNotMatchingError(); + // } + + // await this.contactRepository.save(await contact.update(oldDonor)); + // return new ResponseDonor(await this.contactRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); + // } + + // @Delete('/:id') + // @Authorized("DONOR:DELETE") + // @ResponseSchema(ResponseDonor) + // @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + // @OnUndefined(204) + // @OpenAPI({ description: 'Delete the contact whose id you provided.
If no contact with this id exists it will just return 204(no content).
If the contact still has donations associated this will fail, please provide the query param ?force=true to delete the contact with all associated donations.' }) + // async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + // let contact = await this.contactRepository.findOne({ id: id }); + // if (!contact) { return null; } + // const responseDonor = await this.contactRepository.findOne(contact, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); + + // if (!contact) { + // throw new DonorNotFoundError(); + // } + + // const contactDonations = (await this.contactRepository.findOne({ id: contact.id }, { relations: ["donations"] })).donations; + // if (contactDonations.length > 0 && !force) { + // throw new DonorHasDonationsError(); + // } + // const donationController = new DonationController(); + // for (let donation of contactDonations) { + // await donationController.remove(donation.id, force); + // } + + // await this.contactRepository.delete(contact); + // return new ResponseDonor(responseDonor); + // } +} From d12801e34d57cec0e867f69123e3be39ed2fe2f5 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 15:56:55 +0100 Subject: [PATCH 02/27] Added contact permission target ref #104 --- src/models/enums/PermissionTargets.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts index 86c547d..1dac026 100644 --- a/src/models/enums/PermissionTargets.ts +++ b/src/models/enums/PermissionTargets.ts @@ -14,5 +14,6 @@ export enum PermissionTarget { SCAN = 'SCAN', STATION = 'STATION', CARD = 'CARD', - DONATION = 'DONATION' + DONATION = 'DONATION', + CONTACT = 'CONTACT' } \ No newline at end of file From 1407fe36f3637d6c53024c48788b318d985f8960 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 16:02:13 +0100 Subject: [PATCH 03/27] Added a contact response class ref #104 --- src/models/entities/GroupContact.ts | 5 +- src/models/responses/ResponseGroupContact.ts | 67 ++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/models/responses/ResponseGroupContact.ts diff --git a/src/models/entities/GroupContact.ts b/src/models/entities/GroupContact.ts index f650259..bc624dd 100644 --- a/src/models/entities/GroupContact.ts +++ b/src/models/entities/GroupContact.ts @@ -9,6 +9,7 @@ import { } from "class-validator"; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { config } from '../../config'; +import { ResponseGroupContact } from '../responses/ResponseGroupContact'; import { Address } from "./Address"; import { IAddressUser } from './IAddressUser'; import { RunnerGroup } from "./RunnerGroup"; @@ -85,7 +86,7 @@ export class GroupContact implements IAddressUser { /** * Turns this entity into it's response class. */ - public toResponse() { - return new Error("NotImplemented"); + public toResponse(): ResponseGroupContact { + return new ResponseGroupContact(this); } } \ No newline at end of file diff --git a/src/models/responses/ResponseGroupContact.ts b/src/models/responses/ResponseGroupContact.ts new file mode 100644 index 0000000..f7e63b3 --- /dev/null +++ b/src/models/responses/ResponseGroupContact.ts @@ -0,0 +1,67 @@ +import { IsInt, IsString } from "class-validator"; +import { GroupContact } from '../entities/GroupContact'; +import { ResponseRunnerGroup } from './ResponseRunnerGroup'; + +/** + * Defines the group contact response. +*/ +export class ResponseGroupContact { + /** + * The contact's id. + */ + @IsInt() + id: number; + + /** + * The contact's first name. + */ + @IsString() + firstname: string; + + /** + * The contact's middle name. + */ + @IsString() + middlename?: string; + + /** + * The contact's last name. + */ + @IsString() + lastname: string; + + /** + * The contact's phone number. + */ + @IsString() + phone?: string; + + /** + * The contact's e-mail address. + */ + @IsString() + email?: string; + + /** + * The contact's associated runner groups. + */ + groups: ResponseRunnerGroup[]; + + //TODO: Address + + /** + * Creates a ResponseGroupContact object from a contact. + * @param contact The contact the response shall be build for. + */ + public constructor(contact: GroupContact) { + this.id = contact.id; + this.firstname = contact.firstname; + this.middlename = contact.middlename; + this.lastname = contact.lastname; + this.phone = contact.phone; + this.email = contact.email; + for (let group of contact.groups) { + this.groups.push(group.toResponse()); + } + } +} From ab70f7e49893344dde8f5af93f571a5d67818e19 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 16:05:35 +0100 Subject: [PATCH 04/27] Implemented the get endpoints ref #104 --- src/controllers/ContactController.ts | 52 +++++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/controllers/ContactController.ts b/src/controllers/ContactController.ts index 75a9c26..f9fc752 100644 --- a/src/controllers/ContactController.ts +++ b/src/controllers/ContactController.ts @@ -1,7 +1,9 @@ -import { JsonController } from 'routing-controllers'; -import { OpenAPI } from 'routing-controllers-openapi'; +import { Authorized, Get, JsonController, OnUndefined, Param } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; +import { GroupContactNotFoundError } from '../errors/GroupContactErrors'; import { GroupContact } from '../models/entities/GroupContact'; +import { ResponseGroupContact } from '../models/responses/ResponseGroupContact'; @JsonController('/contacts') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @@ -15,30 +17,30 @@ export class ContactController { this.contactRepository = getConnectionManager().get().getRepository(GroupContact); } - // @Get() - // @Authorized("DONOR:GET") - // @ResponseSchema(ResponseDonor, { isArray: true }) - // @OpenAPI({ description: 'Lists all contact.
This includes the contact\'s current donation amount.' }) - // async getAll() { - // let responseDonors: ResponseDonor[] = new Array(); - // const contacts = await this.contactRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); - // contacts.forEach(contact => { - // responseDonors.push(new ResponseDonor(contact)); - // }); - // return responseDonors; - // } + @Get() + @Authorized("CONTACT:GET") + @ResponseSchema(ResponseGroupContact, { isArray: true }) + @OpenAPI({ description: 'Lists all contacts.
This includes the contact\'s associated groups.' }) + async getAll() { + let responseContacts: ResponseGroupContact[] = new Array(); + const contacts = await this.contactRepository.find({ relations: ['groups'] }); + contacts.forEach(contact => { + responseContacts.push(contact.toResponse()); + }); + return responseContacts; + } - // @Get('/:id') - // @Authorized("DONOR:GET") - // @ResponseSchema(ResponseDonor) - // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) - // @OnUndefined(DonorNotFoundError) - // @OpenAPI({ description: 'Lists all information about the contact whose id got provided.
This includes the contact\'s current donation amount.' }) - // async getOne(@Param('id') id: number) { - // let contact = await this.contactRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }) - // if (!contact) { throw new DonorNotFoundError(); } - // return new ResponseDonor(contact); - // } + @Get('/:id') + @Authorized("DONOR:GET") + @ResponseSchema(ResponseGroupContact) + @ResponseSchema(GroupContactNotFoundError, { statusCode: 404 }) + @OnUndefined(GroupContactNotFoundError) + @OpenAPI({ description: 'Lists all information about the contact whose id got provided.
This includes the contact\'s associated groups.' }) + async getOne(@Param('id') id: number) { + let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups'] }) + if (!contact) { throw new GroupContactNotFoundError(); } + return contact.toResponse(); + } // @Post() // @Authorized("DONOR:CREATE") From a9a5eb673570bb3c0d1a55bb1292e208493663bd Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 16:06:42 +0100 Subject: [PATCH 05/27] Updated the contact errors ref #104 --- src/errors/GroupContactErrors.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/errors/GroupContactErrors.ts b/src/errors/GroupContactErrors.ts index 92fd4a2..fffbdd5 100644 --- a/src/errors/GroupContactErrors.ts +++ b/src/errors/GroupContactErrors.ts @@ -2,18 +2,7 @@ import { IsString } from 'class-validator'; import { NotAcceptableError, NotFoundError } from 'routing-controllers'; /** - * Error to throw, when a provided groupContact doesn't belong to the accepted types. - */ -export class GroupContactWrongTypeError extends NotAcceptableError { - @IsString() - name = "GroupContactWrongTypeError" - - @IsString() - message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type." -} - -/** - * Error to throw, when a non-existent groupContact get's loaded. + * Error to throw, when a non-existent contact get's requested. */ export class GroupContactNotFoundError extends NotFoundError { @IsString() @@ -21,4 +10,16 @@ export class GroupContactNotFoundError extends NotFoundError { @IsString() message = "The groupContact you provided couldn't be located in the system. \n Please check your request." -} \ No newline at end of file +} + +/** + * Error to throw when two contacts' ids don't match. + * Usually occurs when a user tries to change a contact's id. + */ +export class GroupContactIdsNotMatchingError extends NotAcceptableError { + @IsString() + name = "GroupContactIdsNotMatchingError" + + @IsString() + message = "The ids don't match! \n And if you wanted to change a contact's id: This isn't allowed!" +} From 0379786cbda057ad95d709fa135d34beb0db8de1 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 16:09:23 +0100 Subject: [PATCH 06/27] Implemented contact deletion ref #104 --- src/controllers/ContactController.ts | 44 ++++++++++------------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/controllers/ContactController.ts b/src/controllers/ContactController.ts index f9fc752..1a20d82 100644 --- a/src/controllers/ContactController.ts +++ b/src/controllers/ContactController.ts @@ -1,8 +1,9 @@ -import { Authorized, Get, JsonController, OnUndefined, Param } from 'routing-controllers'; +import { Authorized, Delete, Get, JsonController, OnUndefined, Param, QueryParam } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { GroupContactNotFoundError } from '../errors/GroupContactErrors'; import { GroupContact } from '../models/entities/GroupContact'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseGroupContact } from '../models/responses/ResponseGroupContact'; @JsonController('/contacts') @@ -31,7 +32,7 @@ export class ContactController { } @Get('/:id') - @Authorized("DONOR:GET") + @Authorized("CONTACT:GET") @ResponseSchema(ResponseGroupContact) @ResponseSchema(GroupContactNotFoundError, { statusCode: 404 }) @OnUndefined(GroupContactNotFoundError) @@ -79,31 +80,18 @@ export class ContactController { // return new ResponseDonor(await this.contactRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); // } - // @Delete('/:id') - // @Authorized("DONOR:DELETE") - // @ResponseSchema(ResponseDonor) - // @ResponseSchema(ResponseEmpty, { statusCode: 204 }) - // @OnUndefined(204) - // @OpenAPI({ description: 'Delete the contact whose id you provided.
If no contact with this id exists it will just return 204(no content).
If the contact still has donations associated this will fail, please provide the query param ?force=true to delete the contact with all associated donations.' }) - // async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { - // let contact = await this.contactRepository.findOne({ id: id }); - // if (!contact) { return null; } - // const responseDonor = await this.contactRepository.findOne(contact, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); + @Delete('/:id') + @Authorized("CONTACT:DELETE") + @ResponseSchema(ResponseGroupContact) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete the contact whose id you provided.
If no contact with this id exists it will just return 204(no content).
This won\'t delete any groups associated with the contact.' }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let contact = await this.contactRepository.findOne({ id: id }); + if (!contact) { return null; } + const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups'] }); - // if (!contact) { - // throw new DonorNotFoundError(); - // } - - // const contactDonations = (await this.contactRepository.findOne({ id: contact.id }, { relations: ["donations"] })).donations; - // if (contactDonations.length > 0 && !force) { - // throw new DonorHasDonationsError(); - // } - // const donationController = new DonationController(); - // for (let donation of contactDonations) { - // await donationController.remove(donation.id, force); - // } - - // await this.contactRepository.delete(contact); - // return new ResponseDonor(responseDonor); - // } + await this.contactRepository.delete(contact); + return responseContact.toResponse(); + } } From 09e429fc676c7dd370bba0495b072f81867bd250 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:13:46 +0100 Subject: [PATCH 07/27] Added address to contact response ref #104 --- src/models/responses/ResponseGroupContact.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/models/responses/ResponseGroupContact.ts b/src/models/responses/ResponseGroupContact.ts index f7e63b3..d7ac6f2 100644 --- a/src/models/responses/ResponseGroupContact.ts +++ b/src/models/responses/ResponseGroupContact.ts @@ -1,4 +1,5 @@ -import { IsInt, IsString } from "class-validator"; +import { IsInt, IsObject, IsString } from "class-validator"; +import { Address } from '../entities/Address'; import { GroupContact } from '../entities/GroupContact'; import { ResponseRunnerGroup } from './ResponseRunnerGroup'; @@ -45,9 +46,15 @@ export class ResponseGroupContact { /** * The contact's associated runner groups. */ + @IsObject() groups: ResponseRunnerGroup[]; - //TODO: Address + /** + * The contact's address. + * This is a address object to prevent any formatting differences. + */ + @IsObject() + address?: Address; /** * Creates a ResponseGroupContact object from a contact. @@ -60,6 +67,7 @@ export class ResponseGroupContact { this.lastname = contact.lastname; this.phone = contact.phone; this.email = contact.email; + this.address = contact.address; for (let group of contact.groups) { this.groups.push(group.toResponse()); } From 11af9c02d977dcd6919652256dbdb9fd5438cabd Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:14:05 +0100 Subject: [PATCH 08/27] Implemented contact posting ref #104 --- src/controllers/ContactController.ts | 33 +++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/controllers/ContactController.ts b/src/controllers/ContactController.ts index 1a20d82..0ed2eca 100644 --- a/src/controllers/ContactController.ts +++ b/src/controllers/ContactController.ts @@ -1,7 +1,9 @@ -import { Authorized, Delete, Get, JsonController, OnUndefined, Param, QueryParam } from 'routing-controllers'; +import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { GroupContactNotFoundError } from '../errors/GroupContactErrors'; +import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; +import { CreateGroupContact } from '../models/actions/create/CreateGroupContact'; import { GroupContact } from '../models/entities/GroupContact'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseGroupContact } from '../models/responses/ResponseGroupContact'; @@ -43,21 +45,22 @@ export class ContactController { return contact.toResponse(); } - // @Post() - // @Authorized("DONOR:CREATE") - // @ResponseSchema(ResponseDonor) - // @OpenAPI({ description: 'Create a new contact.' }) - // async post(@Body({ validate: true }) createRunner: CreateDonor) { - // let contact; - // try { - // contact = await createRunner.toEntity(); - // } catch (error) { - // throw error; - // } + @Post() + @Authorized("CONTACT:CREATE") + @ResponseSchema(ResponseGroupContact) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @OpenAPI({ description: 'Create a new contact.' }) + async post(@Body({ validate: true }) createContact: CreateGroupContact) { + let contact; + try { + contact = await createContact.toEntity(); + } catch (error) { + throw error; + } - // contact = await this.contactRepository.save(contact) - // return new ResponseDonor(await this.contactRepository.findOne(contact, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); - // } + contact = await this.contactRepository.save(contact) + return (await this.contactRepository.findOne(contact, { relations: ['groups'] })).toResponse(); + } // @Put('/:id') // @Authorized("DONOR:UPDATE") From de824375d3a1da6ee4d78ea39b7da66fc05f2a02 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:27:43 +0100 Subject: [PATCH 09/27] Fixed key null constraint ref #104 --- .../actions/create/CreateGroupContact.ts | 20 +++++++++---------- src/models/entities/Address.ts | 17 +++++----------- src/models/entities/GroupContact.ts | 1 - 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/models/actions/create/CreateGroupContact.ts b/src/models/actions/create/CreateGroupContact.ts index c6538e8..e261f51 100644 --- a/src/models/actions/create/CreateGroupContact.ts +++ b/src/models/actions/create/CreateGroupContact.ts @@ -29,7 +29,7 @@ export class CreateGroupContact { lastname: string; /** - * The new contact's address. + * The new participant's address. */ @IsOptional() @IsObject() @@ -55,14 +55,14 @@ export class CreateGroupContact { * Creates a new Address entity from this. */ public async toEntity(): 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 = this.address; - Address.validate(contact.address); - return contact; + let newContact: GroupContact = new GroupContact(); + newContact.firstname = this.firstname; + newContact.middlename = this.middlename; + newContact.lastname = this.lastname; + newContact.email = this.email; + newContact.phone = this.phone; + newContact.address = this.address; + Address.validate(newContact.address); + return newContact; } } \ No newline at end of file diff --git a/src/models/entities/Address.ts b/src/models/entities/Address.ts index 21ec92a..6526c08 100644 --- a/src/models/entities/Address.ts +++ b/src/models/entities/Address.ts @@ -1,6 +1,4 @@ import { - IsNotEmpty, - IsOptional, IsPostalCode, IsString } from "class-validator"; @@ -18,10 +16,9 @@ export class Address { * The address's first line. * Containing the street and house number. */ - @Column() + @Column({ nullable: true }) @IsString() - @IsNotEmpty() - address1: string; + address1?: string; /** * The address's second line. @@ -29,33 +26,29 @@ export class Address { */ @Column({ nullable: true }) @IsString() - @IsOptional() address2?: string; /** * The address's postal code. * This will get checked against the postal code syntax for the configured country. */ - @Column() + @Column({ nullable: true }) @IsString() - @IsNotEmpty() @IsPostalCode(config.postalcode_validation_countrycode) postalcode: string; /** * The address's city. */ - @Column() + @Column({ nullable: true }) @IsString() - @IsNotEmpty() city: string; /** * The address's country. */ - @Column() + @Column({ nullable: true }) @IsString() - @IsNotEmpty() country: string; public reset() { diff --git a/src/models/entities/GroupContact.ts b/src/models/entities/GroupContact.ts index 17b0b9b..dd6eed1 100644 --- a/src/models/entities/GroupContact.ts +++ b/src/models/entities/GroupContact.ts @@ -54,7 +54,6 @@ export class GroupContact { * The contact's address. * This is a address object to prevent any formatting differences. */ - @IsOptional() @Column(type => Address) address?: Address; From 3b06d1a6ef3c95eb5bb7d485accddabba0a8e4f7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:29:52 +0100 Subject: [PATCH 10/27] Implemented contact group setting on creation ref #104 --- .../actions/create/CreateGroupContact.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/models/actions/create/CreateGroupContact.ts b/src/models/actions/create/CreateGroupContact.ts index e261f51..8d7e0bd 100644 --- a/src/models/actions/create/CreateGroupContact.ts +++ b/src/models/actions/create/CreateGroupContact.ts @@ -1,7 +1,10 @@ import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; import { config } from '../../../config'; +import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; import { Address } from '../../entities/Address'; import { GroupContact } from '../../entities/GroupContact'; +import { RunnerGroup } from '../../entities/RunnerGroup'; /** * This classed is used to create a new Group entity from a json body (post request). @@ -50,6 +53,30 @@ export class CreateGroupContact { @IsEmail() email?: string; + /** + * The new contacts's groups' ids. + * You can provide either one groupId or an array of groupIDs. + */ + @IsOptional() + groups?: number[] | number + + + /** + * Get's all groups for this contact by their id's; + */ + public async getGroups(): Promise { + if (!this.groups) { return null; } + let groups = new Array(); + if (!Array.isArray(this.groups)) { + this.groups = [this.groups] + } + for (let group of this.groups) { + let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group }); + if (!found) { throw new RunnerGroupNotFoundError(); } + groups.push(found); + } + return groups; + } /** * Creates a new Address entity from this. @@ -63,6 +90,8 @@ export class CreateGroupContact { newContact.phone = this.phone; newContact.address = this.address; Address.validate(newContact.address); + newContact.groups = await this.getGroups(); + return newContact; } } \ No newline at end of file From 2eb26e4e381a97fd829a294501fa42ac7b712b56 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:41:00 +0100 Subject: [PATCH 11/27] Fixed push undefined eror ref #104 --- src/models/responses/ResponseGroupContact.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/responses/ResponseGroupContact.ts b/src/models/responses/ResponseGroupContact.ts index d7ac6f2..291dde5 100644 --- a/src/models/responses/ResponseGroupContact.ts +++ b/src/models/responses/ResponseGroupContact.ts @@ -68,6 +68,7 @@ export class ResponseGroupContact { this.phone = contact.phone; this.email = contact.email; this.address = contact.address; + this.groups = new Array(); for (let group of contact.groups) { this.groups.push(group.toResponse()); } From 321d291b4bf983ff4930cadecf3a013430a97649 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:52:51 +0100 Subject: [PATCH 12/27] Fixed column not getting resolved --- src/controllers/ContactController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/ContactController.ts b/src/controllers/ContactController.ts index 0ed2eca..4e469c7 100644 --- a/src/controllers/ContactController.ts +++ b/src/controllers/ContactController.ts @@ -59,7 +59,7 @@ export class ContactController { } contact = await this.contactRepository.save(contact) - return (await this.contactRepository.findOne(contact, { relations: ['groups'] })).toResponse(); + return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse(); } // @Put('/:id') From 2b658ac381f318cf37fc2051ea3e83976e3c5773 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:52:51 +0100 Subject: [PATCH 13/27] Fixed column not getting resolved ref #104 --- src/controllers/ContactController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/ContactController.ts b/src/controllers/ContactController.ts index 0ed2eca..4e469c7 100644 --- a/src/controllers/ContactController.ts +++ b/src/controllers/ContactController.ts @@ -59,7 +59,7 @@ export class ContactController { } contact = await this.contactRepository.save(contact) - return (await this.contactRepository.findOne(contact, { relations: ['groups'] })).toResponse(); + return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse(); } // @Put('/:id') From c172aa8bf8083500828743ed696955a1fe3caef2 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:55:56 +0100 Subject: [PATCH 14/27] Added a contact update class ref #104 --- .../actions/update/UpdateGroupContact.ts | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/models/actions/update/UpdateGroupContact.ts diff --git a/src/models/actions/update/UpdateGroupContact.ts b/src/models/actions/update/UpdateGroupContact.ts new file mode 100644 index 0000000..62d4149 --- /dev/null +++ b/src/models/actions/update/UpdateGroupContact.ts @@ -0,0 +1,98 @@ +import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { getConnectionManager } from 'typeorm'; +import { config } from '../../../config'; +import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; +import { Address } from '../../entities/Address'; +import { GroupContact } from '../../entities/GroupContact'; +import { RunnerGroup } from '../../entities/RunnerGroup'; + +/** + * This classed is used to create a new Group entity from a json body (post request). + */ +export class UpdateGroupContact { + /** + * The updated contact's first name. + */ + @IsNotEmpty() + @IsString() + firstname: string; + + /** + * The updated contact's middle name. + */ + @IsOptional() + @IsString() + middlename?: string; + + /** + * The updated contact's last name. + */ + @IsNotEmpty() + @IsString() + lastname: string; + + /** + * The updated contact's address. + */ + @IsOptional() + @IsObject() + address?: Address; + + /** + * The updated contact's phone number. + * This will be validated against the configured country phone numer syntax (default: international). + */ + @IsOptional() + @IsPhoneNumber(config.phone_validation_countrycode) + phone?: string; + + /** + * The updated contact's email address. + */ + @IsOptional() + @IsEmail() + email?: string; + + /** + * The updated contacts's groups' ids. + * You can provide either one groupId or an array of groupIDs. + */ + @IsOptional() + groups?: number[] | number + + + /** + * Get's all groups for this contact by their id's; + */ + public async getGroups(): Promise { + if (!this.groups) { return null; } + let groups = new Array(); + if (!Array.isArray(this.groups)) { + this.groups = [this.groups] + } + for (let group of this.groups) { + let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group }); + if (!found) { throw new RunnerGroupNotFoundError(); } + groups.push(found); + } + return groups; + } + + /** + * Updates a provided Donor entity based on this. + * @param contact the contact you want to update. + */ + public async update(contact: GroupContact): Promise { + contact.firstname = this.firstname; GroupContact + contact.middlename = this.middlename; + contact.lastname = this.lastname; + contact.phone = this.phone; + contact.email = this.email; + if (!this.address) { contact.address.reset(); } + else { contact.address = this.address; } + Address.validate(contact.address); + contact.groups = await this.getGroups(); + + return contact; + } +} \ No newline at end of file From a4e8311cbd22588ecb4dc2fdbe05397b07d336f8 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:57:15 +0100 Subject: [PATCH 15/27] Updated comments ref #104 --- src/models/actions/create/CreateGroupContact.ts | 8 ++++---- src/models/actions/update/UpdateGroupContact.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/models/actions/create/CreateGroupContact.ts b/src/models/actions/create/CreateGroupContact.ts index 8d7e0bd..0775b2d 100644 --- a/src/models/actions/create/CreateGroupContact.ts +++ b/src/models/actions/create/CreateGroupContact.ts @@ -7,7 +7,7 @@ import { GroupContact } from '../../entities/GroupContact'; import { RunnerGroup } from '../../entities/RunnerGroup'; /** - * This classed is used to create a new Group entity from a json body (post request). + * This classed is used to create a new GroupContact entity from a json body (post request). */ export class CreateGroupContact { /** @@ -32,7 +32,7 @@ export class CreateGroupContact { lastname: string; /** - * The new participant's address. + * The new contact's address. */ @IsOptional() @IsObject() @@ -47,7 +47,7 @@ export class CreateGroupContact { phone?: string; /** - * The contact's email address. + * The new contact's email address. */ @IsOptional() @IsEmail() @@ -79,7 +79,7 @@ export class CreateGroupContact { } /** - * Creates a new Address entity from this. + * Creates a new GroupContact entity from this. */ public async toEntity(): Promise { let newContact: GroupContact = new GroupContact(); diff --git a/src/models/actions/update/UpdateGroupContact.ts b/src/models/actions/update/UpdateGroupContact.ts index 62d4149..9ec4418 100644 --- a/src/models/actions/update/UpdateGroupContact.ts +++ b/src/models/actions/update/UpdateGroupContact.ts @@ -6,8 +6,9 @@ import { Address } from '../../entities/Address'; import { GroupContact } from '../../entities/GroupContact'; import { RunnerGroup } from '../../entities/RunnerGroup'; + /** - * This classed is used to create a new Group entity from a json body (post request). + * This class is used to update a GroupContact entity (via put request). */ export class UpdateGroupContact { /** From d743f7ee1277256ada8fe39f900349ff2643118a Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 17:58:03 +0100 Subject: [PATCH 16/27] Renamed controller to better fit the overall nameing scheme ref #104 --- .../{ContactController.ts => GroupContactController.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/controllers/{ContactController.ts => GroupContactController.ts} (99%) diff --git a/src/controllers/ContactController.ts b/src/controllers/GroupContactController.ts similarity index 99% rename from src/controllers/ContactController.ts rename to src/controllers/GroupContactController.ts index 4e469c7..3030de8 100644 --- a/src/controllers/ContactController.ts +++ b/src/controllers/GroupContactController.ts @@ -10,7 +10,7 @@ import { ResponseGroupContact } from '../models/responses/ResponseGroupContact'; @JsonController('/contacts') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) -export class ContactController { +export class GroupContactController { private contactRepository: Repository; /** From 6b4b16c13b0c2f55745ded3431cad2f4986be296 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 18:00:45 +0100 Subject: [PATCH 17/27] Added missing id property --- src/models/actions/update/UpdateGroupContact.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/models/actions/update/UpdateGroupContact.ts b/src/models/actions/update/UpdateGroupContact.ts index 9ec4418..72f919a 100644 --- a/src/models/actions/update/UpdateGroupContact.ts +++ b/src/models/actions/update/UpdateGroupContact.ts @@ -1,4 +1,4 @@ -import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; +import { IsEmail, IsInt, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; import { getConnectionManager } from 'typeorm'; import { config } from '../../../config'; import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; @@ -11,6 +11,13 @@ import { RunnerGroup } from '../../entities/RunnerGroup'; * This class is used to update a GroupContact entity (via put request). */ export class UpdateGroupContact { + /** + * The updated contact's id. + * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to). + */ + @IsInt() + id: number; + /** * The updated contact's first name. */ From 28fb9834e18bde012c5b51cc49a39585d20f7cc1 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 18:01:37 +0100 Subject: [PATCH 18/27] Implemented contact updateing ref #104 --- src/controllers/GroupContactController.ts | 40 ++++++++++++----------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/controllers/GroupContactController.ts b/src/controllers/GroupContactController.ts index 3030de8..12518c2 100644 --- a/src/controllers/GroupContactController.ts +++ b/src/controllers/GroupContactController.ts @@ -1,9 +1,10 @@ -import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; +import { Authorized, 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 { GroupContactNotFoundError } from '../errors/GroupContactErrors'; +import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors'; import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; import { CreateGroupContact } from '../models/actions/create/CreateGroupContact'; +import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact'; import { GroupContact } from '../models/entities/GroupContact'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseGroupContact } from '../models/responses/ResponseGroupContact'; @@ -62,26 +63,27 @@ export class GroupContactController { return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse(); } - // @Put('/:id') - // @Authorized("DONOR:UPDATE") - // @ResponseSchema(ResponseDonor) - // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) - // @ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 }) - // @OpenAPI({ description: "Update the contact whose id you provided.
Please remember that ids can't be changed." }) - // async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateDonor) { - // let oldDonor = await this.contactRepository.findOne({ id: id }); + @Put('/:id') + @Authorized("CONTACT:UPDATE") + @ResponseSchema(ResponseGroupContact) + @ResponseSchema(GroupContactNotFoundError, { statusCode: 404 }) + @ResponseSchema(GroupContactIdsNotMatchingError, { statusCode: 406 }) + @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 }) + @OpenAPI({ description: "Update the contact whose id you provided.
Please remember that ids can't be changed." }) + async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateGroupContact) { + let oldContact = await this.contactRepository.findOne({ id: id }); - // if (!oldDonor) { - // throw new DonorNotFoundError(); - // } + if (!oldContact) { + throw new GroupContactNotFoundError(); + } - // if (oldDonor.id != contact.id) { - // throw new DonorIdsNotMatchingError(); - // } + if (oldContact.id != contact.id) { + throw new GroupContactIdsNotMatchingError(); + } - // await this.contactRepository.save(await contact.update(oldDonor)); - // return new ResponseDonor(await this.contactRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); - // } + await this.contactRepository.save(await contact.update(oldContact)); + return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse(); + } @Delete('/:id') @Authorized("CONTACT:DELETE") From 56c73c2555d4d12ffb088ec5550667022d3a8694 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 18:03:29 +0100 Subject: [PATCH 19/27] Added openapi description about non-deletion ref #104 --- src/controllers/RunnerOrganisationController.ts | 2 +- src/controllers/RunnerTeamController.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/RunnerOrganisationController.ts b/src/controllers/RunnerOrganisationController.ts index fbbd03a..4d985e4 100644 --- a/src/controllers/RunnerOrganisationController.ts +++ b/src/controllers/RunnerOrganisationController.ts @@ -94,7 +94,7 @@ export class RunnerOrganisationController { @ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 }) @ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 }) @OnUndefined(204) - @OpenAPI({ description: 'Delete the organsisation whose id you provided.
If the organisation still has runners and/or teams associated this will fail.
To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while).
If no organisation with this id exists it will just return 204(no content).' }) + @OpenAPI({ description: 'Delete the organsisation whose id you provided.
If the organisation still has runners and/or teams associated this will fail.
To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while).
This won\'t delete the associated contact.
If no organisation with this id exists it will just return 204(no content).' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let organisation = await this.runnerOrganisationRepository.findOne({ id: id }); if (!organisation) { return null; } diff --git a/src/controllers/RunnerTeamController.ts b/src/controllers/RunnerTeamController.ts index 5b97f2b..ce706ec 100644 --- a/src/controllers/RunnerTeamController.ts +++ b/src/controllers/RunnerTeamController.ts @@ -93,7 +93,7 @@ export class RunnerTeamController { @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 }) @OnUndefined(204) - @OpenAPI({ description: 'Delete the team whose id you provided.
If the team still has runners associated this will fail.
To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while).
If no team with this id exists it will just return 204(no content).' }) + @OpenAPI({ description: 'Delete the team whose id you provided.
If the team still has runners associated this will fail.
To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while).
This won\'t delete the associated contact.
If no team with this id exists it will just return 204(no content).' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let team = await this.runnerTeamRepository.findOne({ id: id }); if (!team) { return null; } From b002cf2df1eafc722fbfb51b3bffb02bee002305 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 18:13:39 +0100 Subject: [PATCH 20/27] Added contact get tests ref #104 --- src/tests/contacts/contact_get.spec.ts | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/tests/contacts/contact_get.spec.ts diff --git a/src/tests/contacts/contact_get.spec.ts b/src/tests/contacts/contact_get.spec.ts new file mode 100644 index 0000000..31aa895 --- /dev/null +++ b/src/tests/contacts/contact_get.spec.ts @@ -0,0 +1,57 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('GET /api/contacts', () => { + it('basic get should return 200', async () => { + const res = await axios.get(base + '/api/contacts', axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('GET /api/contacts/0', () => { + it('basic get should return 404', async () => { + const res = await axios.get(base + '/api/contacts/0', axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('GET /api/contacts after adding', () => { + let added_contact; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('explicit get should return 200', async () => { + const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + let gotten_donor = res.data + expect(gotten_donor).toEqual(added_contact); + }); + it('get from all runners should return 200', async () => { + const res = await axios.get(base + '/api/contacts/', axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + let gotten_donors = res.data + expect(gotten_donors).toContainEqual(added_contact); + }); +}); \ No newline at end of file From 940d62cde4cf7be7780904d681a5e4c9efaa2ba5 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 18:14:09 +0100 Subject: [PATCH 21/27] Added contact add invalid tests ref #104 --- src/tests/contacts/contact_add.spec.ts | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/tests/contacts/contact_add.spec.ts diff --git a/src/tests/contacts/contact_add.spec.ts b/src/tests/contacts/contact_add.spec.ts new file mode 100644 index 0000000..2136aa3 --- /dev/null +++ b/src/tests/contacts/contact_add.spec.ts @@ -0,0 +1,75 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +// --------------- +describe('POST /api/contacts with errors', () => { + it('creating a new contact without any parameters should return 400', async () => { + const res = await axios.post(base + '/api/contacts', null, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact without a last name should return 400', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "middlename": "middle" + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a invalid phone number should return 400', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "middlename": "middle", + "lastname": "last", + "phone": "123" + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a invalid mail address should return 400', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "phone": null, + "email": "123", + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with an invalid address 400', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "address": { + "city": "Testcity" + } + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a invalid group should return 404', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "string", + "middlename": "string", + "lastname": "string", + "groups": 9999999999999 + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); From e165f019307e7745357493eacf3e2fa31538122b Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 18:48:37 +0100 Subject: [PATCH 22/27] Added contact add valid tests ref #104 --- src/tests/contacts/contact_add.spec.ts | 141 +++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/src/tests/contacts/contact_add.spec.ts b/src/tests/contacts/contact_add.spec.ts index 2136aa3..0cb5160 100644 --- a/src/tests/contacts/contact_add.spec.ts +++ b/src/tests/contacts/contact_add.spec.ts @@ -73,3 +73,144 @@ describe('POST /api/contacts with errors', () => { expect(res.headers['content-type']).toContain("application/json") }); }); +// --------------- +describe('POST /api/contacts working (simple)', () => { + it('creating a new contact with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last" + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with all non-relationship optional params should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "middlename": "middle", + "lastname": "last", + "email": "testContact@lauf-fuer-kaya.de", + "phone": "+49017612345678", + "address": { + "address1": "test", + "address2": null, + "city": "herzogenaurach", + "country": "germany", + "postalcode": "91074", + } + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('POST /api/contacts working (with group)', () => { + let added_org; + let added_team; + let added_contact; + 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); + delete res.data.contact; + delete res.data.teams; + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new team with a parent org should return 200', async () => { + const res = await axios.post(base + '/api/teams', { + "name": "test_team", + "parentGroup": added_org.id + }, axios_config); + delete res.data.contact; + delete res.data.parentGroup; + added_team = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a valid org should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": added_org.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + expect(res.data).toEqual({ + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_org] + }); + }); + it('creating a new contact with a valid team should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": added_team.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + expect(res.data).toEqual({ + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_team] + }); + }); + it('creating a new contact with a valid org and team should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": [added_org.id, added_team.id] + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_contact = res.data + delete res.data.id; + expect(res.data).toEqual({ + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_org, added_team] + }); + }); + it('checking if the added team\'s contact is the new contact should return 200', async () => { + const res = await axios.get(base + '/api/teams/' + added_team.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.contact.groups; + delete res.data.contact.id; + delete added_contact.groups; + expect(res.data.contact).toEqual(added_contact); + }); +}); \ No newline at end of file From dd7e5dae368a8decd79357f658dda2164fa6f1e7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 19:04:09 +0100 Subject: [PATCH 23/27] Added contact delete tests ref #104 --- src/tests/contacts/contact_delete.spec.ts | 182 ++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 src/tests/contacts/contact_delete.spec.ts diff --git a/src/tests/contacts/contact_delete.spec.ts b/src/tests/contacts/contact_delete.spec.ts new file mode 100644 index 0000000..e8f22fc --- /dev/null +++ b/src/tests/contacts/contact_delete.spec.ts @@ -0,0 +1,182 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('adding + deletion (non-existant)', () => { + it('delete', async () => { + const res = await axios.delete(base + '/api/contacts/0', axios_config); + expect(res.status).toEqual(204); + }); +}); +// --------------- +describe('add+delete (simple)', () => { + let added_contact; + it('creating a new contact with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete contact', async () => { + const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + let deleted_contact = res.data + expect(deleted_contact).toEqual(added_contact); + }); + it('check if contact really was deleted', async () => { + const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('add+delete (with org)', () => { + let added_org; + let added_contact; + 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); + delete res.data.contact; + delete res.data.teams; + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a valid org should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": added_org.id + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('delete contact', async () => { + const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + let deleted_contact = res.data + expect(deleted_contact).toEqual(added_contact); + }); + it('check if contact really was deleted', async () => { + const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('add+delete (with team)', () => { + let added_org; + let added_team; + let added_contact; + 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); + delete res.data.contact; + delete res.data.teams; + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new team with a parent org should return 200', async () => { + const res = await axios.post(base + '/api/teams', { + "name": "test_team", + "parentGroup": added_org.id + }, axios_config); + delete res.data.contact; + delete res.data.parentGroup; + added_team = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a valid team should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": added_team.id + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('delete contact', async () => { + const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + let deleted_contact = res.data + expect(deleted_contact).toEqual(added_contact); + }); + it('check if contact really was deleted', async () => { + const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('add+delete (with org&team)', () => { + let added_org; + let added_team; + let added_contact; + 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); + delete res.data.contact; + delete res.data.teams; + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new team with a parent org should return 200', async () => { + const res = await axios.post(base + '/api/teams', { + "name": "test_team", + "parentGroup": added_org.id + }, axios_config); + delete res.data.contact; + delete res.data.parentGroup; + added_team = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a valid org and team should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": [added_org.id, added_team.id] + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('delete contact', async () => { + const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + let deleted_contact = res.data + expect(deleted_contact).toEqual(added_contact); + }); + it('check if contact really was deleted', async () => { + const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); \ No newline at end of file From 179c2a5157fca036acf8d0e6a51821d377860bc1 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 19:04:46 +0100 Subject: [PATCH 24/27] Fixed contact cascading ref #104 --- src/controllers/GroupContactController.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controllers/GroupContactController.ts b/src/controllers/GroupContactController.ts index 12518c2..3d6f83e 100644 --- a/src/controllers/GroupContactController.ts +++ b/src/controllers/GroupContactController.ts @@ -1,11 +1,12 @@ import { Authorized, 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 { getConnection, getConnectionManager, Repository } from 'typeorm'; import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors'; import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; import { CreateGroupContact } from '../models/actions/create/CreateGroupContact'; import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact'; import { GroupContact } from '../models/entities/GroupContact'; +import { RunnerGroup } from '../models/entities/RunnerGroup'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseGroupContact } from '../models/responses/ResponseGroupContact'; @@ -95,6 +96,10 @@ export class GroupContactController { let contact = await this.contactRepository.findOne({ id: id }); if (!contact) { return null; } const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups'] }); + for (let group of responseContact.groups) { + group.contact = null; + await getConnection().getRepository(RunnerGroup).save(group); + } await this.contactRepository.delete(contact); return responseContact.toResponse(); From 8ae53f1c4930e2fd72eb230a5314336f3a45a611 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 19:12:53 +0100 Subject: [PATCH 25/27] Updated contact delete tests ref #104 --- src/tests/contacts/contact_delete.spec.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tests/contacts/contact_delete.spec.ts b/src/tests/contacts/contact_delete.spec.ts index e8f22fc..f8d8c64 100644 --- a/src/tests/contacts/contact_delete.spec.ts +++ b/src/tests/contacts/contact_delete.spec.ts @@ -73,8 +73,8 @@ describe('add+delete (with org)', () => { const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") - let deleted_contact = res.data - expect(deleted_contact).toEqual(added_contact); + delete res.data.groups[0].contact; + expect(res.data).toEqual(added_contact); }); it('check if contact really was deleted', async () => { const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); @@ -122,8 +122,8 @@ describe('add+delete (with team)', () => { const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") - let deleted_contact = res.data - expect(deleted_contact).toEqual(added_contact); + delete res.data.groups[0].contact; + expect(res.data).toEqual(added_contact); }); it('check if contact really was deleted', async () => { const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); @@ -171,8 +171,9 @@ describe('add+delete (with org&team)', () => { const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") - let deleted_contact = res.data - expect(deleted_contact).toEqual(added_contact); + delete res.data.groups[0].contact; + delete res.data.groups[1].contact; + expect(res.data).toEqual(added_contact); }); it('check if contact really was deleted', async () => { const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config); From c3d008ec0ff92f80addbdb93ffc1fa2b3278a8a6 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 19:32:39 +0100 Subject: [PATCH 26/27] Updated contact update tests ref #104 --- src/tests/contacts/contact_update.spec.ts | 237 ++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 src/tests/contacts/contact_update.spec.ts diff --git a/src/tests/contacts/contact_update.spec.ts b/src/tests/contacts/contact_update.spec.ts new file mode 100644 index 0000000..e6d733d --- /dev/null +++ b/src/tests/contacts/contact_update.spec.ts @@ -0,0 +1,237 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('Update contact name after adding', () => { + let added_contact; + it('creating a new contact with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('valid update should return 200', async () => { + let contact_copy = added_contact + contact_copy.firstname = "second" + const res = await axios.put(base + '/api/contacts/' + added_contact.id, contact_copy, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + expect(res.data).toEqual(contact_copy); + }); +}); +// --------------- +describe('Update contact id after adding(should fail)', () => { + let added_contact; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_contact = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('invalid update should return 406', async () => { + added_contact.id++; + const res = await axios.put(base + '/api/contacts/' + (added_contact.id - 1), added_contact, axios_config); + expect(res.status).toEqual(406); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('Update contact group after adding (should work)', () => { + let added_org; + let added_team; + let added_contact; + 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); + delete res.data.contact; + delete res.data.teams; + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new team with a parent org should return 200', async () => { + const res = await axios.post(base + '/api/teams', { + "name": "test_team", + "parentGroup": added_org.id + }, axios_config); + delete res.data.contact; + delete res.data.parentGroup; + added_team = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a valid org should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": added_org.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_contact = res.data + expect(res.data).toEqual({ + "id": res.data.id, + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_org] + }); + }); + it('valid group update to single team should return 200', async () => { + const res = await axios.put(base + '/api/contacts/' + added_contact.id, { + "id": added_contact.id, + "firstname": "first", + "lastname": "last", + "groups": added_team.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual({ + "id": res.data.id, + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_team] + }); + }); + it('valid group update to org and team should return 200', async () => { + const res = await axios.put(base + '/api/contacts/' + added_contact.id, { + "id": added_contact.id, + "firstname": "first", + "lastname": "last", + "groups": [added_org.id, added_team.id] + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual({ + "id": res.data.id, + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_org, added_team] + }); + }); + it('valid group update to none should return 200', async () => { + const res = await axios.put(base + '/api/contacts/' + added_contact.id, { + "id": added_contact.id, + "firstname": "first", + "lastname": "last", + "groups": null + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual({ + "id": res.data.id, + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [] + }); + }); +}); +// --------------- +describe('Update contact group invalid after adding (should fail)', () => { + let added_org; + let added_contact; + 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); + delete res.data.contact; + delete res.data.teams; + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new contact with a valid org should return 200', async () => { + const res = await axios.post(base + '/api/contacts', { + "firstname": "first", + "lastname": "last", + "groups": added_org.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_contact = res.data + expect(res.data).toEqual({ + "id": res.data.id, + "firstname": "first", + "middlename": null, + "lastname": "last", + "phone": null, + "email": null, + "address": { + "address1": null, + "address2": null, + "postalcode": null, + "city": null, + "country": null + }, + "groups": [added_org] + }); + }); + it('invalid group update to single team should return 404', async () => { + const res = await axios.put(base + '/api/contacts/' + added_contact.id, { + "id": added_contact.id, + "firstname": "first", + "lastname": "last", + "groups": 999999999999999 + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); \ No newline at end of file From a1acd3519f66965202da0cd15df61a807230619c Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 19 Jan 2021 19:33:11 +0100 Subject: [PATCH 27/27] Adjusted env sample ref #104 ref #105 --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 5595533..076d8cd 100644 --- a/.env.example +++ b/.env.example @@ -6,4 +6,4 @@ DB_USER=bla DB_PASSWORD=bla DB_NAME=bla NODE_ENV=production -POSTALCODE_COUNTRYCODE=null \ No newline at end of file +POSTALCODE_COUNTRYCODE=DE \ No newline at end of file