From 2e02920d75e00ecfd292cfcebd16e3cf8ff144b5 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Mon, 2 Nov 2020 17:14:42 +0100 Subject: [PATCH] bugs and clean up and retry when db connection failed --- README.md | 3 +- src/datasources/db/contactinformationAPI.ts | 8 -- src/datasources/db/participantAPI.ts | 20 +++++ src/datasources/db/workshopAPI.ts | 9 ++ src/index.ts | 83 ++++++++++--------- src/model/Participant.ts | 7 +- src/model/Workshop.ts | 4 +- ...nlogResolvers.ts => actionLogResolvers.ts} | 0 ...gobikeResolver.ts => cargoBikeResolver.ts} | 0 ...vers.ts => contactInformationResolvers.ts} | 0 ...esolvers.ts => lendingStationResolvers.ts} | 0 src/resolvers/participantResolvers.ts | 7 ++ src/resolvers/workshopResolvers.ts | 8 ++ src/schema/type-defs.ts | 4 + 14 files changed, 99 insertions(+), 54 deletions(-) rename src/resolvers/{actionlogResolvers.ts => actionLogResolvers.ts} (100%) rename src/resolvers/{cargobikeResolver.ts => cargoBikeResolver.ts} (100%) rename src/resolvers/{contactinformationResolvers.ts => contactInformationResolvers.ts} (100%) rename src/resolvers/{lendingstationResolvers.ts => lendingStationResolvers.ts} (100%) diff --git a/README.md b/README.md index 001adba..9ebe08c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Userserver and postgres are running e.g. with Julius' Docker Compose. docker build -t . docker run --rm -p 4000:4000 ``` -The Dockerfile is pretty stupid and could produce a smaller image, e.g. with multistage build. ### Compile and run Install gulp if not installed ```bash @@ -23,7 +22,7 @@ gulp npm start ``` ### For Development -Install node_modules and gulp +Install node\_modules and gulp ```bash npm -g gulp npm install diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index fe493d5..d89f9d6 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -13,14 +13,6 @@ export class ContactInformationAPI extends DataSource { this.connection = getConnection(); } - async numContactInformationById (id: number) { - return await this.connection.getRepository(ContactInformation) - .createQueryBuilder('contactInformation') - .select() - .where('"contactInformation".id = :id', { id: id }) - .getCount(); - } - async contactInformation (offset: number, limit: number) { return await this.connection.getRepository(ContactInformation) .createQueryBuilder('ci') diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index 026e61a..854f6c1 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -146,6 +146,14 @@ export class ParticipantAPI extends DataSource { .loadOne(); } + async workshopsByParticipantId (id: number) { + return await this.connection.getRepository(Participant) + .createQueryBuilder('p') + .relation(Participant, 'workshopIds') + .of(id) + .loadMany(); + } + /** * creates participant and creates relation to given contactInformation * @param participant to be created @@ -160,6 +168,11 @@ export class ParticipantAPI extends DataSource { .values([participant]) .returning('*') .execute(); + await entityManager.getRepository(Participant) + .createQueryBuilder('w') + .relation(Participant, 'workshopIds') + .of(participant.id) + .add(participant.workshopIds); }); return this.participantById(inserts?.identifiers[0].id); } @@ -179,6 +192,8 @@ export class ParticipantAPI extends DataSource { if (await LockUtils.isLocked(entityManager, Participant, 'p', participant.id, userId)) { throw new GraphQLError('Participant is locked by another user'); } + const workshops = participant.workshopIds; + delete participant.workshopIds; await ActionLogger.log(entityManager, Participant, 'p', participant, userId); await entityManager.getRepository(Participant) .createQueryBuilder('p') @@ -186,6 +201,11 @@ export class ParticipantAPI extends DataSource { .set({ ...participant }) .where('id = :id', { id: participant.id }) .execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + await entityManager.getRepository(Participant) + .createQueryBuilder('w') + .relation(Participant, 'workshopIds') + .of(participant.id) + .add(workshops); }); !keepLock && await this.unlockParticipant(participant.id, userId); return await this.participantById(participant.id); diff --git a/src/datasources/db/workshopAPI.ts b/src/datasources/db/workshopAPI.ts index f86ae87..b9c0c40 100644 --- a/src/datasources/db/workshopAPI.ts +++ b/src/datasources/db/workshopAPI.ts @@ -5,6 +5,7 @@ import { Workshop } from '../../model/Workshop'; import { ActionLogger, deleteEntity, LockUtils } from './utils'; import { UserInputError } from 'apollo-server-express'; import { GraphQLError } from 'graphql'; +import { Participant } from '../../model/Participant'; export class WorkshopAPI extends DataSource { connection: Connection @@ -152,4 +153,12 @@ export class WorkshopAPI extends DataSource { .of(id) .loadOne(); } + + async participantsByWorkshopId (id: number): Promise { + return await this.connection.getRepository(Workshop) + .createQueryBuilder('w') + .relation(Workshop, 'participantIds') + .of(id) + .loadMany(); + } } diff --git a/src/index.ts b/src/index.ts index 0b305d5..2811f2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,9 @@ import { ApolloServer } from 'apollo-server-express'; -import bikeresolver from './resolvers/cargobikeResolver'; +import bikeResolver from './resolvers/cargoBikeResolver'; import { CargoBikeAPI } from './datasources/db/cargobikeAPI'; import typeDefs from './schema/type-defs'; import 'reflect-metadata'; -import { createConnection } from 'typeorm'; +import { ConnectionOptions, createConnection } from 'typeorm'; import { UserServerAPI } from './datasources/userserver/userserviceAPI'; import express from 'express'; import { requiredPermissions } from './datasources/userserver/permission'; @@ -19,13 +19,13 @@ import { Provider } from './model/Provider'; import { Engagement } from './model/Engagement'; import { Workshop } from './model/Workshop'; import { LendingStationAPI } from './datasources/db/lendingstationAPI'; -import lendingstationResolvers from './resolvers/lendingstationResolvers'; +import lendingStationResolvers from './resolvers/lendingStationResolvers'; import { ParticipantAPI } from './datasources/db/participantAPI'; import participantResolvers from './resolvers/participantResolvers'; import { ContactInformationAPI } from './datasources/db/contactinformationAPI'; import providerResolvers from './resolvers/providerResolvers'; import { ProviderAPI } from './datasources/db/providerAPI'; -import contactinformationResolvers from './resolvers/contactinformationResolvers'; +import contactInformationResolvers from './resolvers/contactInformationResolvers'; import { Person } from './model/Person'; import { WorkshopType } from './model/WorkshopType'; import { EngagementType } from './model/EngagementType'; @@ -34,11 +34,36 @@ import { BikeEventType } from './model/BikeEventType'; import { WorkshopAPI } from './datasources/db/workshopAPI'; import workshopResolvers from './resolvers/workshopResolvers'; import { ActionLog } from './model/ActionLog'; -import actionlogResolvers from './resolvers/actionlogResolvers'; +import actionLogResolvers from './resolvers/actionLogResolvers'; import { ActionLogAPI } from './datasources/db/actionLogAPI'; require('dotenv').config(); +const connOptions: ConnectionOptions = { + type: 'postgres', + url: process.env.POSTGRES_CONNECTION_URL, + entities: [ + CargoBike, + BikeEvent, + BikeEventType, + ContactInformation, + Equipment, + EquipmentType, + LendingStation, + TimeFrame, + Organisation, + Participant, + Provider, + Engagement, + EngagementType, + Workshop, + Person, + WorkshopType, + ActionLog + ], + synchronize: true, + logging: false +}; /** * Function that is called to authenticate a user by using the user rpc server * @param req @@ -68,45 +93,26 @@ async function authenticate (req: any, res: any, next: any) { } } -createConnection({ - type: 'postgres', - url: process.env.POSTGRES_CONNECTION_URL, - entities: [ - CargoBike, - BikeEvent, - BikeEventType, - ContactInformation, - Equipment, - EquipmentType, - LendingStation, - TimeFrame, - Organisation, - Participant, - Provider, - Engagement, - EngagementType, - Workshop, - Person, - WorkshopType, - ActionLog - ], - synchronize: true, - logging: false -}).then(async () => { - console.log('connected to db'); -}).catch(error => console.log(error)); +function retryConnect (): any { + createConnection(connOptions).catch(error => { + console.log(error); + setTimeout(retryConnect, 3000); + }); +} + +retryConnect(); const userAPI = new UserServerAPI(process.env.RPC_HOST); const server = new ApolloServer({ resolvers: [ - bikeresolver, - lendingstationResolvers, + bikeResolver, + lendingStationResolvers, participantResolvers, providerResolvers, - contactinformationResolvers, + contactInformationResolvers, workshopResolvers, - actionlogResolvers + actionLogResolvers ], typeDefs, dataSources: () => ({ @@ -130,8 +136,7 @@ app.post('/graphql', authenticate); app.get(/\/graphql?&.*query=/, authenticate); server.applyMiddleware({ app }); -console.log(__dirname); app.listen(4000, async () => { - console.log('Server listening on port 4000'); - await userAPI.createDefinedPermissions(); + await userAPI.createDefinedPermissions().catch( + err => console.log(err)); }); diff --git a/src/model/Participant.ts b/src/model/Participant.ts index 2774722..e05bd1e 100644 --- a/src/model/Participant.ts +++ b/src/model/Participant.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, OneToMany, ManyToMany } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn, OneToMany, ManyToMany, JoinTable } from 'typeorm'; import { ContactInformation } from './ContactInformation'; import { Engagement } from './Engagement'; import { Workshop } from './Workshop'; @@ -47,10 +47,11 @@ export class Participant implements Lockable { @OneToMany(type => Engagement, engagement => engagement.participantId) engagement: Engagement[]; - @ManyToMany(type => Workshop, workshop => workshop.participants, { + @ManyToMany(type => Workshop, workshop => workshop.participantIds, { nullable: true }) - workshops: Workshop[]; + @JoinTable() + workshopIds: number[]; @Column({ nullable: false, diff --git a/src/model/Workshop.ts b/src/model/Workshop.ts index b193ba0..eab3426 100644 --- a/src/model/Workshop.ts +++ b/src/model/Workshop.ts @@ -22,10 +22,10 @@ export class Workshop implements Lockable { }) date: Date; - @ManyToMany(type => Participant, participant => participant.workshops, { + @ManyToMany(type => Participant, participant => participant.workshopIds, { nullable: true }) - participants: Participant[]; + participantIds: number[]; @ManyToOne(type => Participant, { nullable: false diff --git a/src/resolvers/actionlogResolvers.ts b/src/resolvers/actionLogResolvers.ts similarity index 100% rename from src/resolvers/actionlogResolvers.ts rename to src/resolvers/actionLogResolvers.ts diff --git a/src/resolvers/cargobikeResolver.ts b/src/resolvers/cargoBikeResolver.ts similarity index 100% rename from src/resolvers/cargobikeResolver.ts rename to src/resolvers/cargoBikeResolver.ts diff --git a/src/resolvers/contactinformationResolvers.ts b/src/resolvers/contactInformationResolvers.ts similarity index 100% rename from src/resolvers/contactinformationResolvers.ts rename to src/resolvers/contactInformationResolvers.ts diff --git a/src/resolvers/lendingstationResolvers.ts b/src/resolvers/lendingStationResolvers.ts similarity index 100% rename from src/resolvers/lendingstationResolvers.ts rename to src/resolvers/lendingStationResolvers.ts diff --git a/src/resolvers/participantResolvers.ts b/src/resolvers/participantResolvers.ts index 7a837be..9968bb8 100644 --- a/src/resolvers/participantResolvers.ts +++ b/src/resolvers/participantResolvers.ts @@ -62,6 +62,13 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + workshops (parent: any, _: any, { dataSources, req }: { dataSources: any, req: any }) { + if (req.permissions.includes(Permission.ReadWorkshop)) { + return (dataSources.participantAPI.workshopsByParticipantId(parent.id)); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, isLocked: (parent: any, __: any, { dataSources, req }: { dataSources: any; req: any }) => isLocked(parent, { dataSources, req }) }, Engagement: { diff --git a/src/resolvers/workshopResolvers.ts b/src/resolvers/workshopResolvers.ts index 7955a43..58e2ef3 100644 --- a/src/resolvers/workshopResolvers.ts +++ b/src/resolvers/workshopResolvers.ts @@ -1,6 +1,7 @@ import { Permission } from '../datasources/userserver/permission'; import { GraphQLError } from 'graphql'; import { isLocked } from '../datasources/db/utils'; +import { Participant } from '../model/Participant'; export default { Query: { @@ -48,6 +49,13 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + participants (parent: any, _: any, { dataSources, req }: { dataSources: any; req: any }): Promise | GraphQLError { + if (req.permissions.includes(Permission.ReadParticipant)) { + return dataSources.workshopAPI.participantsByWorkshopId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, isLocked: (parent: any, __: any, { dataSources, req }: { dataSources: any; req: any }) => isLocked(parent, { dataSources, req }) }, WorkshopType: { diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 6677b98..933c0eb 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -325,6 +325,7 @@ type Participant { """ distributedActiveBikeParte: Boolean! engagement: [Engagement] + workshops: [Workshop] isLocked: Boolean! "null if not locked by other user" lockedBy: ID @@ -344,6 +345,7 @@ input ParticipantCreateInput { locationZIPs: [String]! "default: false" memberCoreTeam: Boolean + workshopIds: [ID] } input ParticipantUpdateInput { @@ -360,6 +362,7 @@ input ParticipantUpdateInput { locationZIPs: [String] "default: false" memberCoreTeam: Boolean + workshopIds: [ID] keepLock: Boolean } @@ -371,6 +374,7 @@ type Workshop { workshopType: WorkshopType! trainer1: Participant! trainer2: Participant + participants: [Participant] isLocked: Boolean! "null if not locked by other user" lockedBy: ID