From 7b48573eb34ae3017eecb927ae05164ff798d6db Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 27 Sep 2020 15:35:16 +0200 Subject: [PATCH] Person, ContactInformation, participant: read and create --- src/datasources/db/contactinformationAPI.ts | 62 ++++++++++++ src/datasources/db/participantAPI.ts | 71 ++++++++----- src/datasources/userserver/permission.ts | 10 ++ src/model/ContactInformation.ts | 16 ++- src/model/Engagement.ts | 11 +- src/model/EngagementType.ts | 3 +- src/model/Participant.ts | 18 ++-- src/model/Person.ts | 4 +- src/resolvers/contactinformationResolvers.ts | 43 +++++++- src/resolvers/participantResolvers.ts | 22 +++- src/schema/type-defs.ts | 101 ++++++++++--------- 11 files changed, 263 insertions(+), 98 deletions(-) diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index 0541e4b..6713209 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -2,6 +2,7 @@ import { DataSource } from 'apollo-datasource'; import { Connection, getConnection } from 'typeorm'; import { ContactInformation } from '../../model/ContactInformation'; import { LendingStation } from '../../model/LendingStation'; +import { Person } from '../../model/Person'; export class ContactInformationAPI extends DataSource { connection : Connection @@ -47,6 +48,67 @@ export class ContactInformationAPI extends DataSource { .loadMany(); } + async createPerson (person: any) { + const inserts = await this.connection.getRepository(Person) + .createQueryBuilder('person') + .insert() + .values([person]) + .returning('*') + .execute(); + inserts.generatedMaps[0].id = inserts.identifiers[0].id; + return inserts.generatedMaps[0]; + } + + /** + * Return person by ID + * @param id + */ + async personById (id: number) { + return await this.connection.getRepository(Person) + .createQueryBuilder('person') + .select() + .where('person.id = :id', { id: id }) + .getOne(); + } + + async persons (offset: number, limit: number) { + return await this.connection.getRepository(Person) + .createQueryBuilder('person') + .select() + .skip(offset) + .take(limit) + .execute(); + } + + async personByContactInformationId (id: number) { + return await this.connection.getRepository(ContactInformation) + .createQueryBuilder('ci') + .relation(ContactInformation, 'personId') + .of(id) + .loadOne(); + } + + async createContactInformation (contactInformation: any) { + const inserts = await this.connection.getRepository(ContactInformation) + .createQueryBuilder('contactInformation') + .insert() + .into(ContactInformation) + .values([contactInformation]) + .returning('*') + .execute(); + return inserts.generatedMaps[0]; + } + + async contactInformationByPersonId (id: number) { + const res = await this.connection.getRepository(ContactInformation) + .createQueryBuilder('ci') + .select() + .where('ci."personId" = :id', { id: id }) + .getMany(); + console.log(res); + return res; + } + async contactInformationByContactPersonId (id: number) { /* return (await this.connection.getRepository(ContactPerson) .createQueryBuilder('contactPerson') diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index 5c4a320..9afce47 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -1,10 +1,11 @@ import { DataSource } from 'apollo-datasource'; import { GraphQLError } from 'graphql'; -import { Connection, getConnection } from 'typeorm'; +import { Connection, EntityManager, getConnection } from 'typeorm'; import { CargoBike } from '../../model/CargoBike'; import { ContactInformation } from '../../model/ContactInformation'; import { Engagement } from '../../model/Engagement'; import { Participant } from '../../model/Participant'; +import { EngagementType } from '../../model/EngagementType'; export class ParticipantAPI extends DataSource { connection : Connection @@ -78,6 +79,12 @@ export class ParticipantAPI extends DataSource { .getOne(); } + async engagementTypeByEngagementId (id: number) { + return await this.connection.getRepository(Engagement) + .createQueryBuilder('engagement') + .relation(Engagement, 'engageMent'); + } + async contactInformationById (id: number) { return await this.connection.getRepository(ContactInformation) .createQueryBuilder('contactInformation') @@ -87,14 +94,11 @@ export class ParticipantAPI extends DataSource { } async contactInformationByParticipantId (id: number) { - const ret = (await this.connection.getRepository(Participant) - .createQueryBuilder('participant') - .leftJoinAndSelect('participant.contactInformation', 'contactInformation') - .where('participant."contactInformationId" = "contactInformation".id') - .andWhere('participant.id = :id', { id: id }) - .printSql() - .getOne()); - return (ret) ? ret.contactInformation : null; + return await this.connection.manager + .createQueryBuilder() + .relation(Participant, 'contactInformationId') + .of(id) + .loadOne(); } /** @@ -102,7 +106,7 @@ export class ParticipantAPI extends DataSource { * @param participant to be created */ async createParticipant (participant: any) { - let count = this.connection.getRepository(ContactInformation) + /* let count = this.connection.getRepository(ContactInformation) .createQueryBuilder('contactInformation') .select() .where('contactInformation.id = :id', { id: participant.contactInformationId }) @@ -119,8 +123,24 @@ export class ParticipantAPI extends DataSource { .getCount(); if ((await count) !== 0) { return new GraphQLError('contactInformationId already used by other participant.'); - } - const inserts = await this.connection.getRepository(Participant) + } */ + let inserts: any; + await this.connection.transaction(async (entityManager: EntityManager) => { + inserts = await entityManager.getRepository(Participant) + .createQueryBuilder('participant') + .insert() + .into(Participant) + .values([participant]) + .returning('*') + .execute(); + /* await entityManager.getRepository(Participant) + .createQueryBuilder('participant') + .relation(Participant, 'contactInformation') + .of(inserts.identifiers[0].id) + .set(participant.contactInformationId); + */ + }); + /* const inserts = await this.connection.getRepository(Participant) .createQueryBuilder('participant') .insert() .into(Participant) @@ -132,20 +152,10 @@ export class ParticipantAPI extends DataSource { .relation(Participant, 'contactInformation') .of(inserts.identifiers[0].id) .set(participant.contactInformationId); + */ return this.getParticipantById(inserts.identifiers[0].id); } - async createContactInformation (contactInformation: any) { - const inserts = await this.connection.getRepository(ContactInformation) - .createQueryBuilder('contactInformation') - .insert() - .into(ContactInformation) - .values([contactInformation]) - .returning('*') - .execute(); - return this.contactInformationById(inserts.identifiers[0].id); - } - async createEngagement (engagement: any) { const countB = this.connection.getRepository(CargoBike) .createQueryBuilder('cargoBike') @@ -166,16 +176,27 @@ export class ParticipantAPI extends DataSource { .values([engagement]) .returning('*') .execute(); - this.connection.getRepository(Engagement) + await this.connection.getRepository(Engagement) .createQueryBuilder('engagement') .relation(Engagement, 'cargoBike') .of(inserts.identifiers[0].id) .set(engagement.cargoBikeId); - this.connection.getRepository(Engagement) + await this.connection.getRepository(Engagement) .createQueryBuilder('engagement') .relation(Engagement, 'participant') .of(inserts.identifiers[0].id) .set(engagement.participantId); return this.engagementById(inserts.identifiers[0].id); } + + async createEngagementType (engagementType: any) { + const inserts = await this.connection.getRepository(EngagementType) + .createQueryBuilder('et') + .insert() + .values([engagementType]) + .returning('*') + .execute(); + inserts.generatedMaps[0].id = inserts.identifiers[0].id; + return inserts.generatedMaps[0]; + } } diff --git a/src/datasources/userserver/permission.ts b/src/datasources/userserver/permission.ts index 80ac926..92cc838 100644 --- a/src/datasources/userserver/permission.ts +++ b/src/datasources/userserver/permission.ts @@ -3,6 +3,8 @@ export enum Permission { ReadBike = 'BIKE_READ', WriteBike = 'BIKE_WRITE', WriteEquipmentType = 'EQUIPMENT_TYPE_WRITE', + WritePerson = 'PERSON_WRITE', + ReadPerson = 'PERSON_READ' } // Permissions where the creation will be requested on startup @@ -18,5 +20,13 @@ export const requiredPermissions = [ { name: Permission.WriteEquipmentType, description: 'Allows the modification of EquipmentTypes' + }, + { + name: Permission.WritePerson, + description: 'Allows the modification of Persons and contact information' + }, + { + name: Permission.ReadPerson, + description: 'Allows reading of contact information' } ]; diff --git a/src/model/ContactInformation.ts b/src/model/ContactInformation.ts index b87495e..63f81b3 100644 --- a/src/model/ContactInformation.ts +++ b/src/model/ContactInformation.ts @@ -1,4 +1,4 @@ -import { PrimaryGeneratedColumn, Column, Entity, ManyToOne, OneToOne } from 'typeorm'; +import { PrimaryGeneratedColumn, Column, Entity, ManyToOne, OneToOne, JoinColumn } from 'typeorm'; import { Lockable } from './CargoBike'; import { Person } from './Person'; import { Address } from './Provider'; @@ -9,13 +9,19 @@ export class ContactInformation implements Lockable { @PrimaryGeneratedColumn() id: number; - @ManyToOne(type => Person, person => person.contactInformation) - person: Person; + @ManyToOne(type => Person, person => person.contactInformationIds, { + nullable: false - @OneToOne(type => Participant, participant => participant.contactInformation, { + }) + @JoinColumn({ + name: 'personId' + }) + personId: number; + + @OneToOne(type => Participant, participant => participant.contactInformationId, { nullable: true }) - participant: Participant; + participantId: number; @Column(type => { return Address; diff --git a/src/model/Engagement.ts b/src/model/Engagement.ts index 1711bf1..6f7c064 100644 --- a/src/model/Engagement.ts +++ b/src/model/Engagement.ts @@ -21,16 +21,11 @@ export class Engagement { @ManyToOne(type => EngagementType, engagementType => engagementType.engagementIds) engagementTypeId: number; + // I have to find out how typorm will map the datetange data type. @Column({ - type: 'date' + type: 'daterange' }) - from: Date; - - @Column({ - type: 'date', - nullable: true - }) - to: Date; + dateRange: Date[]; @Column() roleCoordinator: boolean; diff --git a/src/model/EngagementType.ts b/src/model/EngagementType.ts index 9745258..bfdb3fe 100644 --- a/src/model/EngagementType.ts +++ b/src/model/EngagementType.ts @@ -12,7 +12,8 @@ export class EngagementType implements Lockable { @Column({ type: 'text', - nullable: true + nullable: false, + default: '' }) description: string; diff --git a/src/model/Participant.ts b/src/model/Participant.ts index f847676..1053825 100644 --- a/src/model/Participant.ts +++ b/src/model/Participant.ts @@ -10,7 +10,8 @@ export class Participant { id: number; @Column({ - type: 'date' + type: 'date', + default: () => 'CURRENT_DATE' }) start: Date; @@ -20,11 +21,13 @@ export class Participant { }) end: Date; - @OneToOne(type => ContactInformation, contactInformation => contactInformation.participant, { - nullable: true + @OneToOne(type => ContactInformation, contactInformation => contactInformation.participantId, { + nullable: false }) - @JoinColumn() - contactInformation: ContactInformation; + @JoinColumn({ + name: 'contactInformationId' + }) + contactInformationId: number; @Column({ nullable: true @@ -61,6 +64,9 @@ export class Participant { }) employeeADFC: boolean; - @Column() + @Column({ + nullable: false, + default: false + }) memberADFC: boolean; } diff --git a/src/model/Person.ts b/src/model/Person.ts index 1540206..94f877c 100644 --- a/src/model/Person.ts +++ b/src/model/Person.ts @@ -13,8 +13,8 @@ export class Person implements Lockable { @Column() name: string; - @OneToMany(type => ContactInformation, contactInformation => contactInformation.person) - contactInformation: ContactInformation; + @OneToMany(type => ContactInformation, contactInformation => contactInformation.personId) + contactInformationIds: number[]; @Column({ nullable: true diff --git a/src/resolvers/contactinformationResolvers.ts b/src/resolvers/contactinformationResolvers.ts index 6abb6a0..7fa0b79 100644 --- a/src/resolvers/contactinformationResolvers.ts +++ b/src/resolvers/contactinformationResolvers.ts @@ -1,14 +1,22 @@ import { GraphQLError } from 'graphql'; import { Permission } from '../datasources/userserver/permission'; +import { Person } from '../model/Person'; export default { Query: { contactInformation: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { - if (req.permissions.includes(Permission.WriteBike)) { + if (req.permissions.includes(Permission.ReadPerson)) { return dataSources.contactInformationAPI.contactInformation(offset, limit); } else { return new GraphQLError('Insufficient Permissions'); } + }, + persons: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.ReadPerson)) { + return dataSources.contactInformationAPI.persons(offset, limit); + } else { + return new GraphQLError('Insufficient Permissions'); + } } }, ContactPerson: { @@ -20,6 +28,25 @@ export default { } } }, + Person: { + contactInformation: (parent: Person, __: any, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.ReadPerson)) { + return dataSources.contactInformationAPI.contactInformationByPersonId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + } + }, + ContactInformation: { + person: (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.ReadPerson)) { + console.log(parent); + return dataSources.contactInformationAPI.personByContactInformationId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + } + }, Mutation: { createContactPerson: (_: any, { contactPerson }: { contactPerson: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteBike)) { @@ -34,6 +61,20 @@ export default { } else { return new GraphQLError('Insufficient Permissions'); } + }, + createContactInformation: (_: any, { contactInformation }: { contactInformation: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteBike)) { + return dataSources.contactInformationAPI.createContactInformation(contactInformation); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + createPerson: (_: any, { person }: { person: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.createPerson(person); + } else { + return new GraphQLError('Insufficient Permissions'); + } } } }; diff --git a/src/resolvers/participantResolvers.ts b/src/resolvers/participantResolvers.ts index c7b1ec5..017c8f8 100644 --- a/src/resolvers/participantResolvers.ts +++ b/src/resolvers/participantResolvers.ts @@ -1,5 +1,6 @@ import { GraphQLError } from 'graphql'; import { Permission } from '../datasources/userserver/permission'; +import { EngagementType } from '../model/EngagementType'; export default { Query: { @@ -23,7 +24,7 @@ export default { return dataSources.participantAPI.engagementByParticipantId(parent.id); }, contactInformation (parent: any, _: any, { dataSources, req }: { dataSources: any, req: any }) { - return (dataSources.contactInformationAPI.contactInformationByParticipantId(parent.id)); + return (dataSources.participantAPI.contactInformationByParticipantId(parent.id)); } }, Engagement: { @@ -32,6 +33,17 @@ export default { }, participant (parent: any, _: any, { dataSources, req }: { dataSources: any, req: any }) { return dataSources.participantAPI.participantByEngagementId(parent.id); + }, + engagementType (parent: any, _: any, { dataSources, req }: { dataSources: any; req: any }): Promise { + return dataSources.participantAPI.engagementTypeByEngagementId(parent.id); + }, + from (parent: any) { + // TODO + return parent.dateRange; + }, + to (parent: any) { + // TODO + return parent.dateRange; } }, Mutation: { @@ -42,16 +54,16 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - createContactInformation: (_: any, { contactInformation }: { contactInformation: any }, { dataSources, req }: { dataSources: any, req: any }) => { + createEngagement: (_: any, { engagement }: { engagement: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteBike)) { - return dataSources.participantAPI.createContactInformation(contactInformation); + return dataSources.participantAPI.createEngagement(engagement); } else { return new GraphQLError('Insufficient Permissions'); } }, - createEngagement: (_: any, { engagement }: { engagement: any }, { dataSources, req }: { dataSources: any, req: any }) => { + createEngagementType: (_: any, { engagementType }: { engagementType: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteBike)) { - return dataSources.participantAPI.createEngagement(engagement); + return dataSources.participantAPI.createEngagementType(engagementType); } else { return new GraphQLError('Insufficient Permissions'); } diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 65ab0b4..ef1ad8d 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -215,13 +215,8 @@ type Participant { usernamefLotte: String usernameSlack: String memberADFC: Boolean! - locationZIPs: [String] + locationZIPs: [String]! memberCoreTeam: Boolean! - - "Date of workshop to become Mentor dt. Pate" - workshopMentor: Date - "Date of last Erste Hilfe Kurs?" - workshopAmbulance: Date """ Note the kommentierte Infodaten Tabelle. This value is calculated form other values. @@ -229,32 +224,38 @@ type Participant { and is either Mentor dt. Pate or Partner Mentor dt. Partnerpate for at least one bike. """ distributedActiveBikeParte: Boolean! - reserve: String engagement: [Engagement] } input ParticipantCreateInput { - start: Date! + "if not set, CURRENT_DATE will be used" + start: Date end: Date "must create contactinformation first, if you want to use new" contactInformationId: ID! usernamefLotte: String usernameSlack: String + "default: false" memberADFC: Boolean! - locationZIPs: [String] - memberCoreTeam: Boolean! - - "Date of workshop to become Mentor dt. Pate" - workshopMentor: Date - "Date of last Erste Hilfe Kurs?" - workshopAmbulance: Date + locationZIPs: [String]! + "default: false" + memberCoreTeam: Boolean +} - reserve: String +type EngagementType { + id: ID! + name: String! + description: String! } +input EngagementTypeCreateInput { + name: String! + description: String +} type Engagement { id: ID! + engagementType: EngagementType! from: Date! to: Date participant: Participant @@ -270,7 +271,10 @@ type Engagement { } input EngagementCreateInput { - from: Date! + engagementTypeId: ID! + "will use CURRENT_DATE if not set" + from: Date + "will use infinit if not set" to: Date participantId: ID! cargoBikeId: ID! @@ -542,44 +546,48 @@ input ProviderCreateInput { cargoBikeIds: [ID]! } -type ContactInformation { +""" +A Person can have several instances of contact information. +The reason for this is, that some people have info for interns and externals that are different. +""" +type Person { id: ID! name: String! - firstName: String - retiredAt: Date - phoneExtern: String - phone2Extern: String - phoneIntern: String - phone2Intern: String - emailExtern: String - emailIntern: String - note: String + firstName: String! + contactInformation: [ContactInformation] } -input ContactInformationCreateInput { +input PersonCreateInput { name: String! firstName: String! - retiredAt: Date - phoneExtern: String - phone2Extern: String - phoneIntern: String - phone2Intern: String - emailExtern: String - emailIntern: String +} + +type ContactInformation { + id: ID! + person: Person! + phone: String + phone2: String + email: String + email2: String + note: String +} + +input ContactInformationCreateInput { + personId: ID! + phone: String + phone2: String + email: String + email2: String note: String } input ContactInformationUpdateInput { id: ID! - name: String - firstName: String - retiredAt: Date - phoneExtern: String - phone2Extern: String - phoneIntern: String - phone2Intern: String - emailExtern: String - emailIntern: String + personId: ID + phone: String + phone2: String + email: String + email2: String note: String } @@ -744,6 +752,7 @@ type Query { lendingStations(offset: Int!, limit: Int!): [LendingStation]! timeframes(offset: Int!, limit: Int!): [TimeFrame]! contactInformation(offset: Int!, limit: Int!): [ContactInformation]! + persons(offset: Int!, limit: Int!): [Person] "returns BikeEvent with CargoBike" bikeEventById(id:ID!): BikeEvent! } @@ -777,9 +786,11 @@ type Mutation { createParticipant(participant: ParticipantCreateInput!): Participant! "create new contactInfo" createContactInformation(contactInformation: ContactInformationCreateInput!): ContactInformation! + createPerson(person: PersonCreateInput!): Person! + createEngagementType(engagementType: EngagementTypeCreateInput!): EngagementType! "create Engagement" createEngagement(engagement: EngagementCreateInput): Engagement! - "createContactPerson ,return null if contactInformationId does not exist" + "createContactPerson, return null if contactInformationId does not exist" createContactPerson(contactPerson: ContactPersonCreateInput): ContactPerson updateContactPerson(contactPerson: ContactPersonUpdateInput): ContactPerson "create Provider, if cargoBikeIds or contactPersonIds are not valid, provider will still be created"