From 6b03f11de1825ec575d7293e51062c02481885f1 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Mon, 16 Nov 2020 17:43:33 +0100 Subject: [PATCH 01/28] src/schema/*: fixed schema and use custom type for projectAllowance --- src/schema/type-defs.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 5907eb1..f402582 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -25,6 +25,14 @@ export default gql` scalar Date "only time hh-mm-ss" scalar Time +""" +is of american format [-]$[0-9]+.[0-9][0-9] +commas every three digits and . for decimals with 2 digits after the . +There can be a leading -. +There is a currency signe at the first position or second position if - is set. +The kind of currency depends on the database. +""" +scalar Money "The CargoBike type is central to the graph. You could call it the root." type CargoBike { @@ -171,7 +179,7 @@ type InsuranceData { There is a currency signe at the first position or second position if - is set. The kind of currency depends on the database. """ - projectAllowance: String + projectAllowance: Money notes: String } @@ -197,7 +205,7 @@ input InsuranceDataCreateInput { You can pass a currency signe at the first position or second position of + or - is set. The kind of currency depends on the database. """ - projectAllowance: String + projectAllowance: Money notes: String } @@ -215,6 +223,7 @@ input InsuranceDataUpdateInput { maintenanceAgreement: String hasFixedRate: Boolean fixedRate: Float + """ Projektzuschuss: must be of format [+|-][$][0-9]*[.[0-9]*] commas are ignored, non numeric values except , and . lead to errors @@ -222,7 +231,7 @@ input InsuranceDataUpdateInput { You can pass a currency signe at the first position or second position of + or - is set. The kind of currency depends on the database. """ - projectAllowance: String + projectAllowance: Money notes: String } From bba78c5688a54ec573e4bffbb14761a489bb493f Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Thu, 19 Nov 2020 15:25:33 +0100 Subject: [PATCH 02/28] schema: added scalar type for project allowance --- src/schema/type-defs.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 9bd7ad0..1a0267e 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -25,7 +25,14 @@ export default gql` scalar Date "only time hh-mm-ss" scalar Time - + """ + is of american format [-]$[0-9]+.[0-9][0-9] + commas every three digits and . for decimals with 2 digits after the . + There can be a leading -. + There is a currency signe at the first position or second position if - is set. + The kind of currency depends on the database. + """ + scalar Money "The CargoBike type is central to the graph. You could call it the root." type CargoBike { id: ID! @@ -171,7 +178,7 @@ export default gql` There is a currency signe at the first position or second position if - is set. The kind of currency depends on the database. """ - projectAllowance: String + projectAllowance: Money notes: String } @@ -197,7 +204,7 @@ export default gql` You can pass a currency signe at the first position or second position of + or - is set. The kind of currency depends on the database. """ - projectAllowance: String + projectAllowance: Money notes: String } @@ -223,7 +230,7 @@ export default gql` You can pass a currency signe at the first position or second position of + or - is set. The kind of currency depends on the database. """ - projectAllowance: String + projectAllowance: Money notes: String } From c3fa25fa0de7120a8a27642ab767b9d487f92557 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Thu, 19 Nov 2020 18:51:40 +0100 Subject: [PATCH 03/28] src/*: dont requiere limit and offset on queries On Queries like cargoBikes(offset: Int, limit: Int), offset and limit does not need to be passed anymore. When either is not passed, both values are ignored. --- src/datasources/db/cargobikeAPI.ts | 76 ++++++++++---------- src/datasources/db/contactinformationAPI.ts | 20 ++---- src/datasources/db/lendingstationAPI.ts | 21 ++---- src/datasources/db/participantAPI.ts | 58 +++++++-------- src/datasources/db/providerAPI.ts | 20 ++---- src/datasources/db/utils.ts | 16 +++++ src/datasources/db/workshopAPI.ts | 20 ++---- src/resolvers/cargoBikeResolver.ts | 26 ++++--- src/resolvers/contactInformationResolvers.ts | 4 +- src/resolvers/lendingStationResolvers.ts | 4 +- src/resolvers/participantResolvers.ts | 6 +- src/resolvers/providerResolvers.ts | 4 +- src/resolvers/workshopResolvers.ts | 4 +- src/schema/type-defs.ts | 38 +++++----- 14 files changed, 146 insertions(+), 171 deletions(-) diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 75d2c84..4fbbc44 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -26,7 +26,7 @@ import { Equipment } from '../../model/Equipment'; import { Engagement } from '../../model/Engagement'; import { Provider } from '../../model/Provider'; import { TimeFrame } from '../../model/TimeFrame'; -import { ActionLogger, deleteEntity, LockUtils } from './utils'; +import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { EquipmentType } from '../../model/EquipmentType'; import { BikeEventType } from '../../model/BikeEventType'; import { UserInputError } from 'apollo-server-express'; @@ -42,14 +42,8 @@ export class CargoBikeAPI extends DataSource { this.connection = getConnection(); } - async getCargoBikes (offset: number, limit: number) { - return await this.connection.createQueryBuilder() - .select('cargoBike') - .from(CargoBike, 'cargoBike') - .orderBy('name', 'ASC') - .offset(offset) - .limit(limit) - .getMany(); + async getCargoBikes (offset?: number, limit?: number) { + return await getAllEntity(this.connection, CargoBike, 'cb', offset, limit); } /** @@ -221,14 +215,22 @@ export class CargoBikeAPI extends DataSource { .loadOne(); } - async bikeEventsByCargoBikeId (id: number, offset: number = 0, limit:number = 100) { - return await this.connection.getRepository(CargoBike) - .createQueryBuilder('cb') - .skip(offset) - .take(limit) - .relation(CargoBike, 'bikeEvents') - .of(id) - .loadMany(); + async bikeEventsByCargoBikeId (id: number, offset?: number, limit?: number) { + if (offset === null || limit === null) { + return await this.connection.getRepository(CargoBike) + .createQueryBuilder('cb') + .relation(CargoBike, 'bikeEvents') + .of(id) + .loadMany(); + } else { + return await this.connection.getRepository(CargoBike) + .createQueryBuilder('cb') + .skip(offset) + .take(limit) + .relation(CargoBike, 'bikeEvents') + .of(id) + .loadMany(); + } } async createBikeEventType (bikeEventType: any) { @@ -267,22 +269,12 @@ export class CargoBikeAPI extends DataSource { return await this.bikeEventTypeById(bikeEventType.id); } - async bikeEventTypes (offset: number, limit: number) { - return await this.connection.getRepository(BikeEventType) - .createQueryBuilder('bet') - .select() - .skip(offset) - .take(limit) - .getMany(); + async bikeEventTypes (offset?: number, limit?: number) { + return await getAllEntity(this.connection, BikeEventType, 'bet', offset, limit); } - async bikeEvents (offset: number, limit: number) { - return await this.connection.getRepository(BikeEvent) - .createQueryBuilder('be') - .select() - .skip(offset) - .take(limit) - .getMany(); + async bikeEvents (offset?: number, limit?: number) { + return await getAllEntity(this.connection, BikeEvent, 'be', offset, limit); } async bikeEventTypeById (id: number) { @@ -343,12 +335,22 @@ export class CargoBikeAPI extends DataSource { * @param limit * @param id */ - async equipmentByCargoBikeId (offset: number, limit: number, id: number) { - return await this.connection.getRepository(Equipment) - .createQueryBuilder('equipment') - .select() - .where('equipment."cargoBikeId" = :id', { id: id }) - .getMany(); + async equipmentByCargoBikeId (id: number, offset?: number, limit?: number) { + if (offset == null || limit === null) { + return await this.connection.getRepository(Equipment) + .createQueryBuilder('equipment') + .select() + .where('equipment."cargoBikeId" = :id', { id: id }) + .getMany(); + } else { + return await this.connection.getRepository(Equipment) + .createQueryBuilder('equipment') + .select() + .where('equipment."cargoBikeId" = :id', { id: id }) + .skip(offset) + .take(limit) + .getMany(); + } } async createEquipment ({ equipment }: { equipment: any }) { diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index 767b740..a2312db 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { ContactInformation } from '../../model/ContactInformation'; import { Person } from '../../model/Person'; -import { ActionLogger, deleteEntity, LockUtils } from './utils'; +import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { GraphQLError } from 'graphql'; import { LendingStation } from '../../model/LendingStation'; @@ -32,13 +32,8 @@ export class ContactInformationAPI extends DataSource { this.connection = getConnection(); } - async contactInformation (offset: number, limit: number) { - return await this.connection.getRepository(ContactInformation) - .createQueryBuilder('ci') - .select() - .offset(offset) - .limit(limit) - .getMany(); + async contactInformation (offset?: number, limit?: number) { + return await getAllEntity(this.connection, ContactInformation, 'ci', offset, limit); } async contactInformationById (id: number) { @@ -91,13 +86,8 @@ export class ContactInformationAPI extends DataSource { return await deleteEntity(this.connection, Person, 'p', id, userId); } - async persons (offset: number, limit: number) { - return await this.connection.getRepository(Person) - .createQueryBuilder('person') - .select() - .skip(offset) - .take(limit) - .getMany(); + async persons (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Person, 'p', offset, limit); } async personById (id: number) { diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index 3df03e1..9b3e53f 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -24,7 +24,7 @@ import { Connection, EntityManager, getConnection } from 'typeorm'; import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; import { TimeFrame } from '../../model/TimeFrame'; -import { ActionLogger, deleteEntity, genDateRange, LockUtils } from './utils'; +import { ActionLogger, deleteEntity, genDateRange, getAllEntity, LockUtils } from './utils'; export class LendingStationAPI extends DataSource { connection : Connection @@ -44,14 +44,8 @@ export class LendingStationAPI extends DataSource { /** * get all lendingStations */ - async lendingStations (offset: number, limit: number) { - return await this.connection.getRepository(LendingStation) - .createQueryBuilder('lendingStation') - .select() - .offset(offset) - .limit(limit) - .orderBy('name', 'ASC') - .getMany() || new GraphQLError('Internal Server Error: could not query data from table lendingStation'); + async lendingStations (offset?: number, limit?: number) { + return await getAllEntity(this.connection, LendingStation, 'ls', offset, limit); } /** @@ -79,13 +73,8 @@ export class LendingStationAPI extends DataSource { .loadOne(); } - async timeFrames (offset: number, limit: number) { - return await this.connection.getRepository(TimeFrame) - .createQueryBuilder('timeframe') - .select() - .offset(offset) - .limit(limit) - .getMany() || []; + async timeFrames (offset?: number, limit?: number) { + return await getAllEntity(this.connection, TimeFrame, 'tf', offset, limit); } async timeFramesByCargoBikeId (id: number) { diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index e481acc..d492e55 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -23,7 +23,7 @@ import { ContactInformation } from '../../model/ContactInformation'; import { Engagement } from '../../model/Engagement'; import { Participant } from '../../model/Participant'; import { EngagementType } from '../../model/EngagementType'; -import { ActionLogger, deleteEntity, genDateRange, LockUtils } from './utils'; +import { ActionLogger, deleteEntity, genDateRange, getAllEntity, LockUtils } from './utils'; import { UserInputError } from 'apollo-server-express'; import { GraphQLError } from 'graphql'; @@ -42,13 +42,8 @@ export class ParticipantAPI extends DataSource { .getOne(); } - async getParticipants (offset: number, limit: number) { - return await this.connection.getRepository(Participant) - .createQueryBuilder('participant') - .select() - .offset(offset) - .limit(limit) - .getMany(); + async getParticipants (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Participant, 'p', offset, limit); } async participantByEngagementId (id: number) { @@ -84,17 +79,24 @@ export class ParticipantAPI extends DataSource { .getMany(); } - async engagementByCargoBikeId (offset: number, limit: number, id: number) { - return await this.connection.getRepository(Engagement) - .createQueryBuilder('engagement') - .select() - .where('engagement."cargoBikeId" = :id', { - id: id - }) - .skip(offset) - .take(limit) - .orderBy('engagement."dateRange"', 'DESC') - .getMany(); + async engagementByCargoBikeId (id: number, offset?: number, limit?: number) { + if (limit === null || offset === null) { + return await this.connection.getRepository(Engagement) + .createQueryBuilder('engagement') + .select() + .where('engagement."cargoBikeId" = :id', { id: id }) + .orderBy('engagement."dateRange"', 'DESC') + .getMany(); + } else { + return await this.connection.getRepository(Engagement) + .createQueryBuilder('engagement') + .select() + .where('engagement."cargoBikeId" = :id', { id: id }) + .skip(offset) + .take(limit) + .orderBy('engagement."dateRange"', 'DESC') + .getMany(); + } } async currentEngagementByCargoBikeId (id: number) { @@ -107,13 +109,8 @@ export class ParticipantAPI extends DataSource { .getMany(); } - async engagements (offset: number, limit: number) { - return await this.connection.getRepository(Engagement) - .createQueryBuilder('e') - .select() - .skip(offset) - .take(limit) - .getMany(); + async engagements (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Engagement, 'e', offset, limit); } async engagementById (id: number) { @@ -132,13 +129,8 @@ export class ParticipantAPI extends DataSource { .getOne(); } - async engagementTypes (offset: number, limit: number) { - return await this.connection.getRepository(EngagementType) - .createQueryBuilder('et') - .select() - .skip(offset) - .take(limit) - .getMany(); + async engagementTypes (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Engagement, 'e', offset, limit); } async engagementTypeByEngagementId (id: number) { diff --git a/src/datasources/db/providerAPI.ts b/src/datasources/db/providerAPI.ts index d8e2c9c..dd9511c 100644 --- a/src/datasources/db/providerAPI.ts +++ b/src/datasources/db/providerAPI.ts @@ -24,7 +24,7 @@ import { Organisation } from '../../model/Organisation'; import { UserInputError } from 'apollo-server-express'; import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; -import { ActionLogger, deleteEntity, LockUtils } from './utils'; +import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { GraphQLError } from 'graphql'; export class ProviderAPI extends DataSource { @@ -42,13 +42,8 @@ export class ProviderAPI extends DataSource { .getOne(); } - async provider (offset: number, limit: number) { - return await this.connection.getRepository(Provider) - .createQueryBuilder('provider') - .select() - .skip(offset) - .take(limit) - .getMany(); + async provider (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Provider, 'p', offset, limit); } async providerByOrganisationId (id: number) { @@ -75,13 +70,8 @@ export class ProviderAPI extends DataSource { .loadOne(); } - async organisations (offset: number, limit: number) { - return await this.connection.getRepository(Organisation) - .createQueryBuilder('o') - .select() - .skip(offset) - .limit(limit) - .getMany(); + async organisations (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Organisation, 'o', offset, limit); } async organisationById (id: number) { diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 8c56441..43e3554 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -64,6 +64,22 @@ export async function deleteEntity (connection: Connection, target: ObjectType, alias: string, offset?: number, limit?: number) { + if (offset === null || limit === null) { + return await connection.getRepository(target) + .createQueryBuilder(alias) + .select() + .getMany(); + } else { + return await connection.getRepository(target) + .createQueryBuilder(alias) + .select() + .skip(offset) + .take(limit) + .getMany(); + } +} + export class LockUtils { static async findById (connection: Connection, target: ObjectType, alias: string, id: number): Promise { return await connection.getRepository(target) diff --git a/src/datasources/db/workshopAPI.ts b/src/datasources/db/workshopAPI.ts index 62b0d07..7c28cd2 100644 --- a/src/datasources/db/workshopAPI.ts +++ b/src/datasources/db/workshopAPI.ts @@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { WorkshopType } from '../../model/WorkshopType'; import { Workshop } from '../../model/Workshop'; -import { ActionLogger, deleteEntity, LockUtils } from './utils'; +import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; import { UserInputError } from 'apollo-server-express'; import { GraphQLError } from 'graphql'; import { Participant } from '../../model/Participant'; @@ -126,13 +126,8 @@ export class WorkshopAPI extends DataSource { .getOne(); } - async workshopTypes (offset: number, limit: number) { - return await this.connection.getRepository(WorkshopType) - .createQueryBuilder('w') - .select() - .skip(offset) - .take(limit) - .getMany(); + async workshopTypes (offset?: number, limit?: number) { + return getAllEntity(this.connection, WorkshopType, 'wt', offset, limit); } async workshopById (id: number) { @@ -148,13 +143,8 @@ export class WorkshopAPI extends DataSource { * @param offset * @param limit */ - async workshops (offset: number, limit: number) { - return await this.connection.getRepository(Workshop) - .createQueryBuilder('w') - .select() - .skip(offset) - .take(limit) - .getMany(); + async workshops (offset?: number, limit?: number) { + return await getAllEntity(this.connection, Workshop, 'w', offset, limit); } async trainer1ByWorkshopId (id: number) { diff --git a/src/resolvers/cargoBikeResolver.ts b/src/resolvers/cargoBikeResolver.ts index 6793783..38ced74 100644 --- a/src/resolvers/cargoBikeResolver.ts +++ b/src/resolvers/cargoBikeResolver.ts @@ -30,14 +30,14 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - cargoBikes: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + cargoBikes: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBike)) { return dataSources.cargoBikeAPI.getCargoBikes(offset, limit); } else { return new GraphQLError('Insufficient Permissions'); } }, - bikeEvents: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + bikeEvents: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBikeEvent)) { return dataSources.cargoBikeAPI.bikeEvents(offset, limit); } else { @@ -58,14 +58,14 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - bikeEventTypes: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + bikeEventTypes: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBikeEvent)) { return dataSources.cargoBikeAPI.bikeEventTypes(offset, limit); } else { return new GraphQLError('Insufficient Permissions'); } }, - equipment: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + equipment: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadEquipment)) { return dataSources.cargoBikeAPI.getEquipment(offset, limit); } else { @@ -79,7 +79,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - equipmentTypes: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + equipmentTypes: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadEquipment)) { return dataSources.cargoBikeAPI.equipmentTypes(offset, limit); } else { @@ -102,23 +102,23 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - engagement (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) { + engagement (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) { if (req.permissions.includes(Permission.ReadEngagement)) { - return dataSources.participantAPI.engagementByCargoBikeId(offset, limit, parent.id); + return dataSources.participantAPI.engagementByCargoBikeId(parent.id, offset, limit); } else { return new GraphQLError('Insufficient Permissions'); } }, - participants (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) { // TODO should be done with engagements + participants (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) { // TODO should be done with engagements if (req.permissions.includes(Permission.ReadParticipant)) { return dataSources.participantAPI.participantsByCargoBikeId(parent.id); } else { return new GraphQLError('Insufficient Permissions'); } }, - equipment (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) { + equipment (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) { if (req.permissions.includes(Permission.ReadEquipment)) { - return dataSources.cargoBikeAPI.equipmentByCargoBikeId(offset, limit, parent.id); + return dataSources.cargoBikeAPI.equipmentByCargoBikeId(parent.id, offset, limit); } else { return new GraphQLError('Insufficient Permissions'); } @@ -130,7 +130,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - bikeEvents (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) { + bikeEvents (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) { if (req.permissions.includes(Permission.ReadBikeEvent)) { return dataSources.cargoBikeAPI.bikeEventsByCargoBikeId(parent.id, offset, limit); } else { @@ -173,6 +173,10 @@ export default { isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) }, + EquipmentType: { + isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), + isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) + }, BikeEvent: { cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { if (req.permissions.includes(Permission.ReadBike)) { diff --git a/src/resolvers/contactInformationResolvers.ts b/src/resolvers/contactInformationResolvers.ts index 0222875..6743ca7 100644 --- a/src/resolvers/contactInformationResolvers.ts +++ b/src/resolvers/contactInformationResolvers.ts @@ -24,7 +24,7 @@ import { isLocked, isLockedByMe } from '../datasources/db/utils'; export default { Query: { - contactInformation: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + contactInformation: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadPerson)) { return dataSources.contactInformationAPI.contactInformation(offset, limit); } else { @@ -45,7 +45,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - persons: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + 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 { diff --git a/src/resolvers/lendingStationResolvers.ts b/src/resolvers/lendingStationResolvers.ts index b15042d..eb26f62 100644 --- a/src/resolvers/lendingStationResolvers.ts +++ b/src/resolvers/lendingStationResolvers.ts @@ -31,7 +31,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - lendingStations: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + lendingStations: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadLendingStation)) { return dataSources.lendingStationAPI.lendingStations(offset, limit); } else { @@ -45,7 +45,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - timeFrames: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + timeFrames: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadTimeFrame)) { return dataSources.lendingStationAPI.timeFrames(offset, limit); } else { diff --git a/src/resolvers/participantResolvers.ts b/src/resolvers/participantResolvers.ts index eaa9608..ed058e1 100644 --- a/src/resolvers/participantResolvers.ts +++ b/src/resolvers/participantResolvers.ts @@ -30,7 +30,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - participants: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + participants: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadParticipant)) { return dataSources.participantAPI.getParticipants(offset, limit); } else { @@ -44,7 +44,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - engagements: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + engagements: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadEngagement)) { return dataSources.participantAPI.engagements(offset, limit); } else { @@ -58,7 +58,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - engagementTypes: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + engagementTypes: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadEngagement)) { return dataSources.participantAPI.engagementTypes(offset, limit); } else { diff --git a/src/resolvers/providerResolvers.ts b/src/resolvers/providerResolvers.ts index 09f622f..8e48e3d 100644 --- a/src/resolvers/providerResolvers.ts +++ b/src/resolvers/providerResolvers.ts @@ -23,7 +23,7 @@ import { isLocked, isLockedByMe } from '../datasources/db/utils'; export default { Query: { - providers: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + providers: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadProvider)) { return dataSources.providerAPI.provider(offset, limit); } else { @@ -37,7 +37,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - organisations: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + organisations: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadOrganisation)) { return dataSources.providerAPI.organisations(offset, limit); } else { diff --git a/src/resolvers/workshopResolvers.ts b/src/resolvers/workshopResolvers.ts index b51a2be..38ad82f 100644 --- a/src/resolvers/workshopResolvers.ts +++ b/src/resolvers/workshopResolvers.ts @@ -31,7 +31,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - workshopTypes: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + workshopTypes: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadWorkshop)) { return dataSources.workshopAPI.workshopTypes(offset, limit); } else { @@ -45,7 +45,7 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - workshops: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + workshops: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadWorkshop)) { return dataSources.workshopAPI.workshops(offset, limit); } else { diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 1a0267e..c6c9c51 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -58,7 +58,7 @@ export default gql` """ dimensionsAndLoad: DimensionsAndLoad! bikeEvents(offset: Int, limit: Int): [BikeEvent] - equipment(offset: Int!, limit: Int!): [Equipment] + equipment(offset: Int, limit: Int): [Equipment] "Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2" equipmentType: [EquipmentType] "Sticker State" @@ -71,7 +71,7 @@ export default gql` lendingStation: LendingStation taxes: Taxes currentEngagements: [Engagement] - engagement(offset: Int!, limit: Int!): [Engagement] + engagement(offset: Int, limit: Int): [Engagement] timeFrames: [TimeFrame] isLocked: Boolean! isLockedByMe: Boolean! @@ -670,6 +670,8 @@ export default gql` name: String! isLockedByMe: Boolean! isLocked: Boolean! + "null if not locked by other user" + lockedBy: ID lockedUntil: Date } @@ -961,40 +963,40 @@ export default gql` "Will (eventually) return all properties of cargo bike" cargoBikeById(id:ID!): CargoBike "returns cargoBikes ordered by name ascending, relations are not loaded, use cargoBikeById instead" - cargoBikes(offset: Int!, limit: Int!): [CargoBike!]! + cargoBikes(offset: Int, limit: Int): [CargoBike!]! engagementById(id: ID!): Engagement - engagements(offset: Int!, limit: Int!): [Engagement!]! + engagements(offset: Int, limit: Int): [Engagement!]! engagementTypeById(id: ID!): EngagementType - engagementTypes(offset: Int!, limit: Int!): [EngagementType!]! + engagementTypes(offset: Int, limit: Int): [EngagementType!]! "equipment by id, will return null if id not found" equipmentById(id: ID!): Equipment - equipment(offset: Int!, limit: Int!): [Equipment!]! + equipment(offset: Int, limit: Int): [Equipment!]! equipmentTypeById(id: ID!): EquipmentType - equipmentTypes(offset: Int!, limit: Int!): [EquipmentType!]! + equipmentTypes(offset: Int, limit: Int): [EquipmentType!]! "return null if id not found" providerById(id:ID!): Provider "Returns providers with pagination" - providers(offset: Int!, limit: Int!): [Provider!]! + providers(offset: Int, limit: Int): [Provider!]! "participant by id" participantById(id:ID!): Participant - participants(offset: Int!, limit: Int!): [Participant!]! + participants(offset: Int, limit: Int): [Participant!]! workshopTypeById(id: ID!): WorkshopType - workshopTypes(offset: Int!, limit: Int!): [WorkshopType!]! + workshopTypes(offset: Int, limit: Int): [WorkshopType!]! workshopById(id: ID!): Workshop - workshops(offset: Int!, limit: Int!): [Workshop!]! + workshops(offset: Int, limit: Int): [Workshop!]! lendingStationById(id:ID!): LendingStation - lendingStations(offset: Int!, limit: Int!): [LendingStation!]! + lendingStations(offset: Int, limit: Int): [LendingStation!]! organisationById(id: ID!): Organisation - organisations(offset: Int!, limit: Int!): [Organisation!]! + organisations(offset: Int, limit: Int): [Organisation!]! timeFrameById(id: ID!): TimeFrame - timeFrames(offset: Int!, limit: Int!): [TimeFrame!]! + timeFrames(offset: Int, limit: Int): [TimeFrame!]! contactInformationById(id: ID!): ContactInformation - contactInformation(offset: Int!, limit: Int!): [ContactInformation!]! + contactInformation(offset: Int, limit: Int): [ContactInformation!]! personById(id: ID!): Person - persons(offset: Int!, limit: Int!): [Person!] - bikeEventTypes(offset: Int!, limit: Int!): [BikeEventType!] + persons(offset: Int, limit: Int): [Person!] + bikeEventTypes(offset: Int, limit: Int): [BikeEventType!] bikeEventTypeByd(id: ID!): BikeEventType - bikeEvents(offset: Int!, limit: Int!): [BikeEvent!]! + bikeEvents(offset: Int, limit: Int): [BikeEvent!]! bikeEventById(id:ID!): BikeEvent "actionLog for current user" actionLog: [ActionLog!] From 26aed28d8f820f7db7d2c0a5d9444c488b14fd34 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Thu, 19 Nov 2020 18:58:36 +0100 Subject: [PATCH 04/28] src/schema/*: updated comments --- src/schema/type-defs.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index c6c9c51..ce0cf9d 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -57,7 +57,9 @@ export default gql` Does not refer to an extra table in the database. """ dimensionsAndLoad: DimensionsAndLoad! + "If offset or limit is not provided, both values are ignored" bikeEvents(offset: Int, limit: Int): [BikeEvent] + "If offset or limit is not provided, both values are ignored" equipment(offset: Int, limit: Int): [Equipment] "Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2" equipmentType: [EquipmentType] @@ -71,6 +73,7 @@ export default gql` lendingStation: LendingStation taxes: Taxes currentEngagements: [Engagement] + "If offset or limit is not provided, both values are ignored" engagement(offset: Int, limit: Int): [Engagement] timeFrames: [TimeFrame] isLocked: Boolean! @@ -962,40 +965,54 @@ export default gql` type Query { "Will (eventually) return all properties of cargo bike" cargoBikeById(id:ID!): CargoBike - "returns cargoBikes ordered by name ascending, relations are not loaded, use cargoBikeById instead" + "Returns cargoBikes ordered by name ascending. If offset or limit is not provided, both values are ignored." cargoBikes(offset: Int, limit: Int): [CargoBike!]! engagementById(id: ID!): Engagement + "If offset or limit is not provided, both values are ignored" engagements(offset: Int, limit: Int): [Engagement!]! engagementTypeById(id: ID!): EngagementType + "If offset or limit is not provided, both values are ignored" engagementTypes(offset: Int, limit: Int): [EngagementType!]! "equipment by id, will return null if id not found" equipmentById(id: ID!): Equipment + "If offset or limit is not provided, both values are ignored" equipment(offset: Int, limit: Int): [Equipment!]! equipmentTypeById(id: ID!): EquipmentType + "If offset or limit is not provided, both values are ignored" equipmentTypes(offset: Int, limit: Int): [EquipmentType!]! "return null if id not found" providerById(id:ID!): Provider - "Returns providers with pagination" + "Returns providers with pagination. If offset or limit is not provided, both values are ignored" providers(offset: Int, limit: Int): [Provider!]! "participant by id" participantById(id:ID!): Participant + "If offset or limit is not provided, both values are ignored" participants(offset: Int, limit: Int): [Participant!]! workshopTypeById(id: ID!): WorkshopType + "If offset or limit is not provided, both values are ignored" workshopTypes(offset: Int, limit: Int): [WorkshopType!]! workshopById(id: ID!): Workshop + "If offset or limit is not provided, both values are ignored" workshops(offset: Int, limit: Int): [Workshop!]! lendingStationById(id:ID!): LendingStation + "If offset or limit is not provided, both values are ignored" lendingStations(offset: Int, limit: Int): [LendingStation!]! organisationById(id: ID!): Organisation + "If offset or limit is not provided, both values are ignored" organisations(offset: Int, limit: Int): [Organisation!]! timeFrameById(id: ID!): TimeFrame + "If offset or limit is not provided, both values are ignored" timeFrames(offset: Int, limit: Int): [TimeFrame!]! contactInformationById(id: ID!): ContactInformation + "If offset or limit is not provided, both values are ignored" contactInformation(offset: Int, limit: Int): [ContactInformation!]! personById(id: ID!): Person + "If offset or limit is not provided, both values are ignored" persons(offset: Int, limit: Int): [Person!] + "If offset or limit is not provided, both values are ignored" bikeEventTypes(offset: Int, limit: Int): [BikeEventType!] bikeEventTypeByd(id: ID!): BikeEventType + "If offset or limit is not provided, both values are ignored" bikeEvents(offset: Int, limit: Int): [BikeEvent!]! bikeEventById(id:ID!): BikeEvent "actionLog for current user" From f80f619ed9ebba85d7e11ed886bd11c96e05b3f0 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Fri, 20 Nov 2020 14:50:04 +0100 Subject: [PATCH 05/28] src/*: code refactor and comments Some refactoring in the database utilitie functions. --- src/datasources/db/cargobikeAPI.ts | 16 +-- src/datasources/db/contactinformationAPI.ts | 10 +- src/datasources/db/lendingstationAPI.ts | 10 +- src/datasources/db/participantAPI.ts | 14 +- src/datasources/db/providerAPI.ts | 10 +- src/datasources/db/utils.ts | 144 ++++++++++++++++---- src/datasources/db/workshopAPI.ts | 10 +- 7 files changed, 149 insertions(+), 65 deletions(-) diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 4fbbc44..3b860ee 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -26,7 +26,7 @@ import { Equipment } from '../../model/Equipment'; import { Engagement } from '../../model/Engagement'; import { Provider } from '../../model/Provider'; import { TimeFrame } from '../../model/TimeFrame'; -import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; +import { ActionLogger, DBUtils, LockUtils } from './utils'; import { EquipmentType } from '../../model/EquipmentType'; import { BikeEventType } from '../../model/BikeEventType'; import { UserInputError } from 'apollo-server-express'; @@ -43,7 +43,7 @@ export class CargoBikeAPI extends DataSource { } async getCargoBikes (offset?: number, limit?: number) { - return await getAllEntity(this.connection, CargoBike, 'cb', offset, limit); + return await DBUtils.getAllEntity(this.connection, CargoBike, 'cb', offset, limit); } /** @@ -192,11 +192,11 @@ export class CargoBikeAPI extends DataSource { } async deleteBikeEventType (id: number, userId: number) { - return await deleteEntity(this.connection, BikeEventType, 'bet', id, userId); + return await DBUtils.deleteEntity(this.connection, BikeEventType, 'bet', id, userId); } async deleteBikeEvent (id: number, userId: number) { - return await deleteEntity(this.connection, BikeEvent, 'be', id, userId); + return await DBUtils.deleteEntity(this.connection, BikeEvent, 'be', id, userId); } async cargoBikeByEventId (id: number) { @@ -270,11 +270,11 @@ export class CargoBikeAPI extends DataSource { } async bikeEventTypes (offset?: number, limit?: number) { - return await getAllEntity(this.connection, BikeEventType, 'bet', offset, limit); + return await DBUtils.getAllEntity(this.connection, BikeEventType, 'bet', offset, limit); } async bikeEvents (offset?: number, limit?: number) { - return await getAllEntity(this.connection, BikeEvent, 'be', offset, limit); + return await DBUtils.getAllEntity(this.connection, BikeEvent, 'be', offset, limit); } async bikeEventTypeById (id: number) { @@ -408,7 +408,7 @@ export class CargoBikeAPI extends DataSource { } async deleteEquipment (id: number, userId: number) { - return await deleteEntity(this.connection, Equipment, 'e', id, userId); + return await DBUtils.deleteEntity(this.connection, Equipment, 'e', id, userId); } async getEquipment (offset: number, limit: number) { @@ -462,7 +462,7 @@ export class CargoBikeAPI extends DataSource { } async deleteEquipmentType (id:number, userId: number) { - return await deleteEntity(this.connection, EquipmentType, 'et', id, userId); + return await DBUtils.deleteEntity(this.connection, EquipmentType, 'et', id, userId); } async equipmentTypeById (id: number) { diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index a2312db..e3e9a28 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { ContactInformation } from '../../model/ContactInformation'; import { Person } from '../../model/Person'; -import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; +import { ActionLogger, DBUtils, LockUtils } from './utils'; import { GraphQLError } from 'graphql'; import { LendingStation } from '../../model/LendingStation'; @@ -33,7 +33,7 @@ export class ContactInformationAPI extends DataSource { } async contactInformation (offset?: number, limit?: number) { - return await getAllEntity(this.connection, ContactInformation, 'ci', offset, limit); + return await DBUtils.getAllEntity(this.connection, ContactInformation, 'ci', offset, limit); } async contactInformationById (id: number) { @@ -83,11 +83,11 @@ export class ContactInformationAPI extends DataSource { } async deletePerson (id: number, userId: number) { - return await deleteEntity(this.connection, Person, 'p', id, userId); + return await DBUtils.deleteEntity(this.connection, Person, 'p', id, userId); } async persons (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Person, 'p', offset, limit); + return await DBUtils.getAllEntity(this.connection, Person, 'p', offset, limit); } async personById (id: number) { @@ -161,7 +161,7 @@ export class ContactInformationAPI extends DataSource { } async deleteContactInformation (id: number, userId: number) { - return await deleteEntity(this.connection, ContactInformation, 'ci', id, userId); + return await DBUtils.deleteEntity(this.connection, ContactInformation, 'ci', id, userId); } async contactInformationByPersonId (id: number) { diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index 9b3e53f..97f2b6f 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -24,7 +24,7 @@ import { Connection, EntityManager, getConnection } from 'typeorm'; import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; import { TimeFrame } from '../../model/TimeFrame'; -import { ActionLogger, deleteEntity, genDateRange, getAllEntity, LockUtils } from './utils'; +import { ActionLogger, genDateRange, DBUtils, LockUtils } from './utils'; export class LendingStationAPI extends DataSource { connection : Connection @@ -45,7 +45,7 @@ export class LendingStationAPI extends DataSource { * get all lendingStations */ async lendingStations (offset?: number, limit?: number) { - return await getAllEntity(this.connection, LendingStation, 'ls', offset, limit); + return await DBUtils.getAllEntity(this.connection, LendingStation, 'ls', offset, limit); } /** @@ -74,7 +74,7 @@ export class LendingStationAPI extends DataSource { } async timeFrames (offset?: number, limit?: number) { - return await getAllEntity(this.connection, TimeFrame, 'tf', offset, limit); + return await DBUtils.getAllEntity(this.connection, TimeFrame, 'tf', offset, limit); } async timeFramesByCargoBikeId (id: number) { @@ -179,7 +179,7 @@ export class LendingStationAPI extends DataSource { } async deleteLendingStationById (id: number, userId: number) { - return await deleteEntity(this.connection, LendingStation, 'ls', id, userId); + return await DBUtils.deleteEntity(this.connection, LendingStation, 'ls', id, userId); } async createTimeFrame (timeFrame: any) { @@ -257,6 +257,6 @@ export class LendingStationAPI extends DataSource { } async deleteTimeFrame (id: number, userId: number) { - return await deleteEntity(this.connection, TimeFrame, 'tf', id, userId); + return await DBUtils.deleteEntity(this.connection, TimeFrame, 'tf', id, userId); } } diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index d492e55..d480f9e 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -23,7 +23,7 @@ import { ContactInformation } from '../../model/ContactInformation'; import { Engagement } from '../../model/Engagement'; import { Participant } from '../../model/Participant'; import { EngagementType } from '../../model/EngagementType'; -import { ActionLogger, deleteEntity, genDateRange, getAllEntity, LockUtils } from './utils'; +import { ActionLogger, DBUtils, genDateRange, LockUtils } from './utils'; import { UserInputError } from 'apollo-server-express'; import { GraphQLError } from 'graphql'; @@ -43,7 +43,7 @@ export class ParticipantAPI extends DataSource { } async getParticipants (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Participant, 'p', offset, limit); + return await DBUtils.getAllEntity(this.connection, Participant, 'p', offset, limit); } async participantByEngagementId (id: number) { @@ -110,7 +110,7 @@ export class ParticipantAPI extends DataSource { } async engagements (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Engagement, 'e', offset, limit); + return await DBUtils.getAllEntity(this.connection, Engagement, 'e', offset, limit); } async engagementById (id: number) { @@ -130,7 +130,7 @@ export class ParticipantAPI extends DataSource { } async engagementTypes (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Engagement, 'e', offset, limit); + return await DBUtils.getAllEntity(this.connection, Engagement, 'e', offset, limit); } async engagementTypeByEngagementId (id: number) { @@ -223,7 +223,7 @@ export class ParticipantAPI extends DataSource { } async deleteParticipant (id: number, userId: number) { - return await deleteEntity(this.connection, Participant, 'p', id, userId); + return await DBUtils.deleteEntity(this.connection, Participant, 'p', id, userId); } async createEngagement (engagement: any) { @@ -292,7 +292,7 @@ export class ParticipantAPI extends DataSource { } async deleteEngagement (id: number, userId: number) { - return await deleteEntity(this.connection, Engagement, 'e', id, userId); + return await DBUtils.deleteEntity(this.connection, Engagement, 'e', id, userId); } async createEngagementType (engagementType: any) { @@ -334,6 +334,6 @@ export class ParticipantAPI extends DataSource { } async deleteEngagementType (id: number, userId: number) { - return await deleteEntity(this.connection, EngagementType, 'et', id, userId); + return await DBUtils.deleteEntity(this.connection, EngagementType, 'et', id, userId); } } diff --git a/src/datasources/db/providerAPI.ts b/src/datasources/db/providerAPI.ts index dd9511c..56df367 100644 --- a/src/datasources/db/providerAPI.ts +++ b/src/datasources/db/providerAPI.ts @@ -24,7 +24,7 @@ import { Organisation } from '../../model/Organisation'; import { UserInputError } from 'apollo-server-express'; import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; -import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; +import { ActionLogger, DBUtils, LockUtils } from './utils'; import { GraphQLError } from 'graphql'; export class ProviderAPI extends DataSource { @@ -43,7 +43,7 @@ export class ProviderAPI extends DataSource { } async provider (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Provider, 'p', offset, limit); + return await DBUtils.getAllEntity(this.connection, Provider, 'p', offset, limit); } async providerByOrganisationId (id: number) { @@ -71,7 +71,7 @@ export class ProviderAPI extends DataSource { } async organisations (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Organisation, 'o', offset, limit); + return await DBUtils.getAllEntity(this.connection, Organisation, 'o', offset, limit); } async organisationById (id: number) { @@ -170,7 +170,7 @@ export class ProviderAPI extends DataSource { } async deleteProvider (id: number, userId: number) { - return await deleteEntity(this.connection, Provider, 'p', id, userId); + return await DBUtils.deleteEntity(this.connection, Provider, 'p', id, userId); } async createOrganisation (organisation: any) { @@ -213,6 +213,6 @@ export class ProviderAPI extends DataSource { } async deleteOrganisation (id: number, userId: number) { - return await deleteEntity(this.connection, Organisation, 'o', id, userId); + return await DBUtils.deleteEntity(this.connection, Organisation, 'o', id, userId); } } diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 43e3554..962297f 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -36,52 +36,93 @@ export function genDateRange (struct: any) { } /** - * Can be used in resolvers to specify if entry is locked by other user. + * Can be used in resolvers to specify, if entry is locked by other user. * Returns true if locked by other user. * @param parent - * @param dataSources - * @param req user request + * @param req */ export function isLocked (parent: any, { req }: { req: any }) { return req.userId !== parent.lockedBy && new Date() <= new Date(parent.lockedUntil); } +/** + * Can be used in resolvers to specify, if entry is locked by the current user. + * @param parent + * @param req + */ export function isLockedByMe (parent: any, { req }: { req: any }) { return req.userId === parent.lockedBy && new Date() <= new Date(parent.lockedUntil); } -export async function deleteEntity (connection: Connection, target: ObjectType, alias: string, id: number, userId: number): Promise { - return await connection.transaction(async (entityManger: EntityManager) => { - if (await LockUtils.isLocked(entityManger, target, alias, id, userId)) { - throw new UserInputError('Attempting to delete locked resource'); - } - await ActionLogger.log(entityManger, target, alias, { id: id }, userId, Actions.DELETE); - return await entityManger.getRepository(target) - .createQueryBuilder(alias) - .delete() - .where('id = :id', { id: id }) - .execute().then(value => value.affected === 1); - }); -} +/** + * Some utility functions for the database + */ +export class DBUtils { + /** + * Delete any instance of an entity that implements the Lockable interface. + * It must implement the interface, so it can be be ensured, that the instance is not locked by another user. + * @param connection + * @param target + * @param alias + * @param id + * @param userId + */ + static async deleteEntity (connection: Connection, target: ObjectType, alias: string, id: number, userId: number): Promise { + return await connection.transaction(async (entityManger: EntityManager) => { + if (await LockUtils.isLocked(entityManger, target, alias, id, userId)) { + throw new UserInputError('Attempting to delete locked resource'); + } + await ActionLogger.log(entityManger, target, alias, { id: id }, userId, Actions.DELETE); + return await entityManger.getRepository(target) + .createQueryBuilder(alias) + .delete() + .where('id = :id', { id: id }) + .execute().then(value => value.affected === 1); + }); + } -export async function getAllEntity (connection: Connection, target: ObjectType, alias: string, offset?: number, limit?: number) { - if (offset === null || limit === null) { - return await connection.getRepository(target) - .createQueryBuilder(alias) - .select() - .getMany(); - } else { - return await connection.getRepository(target) - .createQueryBuilder(alias) - .select() - .skip(offset) - .take(limit) - .getMany(); + /** + * Return all instances of the given entity called target. + * When offset or limit is not specified, both values are ignored. + * @param connection + * @param target + * @param alias + * @param offset + * @param limit + */ + static async getAllEntity (connection: Connection, target: ObjectType, alias: string, offset?: number, limit?: number) { + if (offset === null || limit === null) { + return await connection.getRepository(target) + .createQueryBuilder(alias) + .select() + .getMany(); + } else { + return await connection.getRepository(target) + .createQueryBuilder(alias) + .select() + .skip(offset) + .take(limit) + .getMany(); + } } } +/** + * Some static functions for the locking feature. + */ export class LockUtils { - static async findById (connection: Connection, target: ObjectType, alias: string, id: number): Promise { + /** + * A helper function to find an instance of any entity that implements Lockable. + * It will throw an error, if nothing is found. + * Using this function only makes sense to use in the context of locking because there is no point in locking + * an instance that does not exist. + * @param connection + * @param target + * @param alias + * @param id + * @private + */ + private static async findById (connection: Connection, target: ObjectType, alias: string, id: number): Promise { return await connection.getRepository(target) .createQueryBuilder(alias) .select() @@ -91,6 +132,17 @@ export class LockUtils { }); } + /** + * Lock an instance of an entity target that implements the Lockable interface and return that instance. + * If lock could not be set, it will still return the entity. + * If lock was set or not can be obtained by the field isLockedByMe in the graphql interface, + * or the the fields lockedBy and lockedUntil in the database. + * @param connection + * @param target + * @param alias + * @param id + * @param userId + */ static async lockEntity (connection: Connection, target: ObjectType, alias: string, id: number, userId: number): Promise { const lock = await connection.getRepository(target) .createQueryBuilder(alias) @@ -119,6 +171,17 @@ export class LockUtils { return await this.findById(connection, target, alias, id); } + /** + * Unlock an instance of an entity target that implements the Lockable interface and return that instance. + * If lock could not be unset, it will still return the entity. + * If lock was set or not can be obtained by the field isLockedByMe in the graphql interface, + * or the the fields lockedBy and lockedUntil in the database. + * @param connection + * @param target + * @param alias + * @param id + * @param userId + */ static async unlockEntity (connection: Connection, target: ObjectType, alias: string, id: number, userId: number): Promise { await connection.getRepository(target) .createQueryBuilder(alias) @@ -161,7 +224,18 @@ export class LockUtils { } } +/** + * Some utility function for the logging features. + */ export class ActionLogger { + /** + * Create array of strings, that can be used to select them form the database. + * If you want to avoid logging all old values, for an update, but only the ones that are updated, + * use this function. If updates are null, ['*'] will be returned. Use this for delete actions. + * @param updates + * @param alias + * @private + */ private static buildSelect (updates: any, alias: string) : string[] { // this hacky shit makes it possible to select subfields like the address or insurance data. Only one layer at the moment if (updates === null) { @@ -181,6 +255,16 @@ export class ActionLogger { return ret; } + /** + * Insert an entry in the log. The log ist just another entity in the database. + * You can only use this in a transaction. So you have to pass an entity manager. + * @param em + * @param target + * @param alias + * @param updates + * @param userId + * @param action + */ static async log (em: EntityManager, target: ObjectType, alias: string, updates: any, userId: number, action: Actions = Actions.UPDATE) { const oldValues = await em.getRepository(target).createQueryBuilder(alias) .select(this.buildSelect(updates, alias)) diff --git a/src/datasources/db/workshopAPI.ts b/src/datasources/db/workshopAPI.ts index 7c28cd2..c6ba9d8 100644 --- a/src/datasources/db/workshopAPI.ts +++ b/src/datasources/db/workshopAPI.ts @@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { WorkshopType } from '../../model/WorkshopType'; import { Workshop } from '../../model/Workshop'; -import { ActionLogger, deleteEntity, getAllEntity, LockUtils } from './utils'; +import { ActionLogger, DBUtils, LockUtils } from './utils'; import { UserInputError } from 'apollo-server-express'; import { GraphQLError } from 'graphql'; import { Participant } from '../../model/Participant'; @@ -73,7 +73,7 @@ export class WorkshopAPI extends DataSource { } async deleteWorkshop (id: number, userId: number) { - return await deleteEntity(this.connection, Workshop, 'w', id, userId); + return await DBUtils.deleteEntity(this.connection, Workshop, 'w', id, userId); } async createWorkshopType (workshopType: any) { @@ -115,7 +115,7 @@ export class WorkshopAPI extends DataSource { } async deleteWorkshopType (id: number, userId: number) { - return await deleteEntity(this.connection, WorkshopType, 'wt', id, userId); + return await DBUtils.deleteEntity(this.connection, WorkshopType, 'wt', id, userId); } async workshopTypeById (id: number) { @@ -127,7 +127,7 @@ export class WorkshopAPI extends DataSource { } async workshopTypes (offset?: number, limit?: number) { - return getAllEntity(this.connection, WorkshopType, 'wt', offset, limit); + return DBUtils.getAllEntity(this.connection, WorkshopType, 'wt', offset, limit); } async workshopById (id: number) { @@ -144,7 +144,7 @@ export class WorkshopAPI extends DataSource { * @param limit */ async workshops (offset?: number, limit?: number) { - return await getAllEntity(this.connection, Workshop, 'w', offset, limit); + return await DBUtils.getAllEntity(this.connection, Workshop, 'w', offset, limit); } async trainer1ByWorkshopId (id: number) { From c193a63785fa32b335880e6378d36e9760ffef00 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Mon, 23 Nov 2020 13:37:26 +0100 Subject: [PATCH 06/28] src/model*: unique fields added unique contraines to some fields. For Example, name of CargoBike, workshopType and the combination of name and firstName of Person. --- src/model/CargoBike.ts | 4 +++- src/model/EngagementType.ts | 4 +++- src/model/EquipmentType.ts | 4 +++- src/model/Organisation.ts | 4 +++- src/model/Person.ts | 3 ++- src/model/WorkshopType.ts | 4 +++- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index 2ea30b0..81fb218 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -183,7 +183,9 @@ export class CargoBike implements Lockable { }) group: Group; - @Column() + @Column({ + unique: true + }) name: string; @OneToMany(type => Equipment, equipment => equipment.cargoBikeId, { diff --git a/src/model/EngagementType.ts b/src/model/EngagementType.ts index 01469f2..cdd816e 100644 --- a/src/model/EngagementType.ts +++ b/src/model/EngagementType.ts @@ -26,7 +26,9 @@ export class EngagementType implements Lockable { @PrimaryGeneratedColumn() id: number; - @Column() + @Column({ + unique: true + }) name: string; @Column({ diff --git a/src/model/EquipmentType.ts b/src/model/EquipmentType.ts index 38647f6..f8fd0b7 100644 --- a/src/model/EquipmentType.ts +++ b/src/model/EquipmentType.ts @@ -25,7 +25,9 @@ export class EquipmentType implements Lockable { @PrimaryGeneratedColumn() id: number; - @Column() + @Column({ + unique: true + }) name: string; @Column({ diff --git a/src/model/Organisation.ts b/src/model/Organisation.ts index 279d47c..80857d5 100644 --- a/src/model/Organisation.ts +++ b/src/model/Organisation.ts @@ -28,7 +28,9 @@ export class Organisation implements Lockable { @PrimaryGeneratedColumn() id: number; - @Column() + @Column({ + unique: true + }) name: string; @OneToMany(type => LendingStation, lendingStation => lendingStation.organisationId) diff --git a/src/model/Person.ts b/src/model/Person.ts index 9d1fafa..e383969 100644 --- a/src/model/Person.ts +++ b/src/model/Person.ts @@ -18,10 +18,11 @@ This file is part of fLotte-API-Server. */ import { Lockable } from './CargoBike'; -import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; +import { Column, Entity, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { ContactInformation } from './ContactInformation'; @Entity() +@Unique(['firstName', 'name']) export class Person implements Lockable { @PrimaryGeneratedColumn() id: number; diff --git a/src/model/WorkshopType.ts b/src/model/WorkshopType.ts index 3322da2..40d922e 100644 --- a/src/model/WorkshopType.ts +++ b/src/model/WorkshopType.ts @@ -26,7 +26,9 @@ export class WorkshopType implements Lockable { @PrimaryGeneratedColumn() id: number; - @Column() + @Column({ + unique: true + }) name: string; @OneToMany(type => Workshop, workshop => workshop.workshopTypeId) From 9d1ba30e462ce84b44a73dbc0a772f3fb986b86c Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Tue, 24 Nov 2020 11:49:26 +0100 Subject: [PATCH 07/28] src/*: update equipment/type with cargoBike update Now it is possible to to update equipment and equipment type via the mutations carogBikeUpdate and cargoBikeCreate. --- src/datasources/db/cargobikeAPI.ts | 42 ++++++++++++++---------------- src/model/CargoBike.ts | 2 +- src/model/Equipment.ts | 6 +++-- src/resolvers/cargoBikeResolver.ts | 2 +- src/schema/type-defs.ts | 27 ++++++++++++++----- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 3b860ee..4f59e36 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -99,11 +99,10 @@ export class CargoBikeAPI extends DataSource { const keepLock = cargoBike?.keepLock; delete cargoBike.keepLock; delete cargoBike.lendingStationId; - let equipmentTypeIds: any = null; - if (cargoBike.equipmentTypeIds) { - equipmentTypeIds = cargoBike.equipmentTypeIds; - delete cargoBike.equipmentTypeIds; - } + const equipmentTypeIds = cargoBike?.equipmentTypeIds; + delete cargoBike?.equipmentTypeIds; + const equipmentIds = cargoBike?.equipmentIds; + delete cargoBike?.equipmentIds; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, CargoBike, 'cb', cargoBike.id, userId)) { throw new GraphQLError('CargoBike locked by other user'); @@ -119,7 +118,12 @@ export class CargoBikeAPI extends DataSource { .createQueryBuilder('cb') .relation(CargoBike, 'equipmentTypeIds') .of(cargoBike.id) - .addAndRemove(equipmentTypeIds, await this.equipmentTypeByCargoBikeId(cargoBike.id)); // TODO remove all existing relations + .addAndRemove(equipmentTypeIds, await this.equipmentTypeByCargoBikeId(cargoBike.id)); + equipmentIds && await entityManager.getRepository(CargoBike) + .createQueryBuilder('cb') + .relation(CargoBike, 'equipmentIds') + .of(cargoBike.id) + .addAndRemove(equipmentIds, await this.equipmentByCargoBikeId(cargoBike.id)); }); !keepLock && await LockUtils.unlockEntity(this.connection, CargoBike, 'cb', cargoBike.id, userId); return await this.findCargoBikeById(cargoBike.id); @@ -144,7 +148,7 @@ export class CargoBikeAPI extends DataSource { * created CargoBike and returns created bike with new ID * @param param0 cargoBike to be created */ - async createCargoBike ({ cargoBike }: { cargoBike: any }) { + async createCargoBike (cargoBike: any) { let inserts: any = {}; await this.connection.transaction(async (entityManager:any) => { inserts = await entityManager.getRepository(CargoBike) @@ -158,6 +162,11 @@ export class CargoBikeAPI extends DataSource { .relation(CargoBike, 'equipmentTypeIds') .of(inserts.identifiers[0].id) .add(cargoBike.equipmentTypeIds); + cargoBike?.equipmentIds && await entityManager.getRepository(CargoBike) + .createQueryBuilder('cb') + .relation(CargoBike, 'equipmentIds') + .of(inserts.identifiers[0].id) + .add(cargoBike.equipmentIds); }); inserts.generatedMaps[0].id = inserts?.identifiers[0].id; return inserts?.generatedMaps[0]; @@ -411,14 +420,8 @@ export class CargoBikeAPI extends DataSource { return await DBUtils.deleteEntity(this.connection, Equipment, 'e', id, userId); } - async getEquipment (offset: number, limit: number) { - return await this.connection.getRepository(Equipment) - .createQueryBuilder('equipment') - .leftJoinAndSelect('equipment.cargoBike', 'cargoBike') - .orderBy('title', 'ASC') - .offset(offset) - .limit(limit) - .getMany(); + async getEquipment (offset?: number, limit?: number) { + return await DBUtils.getAllEntity(this.connection, Equipment, 'e', offset, limit); } async createEquipmentType (equipmentType: any) { @@ -473,13 +476,8 @@ export class CargoBikeAPI extends DataSource { .getOne(); } - async equipmentTypes (offset: number, limit: number) { - return await this.connection.getRepository(EquipmentType) - .createQueryBuilder('et') - .select() - .skip(offset) - .take(limit) - .getMany(); + async equipmentTypes (offset?: number, limit?: number) { + return await DBUtils.getAllEntity(this.connection, EquipmentType, 'et', offset, limit); } async equipmentTypeByCargoBikeId (id: number) { diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index 81fb218..da58bb9 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -192,7 +192,7 @@ export class CargoBike implements Lockable { nullable: true, eager: true }) - equipment: Equipment[]; + equipmentIds: number[]; // Equipment that is not unique and is supposed to be selected out of a list e.g. drop down @ManyToMany(type => EquipmentType, equipmentType => equipmentType.cargoBikeIds) diff --git a/src/model/Equipment.ts b/src/model/Equipment.ts index 91a8b10..6c598f9 100644 --- a/src/model/Equipment.ts +++ b/src/model/Equipment.ts @@ -26,7 +26,9 @@ export class Equipment implements Lockable { @PrimaryGeneratedColumn() id: number; - @Column() + @Column({ + unique: true + }) serialNo: string; @Column() @@ -37,7 +39,7 @@ export class Equipment implements Lockable { }) description: string; - @ManyToOne(type => CargoBike, cargoBike => cargoBike.equipment, { + @ManyToOne(type => CargoBike, cargoBike => cargoBike.equipmentIds, { nullable: true }) @JoinColumn({ diff --git a/src/resolvers/cargoBikeResolver.ts b/src/resolvers/cargoBikeResolver.ts index 38ced74..386cc36 100644 --- a/src/resolvers/cargoBikeResolver.ts +++ b/src/resolvers/cargoBikeResolver.ts @@ -216,7 +216,7 @@ export default { Mutation: { createCargoBike: (_: any, { cargoBike }: { cargoBike: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteBike)) { - return dataSources.cargoBikeAPI.createCargoBike({ cargoBike }); + return dataSources.cargoBikeAPI.createCargoBike(cargoBike); } else { return new GraphQLError('Insufficient Permissions'); } diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index ce0cf9d..fe125e0 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -97,7 +97,6 @@ export default gql` numberOfChildren: Int! """ Safety is a custom type, that stores information about security features. - TODO: Should this be called Security? """ security: SecurityCreateInput! """ @@ -108,8 +107,16 @@ export default gql` Does not refer to an extra table in the database. """ dimensionsAndLoad: DimensionsAndLoadCreateInput! - "Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2" + """ + Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2 + When set to null or [], no relations will be added. + """ equipmentTypeIds: [ID] + """ + Refers to unique equipment + When set to null or [], no relations will be added. + """ + equipmentIds: [ID] "Sticker State" stickerBikeNameState: StickerBikeNameState note: String @@ -119,7 +126,7 @@ export default gql` } """ - if you want to add bike to a lending station, create a new timeFrame with to: Date = null + If you want to add bike to a lending station, create a new timeFrame with to: Date = null """ input CargoBikeUpdateInput { id: ID! @@ -133,7 +140,6 @@ export default gql` numberOfChildren: Int """ Safety is a custom type, that stores information about security features. - TODO: Should this be called Security? """ security: SecurityUpdateInput """ @@ -146,9 +152,18 @@ export default gql` dimensionsAndLoad: DimensionsAndLoadUpdateInput """ Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2 - If set, ols relations will be over written. Set [] to delete all + When set to null, field will be ignored. + When set to [], all relations will be deleted. + Else all realtions will be deleted and the specified relations will be added. """ equipmentTypeIds: [ID] + """ + Refers to unique equipment + When set to null, field will be ignored. + When set to [], all relations will be deleted. + Else all realtions will be deleted and the specified relations will be added. + """ + equipmentIds: [ID] "Sticker State" stickerBikeNameState: StickerBikeNameState note: String @@ -606,7 +621,7 @@ export default gql` } input EquipmentTypeCreateInput { - name: String + name: String! description: String } From ca96923d3c688e71c0431512308f8506c9b573b9 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Tue, 24 Nov 2020 12:02:42 +0100 Subject: [PATCH 08/28] src/schema/*: added comment --- src/schema/type-defs.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index fe125e0..7152e31 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -115,6 +115,7 @@ export default gql` """ Refers to unique equipment When set to null or [], no relations will be added. + When specified id is in a relation with another bike, this relation will be deleted. """ equipmentIds: [ID] "Sticker State" @@ -162,6 +163,7 @@ export default gql` When set to null, field will be ignored. When set to [], all relations will be deleted. Else all realtions will be deleted and the specified relations will be added. + When specified id is in a relation with another bike, this relation will be deleted. """ equipmentIds: [ID] "Sticker State" From 5011a29be86df606a5ea64fc9572e6ec3e622f08 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Wed, 25 Nov 2020 23:45:59 +0100 Subject: [PATCH 09/28] src: use range for dimensionAndLoad Changes in the schema: insead of _boxWidth_ etc. min and maxBoxWidth is used In the postgres db, this is saved as numrange. --- src/datasources/db/cargobikeAPI.ts | 7 ++-- src/datasources/db/lendingstationAPI.ts | 1 - src/datasources/db/utils.ts | 27 +++++++++++++++ src/model/CargoBike.ts | 31 ++++++++++------- src/resolvers/cargoBikeResolver.ts | 46 +++++++++++++++++++++++-- src/schema/type-defs.ts | 43 ++++++++++++++--------- 6 files changed, 120 insertions(+), 35 deletions(-) diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 4f59e36..ff47082 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -26,7 +26,7 @@ import { Equipment } from '../../model/Equipment'; import { Engagement } from '../../model/Engagement'; import { Provider } from '../../model/Provider'; import { TimeFrame } from '../../model/TimeFrame'; -import { ActionLogger, DBUtils, LockUtils } from './utils'; +import { ActionLogger, DBUtils, genBoxDimensions, LockUtils } from './utils'; import { EquipmentType } from '../../model/EquipmentType'; import { BikeEventType } from '../../model/BikeEventType'; import { UserInputError } from 'apollo-server-express'; @@ -98,11 +98,13 @@ export class CargoBikeAPI extends DataSource { async updateCargoBike (cargoBike: any, userId:number) { const keepLock = cargoBike?.keepLock; delete cargoBike.keepLock; - delete cargoBike.lendingStationId; const equipmentTypeIds = cargoBike?.equipmentTypeIds; delete cargoBike?.equipmentTypeIds; const equipmentIds = cargoBike?.equipmentIds; delete cargoBike?.equipmentIds; + // generate ranges for box dimensions + genBoxDimensions(cargoBike); + await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, CargoBike, 'cb', cargoBike.id, userId)) { throw new GraphQLError('CargoBike locked by other user'); @@ -150,6 +152,7 @@ export class CargoBikeAPI extends DataSource { */ async createCargoBike (cargoBike: any) { let inserts: any = {}; + genBoxDimensions(cargoBike); await this.connection.transaction(async (entityManager:any) => { inserts = await entityManager.getRepository(CargoBike) .createQueryBuilder('cb') diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index 97f2b6f..a0f84c6 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -19,7 +19,6 @@ This file is part of fLotte-API-Server. import { DataSource } from 'apollo-datasource'; import { UserInputError } from 'apollo-server-express'; -import { GraphQLError } from 'graphql'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 962297f..be8fbf1 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -35,6 +35,33 @@ export function genDateRange (struct: any) { delete struct.to; } +/** + * This function prepares the cargoBike struct, to be used in an update or create. + * It creates the numrange attributes than can be understood by postgres. + * @param from + * @param to + */ +function genNumRange (from: number, to: number) { + if (from === null || from === undefined) { + from = to; + } else if (to === null || to === undefined) { + to = from; + } + return from ? '[' + from + ',' + to + ']' : null; +} + +export function genBoxDimensions (cargoBike: any) { + cargoBike.dimensionsAndLoad.boxLengthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxLength, cargoBike.dimensionsAndLoad.maxBoxLength); + cargoBike.dimensionsAndLoad.boxWidthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxWidth, cargoBike.dimensionsAndLoad.maxBoxWidth); + cargoBike.dimensionsAndLoad.boxHeightRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxHeight, cargoBike.dimensionsAndLoad.maxBoxHeight); + // delete this so update cargo bike works + delete cargoBike.dimensionsAndLoad.minBoxLength; + delete cargoBike.dimensionsAndLoad.maxBoxLength; + delete cargoBike.dimensionsAndLoad.minBoxWidth; + delete cargoBike.dimensionsAndLoad.maxBoxWidth; + delete cargoBike.dimensionsAndLoad.minBoxHeight; + delete cargoBike.dimensionsAndLoad.maxBoxHeight; +} /** * Can be used in resolvers to specify, if entry is locked by other user. * Returns true if locked by other user. diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index da58bb9..4193a41 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -113,43 +113,50 @@ export class DimensionsAndLoad { lockable:boolean; @Column({ - type: 'decimal' + type: 'numrange', + nullable: true }) - boxLength: number; + boxLengthRange: string; @Column({ - type: 'decimal' + type: 'numrange', + nullable: true }) - boxWidth: number; + boxWidthRange: string; @Column({ - type: 'decimal' + type: 'numrange', + nullable: true }) - boxHeight: number; + boxHeightRange: string; @Column({ - type: 'decimal' + type: 'decimal', + nullable: true }) - maxWeightBox: number; + maxWeightBox: string; @Column({ - type: 'decimal' + type: 'decimal', + nullable: true }) maxWeightLuggageRack: number; @Column({ - type: 'decimal' + type: 'decimal', + nullable: true }) maxWeightTotal: number; @Column({ - type: 'decimal' + type: 'decimal', + nullable: true }) bikeLength: number; @Column({ nullable: true, - type: 'decimal' + type: 'numrange' }) bikeWidth: number; diff --git a/src/resolvers/cargoBikeResolver.ts b/src/resolvers/cargoBikeResolver.ts index 386cc36..502f181 100644 --- a/src/resolvers/cargoBikeResolver.ts +++ b/src/resolvers/cargoBikeResolver.ts @@ -137,8 +137,6 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), - isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }), timeFrames (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { if (req.permissions.includes(Permission.ReadTimeFrame)) { return dataSources.lendingStationAPI.timeFramesByCargoBikeId(parent.id); @@ -159,8 +157,50 @@ export default { } else { return new GraphQLError('Insufficient Permissions'); } + }, + isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), + isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) + }, + DimensionsAndLoad: { + minBoxLength: (parent: any) => { + if (!parent.boxLengthRange || parent.boxLengthRange === 'empty') { + return null; + } + return parent.boxLengthRange ? (parent.boxLengthRange as string).split(',')[0].replace('[', '') : null; + }, + maxBoxLength: (parent: any) => { + if (!parent.boxLengthRange || parent.boxLengthRange === 'empty') { + return null; + } + const str = (parent.boxLengthRange as string).split(',')[1].replace(']', ''); + return (str.length > 0) ? str : null; + }, + minBoxWidth: (parent: any) => { + if (!parent.boxWidthRange || parent.boxWidthRange === 'empty') { + return null; + } + return parent.boxWidthRange ? (parent.boxWidthRange as string).split(',')[0].replace('[', '') : null; + }, + maxBoxWidth: (parent: any) => { + if (!parent.boxWidthRange || parent.boxWidthRange === 'empty') { + return null; + } + const str = (parent.boxWidthRange as string).split(',')[1].replace(']', ''); + return (str.length > 0) ? str : null; + }, + minBoxHeight: (parent: any) => { + if (!parent.boxHeightRange || parent.boxHeightRange === 'empty') { + return null; + } + return parent.boxHeightRange ? (parent.boxHeightRange as string).split(',')[0].replace('[', '') : null; + }, + maxBoxHeight: (parent: any) => { + if (!parent.boxHeightRange || parent.boxHeightRange === 'empty') { + return null; + } + const str = (parent.boxHeightRange as string).split(',')[1].replace(']', ''); + return (str.length > 0) ? str : null; } - }, Equipment: { cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 7152e31..d92729d 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -259,13 +259,16 @@ export default gql` hasCoverBox: Boolean! "cover box can be locked" lockable: Boolean! - boxLength: Float! - boxWidth: Float! - boxHeight: Float! - maxWeightBox: Float! - maxWeightLuggageRack: Float! - maxWeightTotal: Float! - bikeLength: Float! + minBoxLength: Float + maxBoxLength: Float + minBoxWidth: Float + maxBoxWidth: Float + minBoxHeight: Float + maxBoxHeight: Float + maxWeightBox: Float + maxWeightLuggageRack: Float + maxWeightTotal: Float + bikeLength: Float bikeWidth: Float bikeHeight: Float bikeWeight: Float @@ -274,13 +277,16 @@ export default gql` input DimensionsAndLoadCreateInput { hasCoverBox: Boolean! lockable: Boolean! - boxLength: Float! - boxWidth: Float! - boxHeight: Float! - maxWeightBox: Float! - maxWeightLuggageRack: Float! - maxWeightTotal: Float! - bikeLength: Float! + minBoxLength: Float + maxBoxLength: Float + minBoxWidth: Float + maxBoxWidth: Float + minBoxHeight: Float + maxBoxHeight: Float + maxWeightBox: Float + maxWeightLuggageRack: Float + maxWeightTotal: Float + bikeLength: Float bikeWidth: Float bikeHeight: Float bikeWeight: Float @@ -289,9 +295,12 @@ export default gql` input DimensionsAndLoadUpdateInput { hasCoverBox: Boolean lockable: Boolean - boxLength: Float - boxWidth: Float - boxHeight: Float + minBoxLength: Float + maxBoxLength: Float + minBoxWidth: Float + maxBoxWidth: Float + minBoxHeight: Float + maxBoxHeight: Float maxWeightBox: Float maxWeightLuggageRack: Float maxWeightTotal: Float From d83533375fe66dede759fb28d30e022bfd907e92 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Wed, 25 Nov 2020 23:52:33 +0100 Subject: [PATCH 10/28] src/dataSource/ds/utils.ts: corrected comment --- src/datasources/db/utils.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index be8fbf1..995905c 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -36,7 +36,7 @@ export function genDateRange (struct: any) { } /** - * This function prepares the cargoBike struct, to be used in an update or create. + * This function helps prepare the cargoBike struct, to be used in an update or create. * It creates the numrange attributes than can be understood by postgres. * @param from * @param to @@ -50,6 +50,11 @@ function genNumRange (from: number, to: number) { return from ? '[' + from + ',' + to + ']' : null; } +/** + * This function prepares the cargoBike struct, to be used in an update or create. + * It creates the numrange attributes than can be understood by postgres. + * @param cargoBike + */ export function genBoxDimensions (cargoBike: any) { cargoBike.dimensionsAndLoad.boxLengthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxLength, cargoBike.dimensionsAndLoad.maxBoxLength); cargoBike.dimensionsAndLoad.boxWidthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxWidth, cargoBike.dimensionsAndLoad.maxBoxWidth); From 1edf15ca2402c76ff2b733a984934fc1b8a041c0 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Thu, 26 Nov 2020 10:47:53 +0100 Subject: [PATCH 11/28] src/model/cargoBike.ts: fixed data type of bikeWidth --- src/datasources/db/utils.ts | 1 + src/model/CargoBike.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 995905c..0f62e60 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -67,6 +67,7 @@ export function genBoxDimensions (cargoBike: any) { delete cargoBike.dimensionsAndLoad.minBoxHeight; delete cargoBike.dimensionsAndLoad.maxBoxHeight; } + /** * Can be used in resolvers to specify, if entry is locked by other user. * Returns true if locked by other user. diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index 4193a41..10b2d78 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -156,7 +156,7 @@ export class DimensionsAndLoad { @Column({ nullable: true, - type: 'numrange' + type: 'decimal' }) bikeWidth: number; From 2c8a5ea6b7b6c87563abcb77228ddfa52f5e18d3 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Fri, 27 Nov 2020 10:39:33 +0100 Subject: [PATCH 12/28] src/model + src/schema: removed mandatory fields --- src/model/CargoBike.ts | 20 +++++++--- src/model/ContactInformation.ts | 4 +- src/model/InsuranceData.ts | 24 +++++++++--- src/model/Taxes.ts | 4 +- src/schema/type-defs.ts | 66 ++++++++++++++++----------------- 5 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index 10b2d78..cbd1073 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -90,13 +90,19 @@ export class Security { adfcCoding: string; } export class TechnicalEquipment { - @Column() + @Column({ + nullable: true + }) bicycleShift: string; - @Column() + @Column({ + nullable: true + }) isEBike: boolean; - @Column() + @Column({ + nullable: true + }) hasLightSystem: boolean; @Column({ @@ -106,10 +112,14 @@ export class TechnicalEquipment { } export class DimensionsAndLoad { - @Column() + @Column({ + nullable: true + }) hasCoverBox: boolean; - @Column() + @Column({ + nullable: true + }) lockable:boolean; @Column({ diff --git a/src/model/ContactInformation.ts b/src/model/ContactInformation.ts index 0e80f27..64e17aa 100644 --- a/src/model/ContactInformation.ts +++ b/src/model/ContactInformation.ts @@ -42,9 +42,7 @@ export class ContactInformation implements Lockable { }) participantId: number; - @Column(type => { - return Address; - }) + @Column(type => { return Address; }) address: Address; @Column({ diff --git a/src/model/InsuranceData.ts b/src/model/InsuranceData.ts index ef7c0fd..27fe256 100644 --- a/src/model/InsuranceData.ts +++ b/src/model/InsuranceData.ts @@ -20,22 +20,34 @@ This file is part of fLotte-API-Server. import { Column } from 'typeorm'; export class InsuranceData { - @Column() + @Column({ + nullable: true + }) name: string; - @Column() + @Column({ + nullable: true + }) benefactor: string; - @Column() + @Column({ + nullable: true + }) billing: string; - @Column() + @Column({ + nullable: true + }) noPnP: string; - @Column() + @Column({ + nullable: true + }) maintenanceResponsible: string; - @Column() + @Column({ + nullable: true + }) maintenanceBenefactor: string; @Column({ diff --git a/src/model/Taxes.ts b/src/model/Taxes.ts index 3c0f033..449cf27 100644 --- a/src/model/Taxes.ts +++ b/src/model/Taxes.ts @@ -25,7 +25,9 @@ export enum OrganisationArea { ZB = 'ZB' } export class Taxes { - @Column() + @Column({ + nullable: true + }) costCenter: string; @Column({ diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index d92729d..557f3f8 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -56,7 +56,7 @@ export default gql` """ Does not refer to an extra table in the database. """ - dimensionsAndLoad: DimensionsAndLoad! + dimensionsAndLoad: DimensionsAndLoad "If offset or limit is not provided, both values are ignored" bikeEvents(offset: Int, limit: Int): [BikeEvent] "If offset or limit is not provided, both values are ignored" @@ -69,7 +69,7 @@ export default gql` provider: Provider "all participants currently engaged with the cargoBike" participants: [Participant] - insuranceData: InsuranceData! + insuranceData: InsuranceData lendingStation: LendingStation taxes: Taxes currentEngagements: [Engagement] @@ -102,11 +102,11 @@ export default gql` """ Does not refer to an extra table in the database. """ - technicalEquipment: TechnicalEquipmentCreateInput! + technicalEquipment: TechnicalEquipmentCreateInput """ Does not refer to an extra table in the database. """ - dimensionsAndLoad: DimensionsAndLoadCreateInput! + dimensionsAndLoad: DimensionsAndLoadCreateInput """ Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2 When set to null or [], no relations will be added. @@ -122,8 +122,8 @@ export default gql` stickerBikeNameState: StickerBikeNameState note: String providerId: ID - insuranceData: InsuranceDataCreateInput! - taxes: TaxesCreateInput! + insuranceData: InsuranceDataCreateInput + taxes: TaxesCreateInput } """ @@ -180,15 +180,15 @@ export default gql` """ Eventually, this field will become an enum or a separate data table and user can choose from a pool of insurance companies. """ - name: String! - benefactor: String! - billing: String! - noPnP: String! + name: String + benefactor: String + billing: String + noPnP: String "eg. Anbieter, flotte, eigenleistung" - maintenanceResponsible: String! - maintenanceBenefactor: String! + maintenanceResponsible: String + maintenanceBenefactor: String maintenanceAgreement: String - hasFixedRate: Boolean! + hasFixedRate: Boolean fixedRate: Float """ Projektzuschuss: @@ -206,15 +206,15 @@ export default gql` """ Eventually, this field will become an enum or a separate data table and user can choose from a pool of insurance companies. """ - name: String! - benefactor: String! - billing: String! - noPnP: String! + name: String + benefactor: String + billing: String + noPnP: String "eg. Anbieter, flotte, eigenleistung" - maintenanceResponsible: String! - maintenanceBenefactor: String! + maintenanceResponsible: String + maintenanceBenefactor: String maintenanceAgreement: String - hasFixedRate: Boolean! + hasFixedRate: Boolean fixedRate: Float """ Projektzuschuss: @@ -256,9 +256,9 @@ export default gql` "How are the dimensions and how much weight can handle a bike. This data is merged in the CargoBike table and the BikeModel table." type DimensionsAndLoad { - hasCoverBox: Boolean! + hasCoverBox: Boolean "cover box can be locked" - lockable: Boolean! + lockable: Boolean minBoxLength: Float maxBoxLength: Float minBoxWidth: Float @@ -275,8 +275,8 @@ export default gql` } input DimensionsAndLoadCreateInput { - hasCoverBox: Boolean! - lockable: Boolean! + hasCoverBox: Boolean + lockable: Boolean minBoxLength: Float maxBoxLength: Float minBoxWidth: Float @@ -316,16 +316,16 @@ export default gql` So no id needed for mutation. One Mutation for the CargoBike will be enough. """ type TechnicalEquipment { - bicycleShift: String! - isEBike: Boolean! - hasLightSystem: Boolean! + bicycleShift: String + isEBike: Boolean + hasLightSystem: Boolean specialFeatures: String } input TechnicalEquipmentCreateInput { - bicycleShift: String! - isEBike: Boolean! - hasLightSystem: Boolean! + bicycleShift: String + isEBike: Boolean + hasLightSystem: Boolean specialFeatures: String } @@ -563,12 +563,12 @@ export default gql` } type Taxes { - costCenter: String! + costCenter: String organisationArea: OrganisationArea } input TaxesCreateInput { - costCenter: String! + costCenter: String organisationArea: OrganisationArea } @@ -643,7 +643,7 @@ export default gql` keepLock: Boolean } - "An Event is a point in time, when the state of the bike somehow changed." + "An Event is a point in time concerning one cargo bike of an event type. For example a chain swap." type BikeEvent { id: ID! bikeEventType: BikeEventType! From 321dc72123e6eff0607b105382f601ff1bfe174d Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sat, 28 Nov 2020 20:29:28 +0100 Subject: [PATCH 13/28] src/model schema: added bike Status --- src/model/CargoBike.ts | 6 +++++- src/schema/type-defs.ts | 14 +++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index cbd1073..965651a 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -30,7 +30,6 @@ import { DeleteDateColumn } from 'typeorm'; import { Provider } from './Provider'; -import { Participant } from './Participant'; import { InsuranceData } from './InsuranceData'; import { TimeFrame } from './TimeFrame'; import { Taxes } from './Taxes'; @@ -205,6 +204,11 @@ export class CargoBike implements Lockable { }) name: string; + @Column({ + nullable: true + }) + state: string; + @OneToMany(type => Equipment, equipment => equipment.cargoBikeId, { nullable: true, eager: true diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 557f3f8..e5847cc 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -38,7 +38,8 @@ export default gql` id: ID! "see column A in info tabelle" group: Group - name: String + name: String! + state: BikeState modelName: String numberOfWheels: Int forCargo: Boolean @@ -83,6 +84,15 @@ export default gql` lockedUntil: Date } + """ + Status of the CargoBike. More fields will be added, or removed. + """ + enum BikeState { + ACTIVE + INACTIVE + INPREPARATION + } + """ if you want to add bike to a lending station, create a new timeFrame with to: Date = null """ @@ -90,6 +100,7 @@ export default gql` "see column A in info tabelle" group: Group! name: String! + state: BikeState modelName: String! numberOfWheels: Int! forCargo: Boolean! @@ -134,6 +145,7 @@ export default gql` "see column A in info tabelle" group: Group name: String + state: BikeState modelName: String numberOfWheels: Int forCargo: Boolean From 0754fd5f74e7b18bbffe1114c5b967bedec38f1a Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sat, 28 Nov 2020 21:56:09 +0100 Subject: [PATCH 14/28] src/*: use type daterange for timeframes Instead of to and from, no type dataRange is used. Type DateRange still has to and from. --- src/datasources/db/lendingstationAPI.ts | 5 +---- src/datasources/db/utils.ts | 15 ++++++------- src/resolvers/lendingStationResolvers.ts | 12 ++++++----- src/schema/type-defs.ts | 27 +++++++++++++++++------- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index a0f84c6..52c08ad 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -183,10 +183,7 @@ export class LendingStationAPI extends DataSource { async createTimeFrame (timeFrame: any) { return await this.connection.transaction(async (entityManager: EntityManager) => { - if (timeFrame.to === undefined) { - timeFrame.to = ''; - } - timeFrame.dateRange = '[' + timeFrame.from + ',' + timeFrame.to + ')'; + genDateRange(timeFrame); // checking for overlapping time frames const overlapping = await entityManager.getRepository(TimeFrame) .createQueryBuilder('timeframe') diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 0f62e60..69318dc 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -23,16 +23,15 @@ import { ActionLog, Actions } from '../../model/ActionLog'; import { UserInputError } from 'apollo-server-express'; export function genDateRange (struct: any) { - if (struct.to === undefined) { - struct.to = ''; - } - struct.dateRange = '[' + struct.from + ',' + struct.to + ')'; - if (struct.from === undefined) { + if (!struct.dateRange || !struct.dateRange.from) { delete struct.dateRange; + return; + } else if (!struct.dateRange?.to) { + struct.dateRange.to = ''; + } else if (struct.dateRange.to === struct.dateRange.from) { + throw new UserInputError('Date Range can not be empty, provide different dates.'); } - // delete these keys, so the struct can be used to update the engagement entity - delete struct.from; - delete struct.to; + struct.dateRange = '[' + struct.dateRange.from + ',' + struct.dateRange.to + ')'; } /** diff --git a/src/resolvers/lendingStationResolvers.ts b/src/resolvers/lendingStationResolvers.ts index eb26f62..8cc3467 100644 --- a/src/resolvers/lendingStationResolvers.ts +++ b/src/resolvers/lendingStationResolvers.ts @@ -104,14 +104,16 @@ export default { return parent.loanTimes ? parent.loanTimes : []; } }, - TimeFrame: { + DateRange: { from (parent: any) { - return (parent.dateRange as string).split(',')[0].replace('[', ''); + return (parent as string).split(',')[0].replace('[', ''); }, - to (parent: any) { - const str = (parent.dateRange as string).split(',')[1].replace(')', ''); + to (parent: string) { + const str = (parent as string).split(',')[1].replace(')', ''); return (str.length > 0) ? str : null; - }, + } + }, + TimeFrame: { cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { if (req.permissions.includes(Permission.ReadBike)) { return dataSources.cargoBikeAPI.cargoBikeByTimeFrameId(parent.id); diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index e5847cc..ff66e76 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -935,13 +935,26 @@ export default gql` loanTimes: [String!] } - "(dt. Zeitscheibe) When was a bike where" - type TimeFrame { - id: ID! - "format YYYY-MM-dd" + type DateRange{ from: Date! + "will be infinity of not omitted" + to: Date + } + + input DateRangeInput { "format YYYY-MM-dd" + from: Date! + """ + format YYYY-MM-dd + will be infinity of not omitted + """ to: Date + } + + "(dt. Zeitscheibe) When was a bike where" + type TimeFrame { + id: ID! + dateRange: DateRange! note: String lendingStation: LendingStation! cargoBike: CargoBike! @@ -953,8 +966,7 @@ export default gql` } input TimeFrameCreateInput { - from: Date! - to: Date + dateRange: DateRangeInput! note: String lendingStationId: ID! cargoBikeId: ID! @@ -962,8 +974,7 @@ export default gql` input TimeFrameUpdateInput { id: ID! - from: Date - to: Date + dateRange: DateRangeInput note: String lendingStationId: ID cargoBikeId: ID From b0fc6cabe39780c8b49e3f55e605d88a5f321551 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 29 Nov 2020 00:39:24 +0100 Subject: [PATCH 15/28] src/* : use numRange for box dimensions instead of minBoxWidth, etc, use range type NumRange --- src/datasources/db/utils.ts | 46 +++++++++++++++++++----------- src/resolvers/cargoBikeResolver.ts | 43 ++++------------------------ src/schema/type-defs.ts | 42 +++++++++++++++------------ 3 files changed, 59 insertions(+), 72 deletions(-) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 69318dc..443c9d3 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -40,13 +40,33 @@ export function genDateRange (struct: any) { * @param from * @param to */ -function genNumRange (from: number, to: number) { - if (from === null || from === undefined) { - from = to; - } else if (to === null || to === undefined) { - to = from; +function genNumRange (range: { min: number, max: number}) :string { + if (!range || (!range.max && !range.min)) { + return null; + } else if (range.min === null || range.min === undefined) { + range.min = range.max; + } else if (range.max === null || range.max === undefined) { + range.max = range.min; } - return from ? '[' + from + ',' + to + ']' : null; + if (range.min < 0) { + throw new UserInputError('Minimal value must be greater or equal to 0'); + } + return range.min ? '[' + range.min + ',' + range.max + ']' : null; +} + +export function minNumRange (parent: string) { + if (!parent || parent === 'empty') { + return null; + } + return (parent).split(',')[0].replace('[', ''); +} + +export function maxNumRange (parent: string) { + if (!parent || parent === 'empty') { + return null; + } + const str = (parent).split(',')[1].replace(']', ''); + return (str.length > 0) ? str : null; } /** @@ -55,16 +75,10 @@ function genNumRange (from: number, to: number) { * @param cargoBike */ export function genBoxDimensions (cargoBike: any) { - cargoBike.dimensionsAndLoad.boxLengthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxLength, cargoBike.dimensionsAndLoad.maxBoxLength); - cargoBike.dimensionsAndLoad.boxWidthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxWidth, cargoBike.dimensionsAndLoad.maxBoxWidth); - cargoBike.dimensionsAndLoad.boxHeightRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxHeight, cargoBike.dimensionsAndLoad.maxBoxHeight); - // delete this so update cargo bike works - delete cargoBike.dimensionsAndLoad.minBoxLength; - delete cargoBike.dimensionsAndLoad.maxBoxLength; - delete cargoBike.dimensionsAndLoad.minBoxWidth; - delete cargoBike.dimensionsAndLoad.maxBoxWidth; - delete cargoBike.dimensionsAndLoad.minBoxHeight; - delete cargoBike.dimensionsAndLoad.maxBoxHeight; + if (!cargoBike.dimensionsAndLoad) { return; } + cargoBike.dimensionsAndLoad.boxLengthRange = genNumRange(cargoBike.dimensionsAndLoad.boxLengthRange); + cargoBike.dimensionsAndLoad.boxWidthRange = genNumRange(cargoBike.dimensionsAndLoad.boxWidthRange); + cargoBike.dimensionsAndLoad.boxHeightRange = genNumRange(cargoBike.dimensionsAndLoad.boxHeightRange); } /** diff --git a/src/resolvers/cargoBikeResolver.ts b/src/resolvers/cargoBikeResolver.ts index 502f181..27542dd 100644 --- a/src/resolvers/cargoBikeResolver.ts +++ b/src/resolvers/cargoBikeResolver.ts @@ -161,45 +161,12 @@ export default { isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) }, - DimensionsAndLoad: { - minBoxLength: (parent: any) => { - if (!parent.boxLengthRange || parent.boxLengthRange === 'empty') { - return null; - } - return parent.boxLengthRange ? (parent.boxLengthRange as string).split(',')[0].replace('[', '') : null; - }, - maxBoxLength: (parent: any) => { - if (!parent.boxLengthRange || parent.boxLengthRange === 'empty') { - return null; - } - const str = (parent.boxLengthRange as string).split(',')[1].replace(']', ''); - return (str.length > 0) ? str : null; - }, - minBoxWidth: (parent: any) => { - if (!parent.boxWidthRange || parent.boxWidthRange === 'empty') { - return null; - } - return parent.boxWidthRange ? (parent.boxWidthRange as string).split(',')[0].replace('[', '') : null; + NumRange: { + min: (parent: string) => { + return parent.split(',')[0].replace('[', ''); }, - maxBoxWidth: (parent: any) => { - if (!parent.boxWidthRange || parent.boxWidthRange === 'empty') { - return null; - } - const str = (parent.boxWidthRange as string).split(',')[1].replace(']', ''); - return (str.length > 0) ? str : null; - }, - minBoxHeight: (parent: any) => { - if (!parent.boxHeightRange || parent.boxHeightRange === 'empty') { - return null; - } - return parent.boxHeightRange ? (parent.boxHeightRange as string).split(',')[0].replace('[', '') : null; - }, - maxBoxHeight: (parent: any) => { - if (!parent.boxHeightRange || parent.boxHeightRange === 'empty') { - return null; - } - const str = (parent.boxHeightRange as string).split(',')[1].replace(']', ''); - return (str.length > 0) ? str : null; + max: (parent: string) => { + return parent.split(',')[1].replace(']', ''); } }, Equipment: { diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index ff66e76..af19618 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -33,6 +33,7 @@ export default gql` The kind of currency depends on the database. """ scalar Money + "The CargoBike type is central to the graph. You could call it the root." type CargoBike { id: ID! @@ -266,17 +267,28 @@ export default gql` notes: String } + type NumRange { + min: Float + max: Float + } + + """ + If min or max is omitted, the omitted value will be the same as the other given value + So if you pass one as null, both values with be over written with null. + """ + input NumRangeInput { + min: Float + max: Float + } + "How are the dimensions and how much weight can handle a bike. This data is merged in the CargoBike table and the BikeModel table." type DimensionsAndLoad { hasCoverBox: Boolean "cover box can be locked" lockable: Boolean - minBoxLength: Float - maxBoxLength: Float - minBoxWidth: Float - maxBoxWidth: Float - minBoxHeight: Float - maxBoxHeight: Float + boxLengthRange: NumRange + boxWidthRange: NumRange + boxHeightRange: NumRange maxWeightBox: Float maxWeightLuggageRack: Float maxWeightTotal: Float @@ -289,12 +301,9 @@ export default gql` input DimensionsAndLoadCreateInput { hasCoverBox: Boolean lockable: Boolean - minBoxLength: Float - maxBoxLength: Float - minBoxWidth: Float - maxBoxWidth: Float - minBoxHeight: Float - maxBoxHeight: Float + boxLengthRange: NumRangeInput + boxWidthRange: NumRangeInput + boxHeightRange: NumRangeInput maxWeightBox: Float maxWeightLuggageRack: Float maxWeightTotal: Float @@ -307,12 +316,9 @@ export default gql` input DimensionsAndLoadUpdateInput { hasCoverBox: Boolean lockable: Boolean - minBoxLength: Float - maxBoxLength: Float - minBoxWidth: Float - maxBoxWidth: Float - minBoxHeight: Float - maxBoxHeight: Float + boxLengthRange: NumRangeInput + boxWidthRange: NumRangeInput + boxHeightRange: NumRangeInput maxWeightBox: Float maxWeightLuggageRack: Float maxWeightTotal: Float From f3acf1e3d8eb5c9bc0a9bd9b0f109541959b9c26 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 29 Nov 2020 00:51:05 +0100 Subject: [PATCH 16/28] src/datasources/db/utils.ts: clean up clean some unused functions --- src/datasources/db/utils.ts | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 443c9d3..4e9d38b 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -5,7 +5,20 @@ This file is part of fLotte-API-Server. fLotte-API-Server is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or + the Free Soexport function minNumRange (parent: string) { + if (!parent || parent === 'empty') { + return null; + } + return (parent).split(',')[0].replace('[', ''); +} + +export function maxNumRange (parent: string) { + if (!parent || parent === 'empty') { + return null; + } + const str = (parent).split(',')[1].replace(']', ''); + return (str.length > 0) ? str : null; +}ftware Foundation, either version 3 of the License, or (at your option) any later version. fLotte-API-Server is distributed in the hope that it will be useful, @@ -54,21 +67,6 @@ function genNumRange (range: { min: number, max: number}) :string { return range.min ? '[' + range.min + ',' + range.max + ']' : null; } -export function minNumRange (parent: string) { - if (!parent || parent === 'empty') { - return null; - } - return (parent).split(',')[0].replace('[', ''); -} - -export function maxNumRange (parent: string) { - if (!parent || parent === 'empty') { - return null; - } - const str = (parent).split(',')[1].replace(']', ''); - return (str.length > 0) ? str : null; -} - /** * This function prepares the cargoBike struct, to be used in an update or create. * It creates the numrange attributes than can be understood by postgres. From 9f032ab0a109bbe3e291f2560218697005cc2d4a Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 29 Nov 2020 00:57:42 +0100 Subject: [PATCH 17/28] src/datasource/db/utils.ts: code clean up --- src/datasources/db/utils.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 4e9d38b..8e6bd96 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -5,20 +5,7 @@ This file is part of fLotte-API-Server. fLotte-API-Server is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Soexport function minNumRange (parent: string) { - if (!parent || parent === 'empty') { - return null; - } - return (parent).split(',')[0].replace('[', ''); -} - -export function maxNumRange (parent: string) { - if (!parent || parent === 'empty') { - return null; - } - const str = (parent).split(',')[1].replace(']', ''); - return (str.length > 0) ? str : null; -}ftware Foundation, either version 3 of the License, or + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. fLotte-API-Server is distributed in the hope that it will be useful, From 4a89b758de931dea02b794576d489353e879bc39 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 29 Nov 2020 13:56:06 +0100 Subject: [PATCH 18/28] src/dataSource/db/utils.ts: use template strings --- src/datasources/db/utils.ts | 11 +++++------ src/resolvers/lendingStationResolvers.ts | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 8e6bd96..7262417 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -31,14 +31,13 @@ export function genDateRange (struct: any) { } else if (struct.dateRange.to === struct.dateRange.from) { throw new UserInputError('Date Range can not be empty, provide different dates.'); } - struct.dateRange = '[' + struct.dateRange.from + ',' + struct.dateRange.to + ')'; + struct.dateRange = `[${struct.dateRange.from},${struct.dateRange.to})`; } /** * This function helps prepare the cargoBike struct, to be used in an update or create. * It creates the numrange attributes than can be understood by postgres. - * @param from - * @param to + * @param range */ function genNumRange (range: { min: number, max: number}) :string { if (!range || (!range.max && !range.min)) { @@ -51,7 +50,7 @@ function genNumRange (range: { min: number, max: number}) :string { if (range.min < 0) { throw new UserInputError('Minimal value must be greater or equal to 0'); } - return range.min ? '[' + range.min + ',' + range.max + ']' : null; + return `[${range.min},${range.max}]`; } /** @@ -277,10 +276,10 @@ export class ActionLogger { // sometimes updates[value] is an array, e.g. timePeriods that are saved as a simple array in postgres if (updates[value] && typeof updates[value] === 'object' && !Array.isArray(updates[value])) { Object.keys(updates[value]).forEach(subValue => { - ret.push(alias + '."' + value + subValue[0].toUpperCase() + subValue.substr(1).toLowerCase() + '"'); + ret.push(`${alias}."${value}${subValue[0].toUpperCase()}${subValue.substr(1).toLowerCase()}"`); }); } else { - ret.push(alias + '."' + value + '"'); + ret.push(`${alias}."${value}"`); } }); return ret; diff --git a/src/resolvers/lendingStationResolvers.ts b/src/resolvers/lendingStationResolvers.ts index 8cc3467..f17d87f 100644 --- a/src/resolvers/lendingStationResolvers.ts +++ b/src/resolvers/lendingStationResolvers.ts @@ -105,11 +105,11 @@ export default { } }, DateRange: { - from (parent: any) { - return (parent as string).split(',')[0].replace('[', ''); + from (parent: string) { + return parent.split(',')[0].replace('[', ''); }, to (parent: string) { - const str = (parent as string).split(',')[1].replace(')', ''); + const str = parent.split(',')[1].replace(')', ''); return (str.length > 0) ? str : null; } }, From 8dfa429e227f543d85c2f74656e0bdda6b2cd799 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 29 Nov 2020 16:14:40 +0100 Subject: [PATCH 19/28] src/resolvers/*: use reg ex --- src/resolvers/cargoBikeResolver.ts | 4 ++-- src/resolvers/lendingStationResolvers.ts | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/resolvers/cargoBikeResolver.ts b/src/resolvers/cargoBikeResolver.ts index 27542dd..b90b372 100644 --- a/src/resolvers/cargoBikeResolver.ts +++ b/src/resolvers/cargoBikeResolver.ts @@ -163,10 +163,10 @@ export default { }, NumRange: { min: (parent: string) => { - return parent.split(',')[0].replace('[', ''); + return parent.replace(/^\[(.*),.*]$/, '$1'); }, max: (parent: string) => { - return parent.split(',')[1].replace(']', ''); + return parent.replace(/^\[.*,(.*)]$/, '$1'); } }, Equipment: { diff --git a/src/resolvers/lendingStationResolvers.ts b/src/resolvers/lendingStationResolvers.ts index f17d87f..91d25bb 100644 --- a/src/resolvers/lendingStationResolvers.ts +++ b/src/resolvers/lendingStationResolvers.ts @@ -106,11 +106,10 @@ export default { }, DateRange: { from (parent: string) { - return parent.split(',')[0].replace('[', ''); + return parent.replace(/^\[(.*),.*\)$/, '$1'); }, to (parent: string) { - const str = parent.split(',')[1].replace(')', ''); - return (str.length > 0) ? str : null; + return parent.replace(/^\[.*,(.*)\)$/, '$1'); } }, TimeFrame: { From fadf128fa5bb0012b7c9b879571e07e311642404 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Sun, 29 Nov 2020 16:37:31 +0100 Subject: [PATCH 20/28] README.md: update --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6a46ead..682dd5f 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,16 @@ Apollo server written in typescript that handles business logic. [![Build Status](https://travis-ci.com/fLotte-meets-HWR-DB/apollo-server.svg?token=YfRmpHAXqyUafCgSEexw&branch=main)](https://travis-ci.com/fLotte-meets-HWR-DB/apollo-server) ## Assumptions -Userserver and postgres are running e.g. with Julius' Docker Compose. +The [flotte-user-management server](https://github.com/fLotte-meets-HWR-DB/flotte-user-management) and postgres are running. Set the [environment variables](#Environment-Variables) accordingly. ## Usage ### Docker +You can build and run a docker image with ```bash docker build -t . -docker run --rm -p 4000:4000 +docker run --rm -p 4000:4000 -e ... ``` -### Compile and run +Don't forget to pass all the [environment variables](#Environment-Variables) with the -e option. +### Compile and Run Install gulp if not installed ```bash npm -g gulp @@ -21,25 +23,28 @@ npm install gulp npm start ``` +You can set the [environment variables](#Environment-Variables) in a _.env_ file. ### For Development Install node\_modules and gulp ```bash npm -g gulp npm install ``` -And start gulp in watch mode +Start gulp in watch mode to recompile the type script ```bash -gulp watch +gulp watchTs ``` -This will watch *.ts files in _./src_ and recompile to _./dist_ and finally restart the server. +This will watch *.ts files in _./src_ and recompile to _./dist_. You will have to restart the server yourself. ## Environment Variables The following environment variables can be used to configure the server: ```bash RPC_HOST=host:port -NODE_ENV=development/porduction +NODE_ENV=develop/production POSTGRES_CONNECTION_URL=postgres://username:password@host:port/database_name ``` -- __RPC_HOST__ is used for the connection with the userserver. +- __RPC_HOST__ is used for the connection with the [flotte-user-management server](https://github.com/fLotte-meets-HWR-DB/flotte-user-management). - __NODE_ENV__ will not check authentication if set to development - __POSTGRES_CONNECTION_URL__ for connection with the postgres database + +If the API server cannot connect to the [flotte-user-management server](https://github.com/fLotte-meets-HWR-DB/flotte-user-management) or the postgres data base. It will try to reconnect in an endless loop. From 4e33ec2095bfd7db6dfff5aa25d04bc0be3b61d6 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Mon, 30 Nov 2020 14:23:38 +0100 Subject: [PATCH 21/28] src/schema/*: remove mandatory for fields --- src/model/CargoBike.ts | 31 ++++++++++++++++++++++--------- src/schema/type-defs.ts | 20 ++++++++++---------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index 965651a..ccf214c 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -65,7 +65,9 @@ export interface Lockable { } export class Security { - @Column() + @Column({ + nullable: true + }) frameNumber: string; @Column({ @@ -190,9 +192,6 @@ export class CargoBike implements Lockable { @PrimaryGeneratedColumn() id: number; - @DeleteDateColumn() - deleteDate: Date; - @Column({ type: 'enum', enum: Group @@ -268,19 +267,32 @@ export class CargoBike implements Lockable { }) description: string; - @Column() + @Column({ + nullable: true + }) modelName: string; - @Column() + @Column({ + nullable: true + }) numberOfWheels: number; - @Column() + @Column({ + type: 'boolean', + nullable: true + }) forCargo: boolean; - @Column() + @Column({ + type: 'boolean', + nullable: true + }) forChildren: boolean; - @Column() + @Column({ + type: 'int', + nullable: true + }) numberOfChildren: number; @Column(type => TechnicalEquipment) @@ -290,6 +302,7 @@ export class CargoBike implements Lockable { dimensionsAndLoad: DimensionsAndLoad; @Column({ + type: 'int', nullable: true }) lockedBy: number; diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index af19618..afc7d7f 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -45,12 +45,12 @@ export default gql` numberOfWheels: Int forCargo: Boolean forChildren: Boolean - numberOfChildren: Int! + numberOfChildren: Int """ Safety is a custom type, that stores information about security features. TODO: Should this be called Security? """ - security: Security! + security: Security """ Does not refer to an extra table in the database. """ @@ -102,15 +102,15 @@ export default gql` group: Group! name: String! state: BikeState - modelName: String! - numberOfWheels: Int! - forCargo: Boolean! - forChildren: Boolean! - numberOfChildren: Int! + modelName: String + numberOfWheels: Int + forCargo: Boolean + forChildren: Boolean + numberOfChildren: Int """ Safety is a custom type, that stores information about security features. """ - security: SecurityCreateInput! + security: SecurityCreateInput """ Does not refer to an extra table in the database. """ @@ -360,7 +360,7 @@ export default gql` So no id needed for mutation. One Mutation for the CargoBike will be enough. """ type Security { - frameNumber: String! + frameNumber: String keyNumberFrameLock: String keyNumberAXAChain: String policeCoding: String @@ -368,7 +368,7 @@ export default gql` } input SecurityCreateInput { - frameNumber: String! + frameNumber: String keyNumberFrameLock: String keyNumberAXAChain: String policeCoding: String From 5e6e796fe8005c53527cb0c9ba15e295cfbfb62c Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Mon, 30 Nov 2020 14:44:29 +0100 Subject: [PATCH 22/28] src/schema/*: use dateRange for engagement --- src/datasources/db/participantAPI.ts | 6 +++--- src/resolvers/participantResolvers.ts | 7 ------- src/schema/type-defs.ts | 11 +++-------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index d480f9e..311ba26 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -179,7 +179,7 @@ export class ParticipantAPI extends DataSource { .values([participant]) .returning('*') .execute(); - await entityManager.getRepository(Participant) + participant.workshopIds && await entityManager.getRepository(Participant) .createQueryBuilder('w') .relation(Participant, 'workshopIds') .of(participant.id) @@ -211,8 +211,8 @@ export class ParticipantAPI extends DataSource { .update() .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) + .execute().then(value => { if (value.affected !== 1) { throw new UserInputError('ID not found'); } }); + workshops && await entityManager.getRepository(Participant) .createQueryBuilder('w') .relation(Participant, 'workshopIds') .of(participant.id) diff --git a/src/resolvers/participantResolvers.ts b/src/resolvers/participantResolvers.ts index ed058e1..89deb3e 100644 --- a/src/resolvers/participantResolvers.ts +++ b/src/resolvers/participantResolvers.ts @@ -113,13 +113,6 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - from (parent: any) { - return (parent.dateRange as string).split(',')[0].replace('[', ''); - }, - to (parent: any) { - const str = (parent.dateRange as string).split(',')[1].replace(')', ''); - return (str.length > 0) ? str : null; - }, isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) }, diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index afc7d7f..e72012c 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -550,8 +550,7 @@ export default gql` type Engagement { id: ID! engagementType: EngagementType! - from: Date! - to: Date + dateRange: DateRange! participant: Participant! cargoBike: CargoBike! isLocked: Boolean! @@ -563,18 +562,14 @@ export default gql` input EngagementCreateInput { engagementTypeId: ID! - "will use CURRENT_DATE if not set" - from: Date - "will use infinit if not set" - to: Date + dateRange: DateRangeInput participantId: ID! cargoBikeId: ID! } input EngagementUpdateInput { id: ID! engagementTypeId: ID - from: Date - to: Date + dateRange: DateRangeInput participantId: ID cargoBikeId: ID keepLock: Boolean From 48e6ed2be3336109a2f5238b334f3351af119265 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Wed, 2 Dec 2020 09:51:20 +0100 Subject: [PATCH 23/28] src/schema/*: fixed return type for unlock mutations --- src/schema/type-defs.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index e72012c..309f9df 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -1138,23 +1138,23 @@ export default gql` """ createParticipant(participant: ParticipantCreateInput!): Participant! lockParticipant(id: ID!): Participant! - unlockParticipant(id: ID!): Boolean + unlockParticipant(id: ID!): Participant updateParticipant(participant: ParticipantUpdateInput!): Participant! deleteParticipant(id: ID!): Boolean! createWorkshopType(workshopType: WorkshopTypeCreateInput!): WorkshopType! lockWorkshopType(id: ID!): WorkshopType! - unlockWorkshopType(id: ID!): Boolean! + unlockWorkshopType(id: ID!): WorkshopType! updateWorkshopType(workshopType: WorkshopTypeUpdateInput!): WorkshopType! deleteWorkshopType(id: ID!): Boolean! createWorkshop(workshop: WorkshopCreateInput!): Workshop! lockWorkshop(id: ID!): Workshop! - unlockWorkshop(id: ID!): Boolean! + unlockWorkshop(id: ID!): Workshop! updateWorkshop(workshop: WorkshopUpdateInput!): Workshop! deleteWorkshop(id: ID!): Boolean! "create new contactInfo" createContactInformation(contactInformation: ContactInformationCreateInput!): ContactInformation! lockContactInformation(id: ID!): ContactInformation! - unlockContactInformation(id: ID!): Boolean! + unlockContactInformation(id: ID!): ContactInformation! updateContactInformation(contactInformation: ContactInformationUpdateInput!): ContactInformation! deleteContactInformation(id: ID!): Boolean! createPerson(person: PersonCreateInput!): Person! @@ -1165,22 +1165,22 @@ export default gql` "create Engagement" createEngagement(engagement: EngagementCreateInput): Engagement! lockEngagement(id: ID!): Engagement! - unlockEngagement(id: ID!): Boolean! + unlockEngagement(id: ID!): Engagement! updateEngagement(engagement: EngagementUpdateInput!): Engagement! deleteEngagement(id: ID!): Boolean! createEngagementType(engagementType: EngagementTypeCreateInput!): EngagementType! lockEngagementType(id: ID!): EngagementType! - unlockEngagementType(id: ID!): Boolean! + unlockEngagementType(id: ID!): EngagementType! updateEngagementType(engagementType: EngagementTypeUpdateInput!): EngagementType! deleteEngagementType(id: ID!): Boolean! createProvider(provider: ProviderCreateInput!): Provider! lockProvider(id: ID!): Provider! - unlockProvider(id: ID!): Boolean! + unlockProvider(id: ID!): Provider! updateProvider(provider: ProviderUpdateInput!): Provider! deleteProvider(id: ID!): Boolean! createOrganisation(organisation: OrganisationCreateInput!): Organisation! lockOrganisation(id: ID!): Organisation! - unlockOrganisation(id: ID!): Boolean! + unlockOrganisation(id: ID!): Organisation! updateOrganisation(organisation: OrganisationUpdateInput!): Organisation! deleteOrganisation(id: ID!): Boolean! } From 49bc8bdcfcc1e14fe0b0fc000cca93131eed578b Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Wed, 2 Dec 2020 10:22:01 +0100 Subject: [PATCH 24/28] src/schema/*: explicit time for loanPeriod instead of an array, all day of the week are listed and their type is string for simplicity incase of several time frames in a day. --- src/model/LendingStation.ts | 33 +++++++++++- src/resolvers/lendingStationResolvers.ts | 5 -- src/schema/type-defs.ts | 64 ++++++++++++------------ 3 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/model/LendingStation.ts b/src/model/LendingStation.ts index 34dc79a..9c266de 100644 --- a/src/model/LendingStation.ts +++ b/src/model/LendingStation.ts @@ -44,10 +44,39 @@ export class LoanPeriod { to: Date; @Column({ - type: 'simple-array', nullable: true }) - loanTimes: string[]; + mo: string; + + @Column({ + nullable: true + }) + tu: string; + + @Column({ + nullable: true + }) + we: string; + + @Column({ + nullable: true + }) + th: string; + + @Column({ + nullable: true + }) + fr: string; + + @Column({ + nullable: true + }) + sa: string; + + @Column({ + nullable: true + }) + su: string; } @Entity() diff --git a/src/resolvers/lendingStationResolvers.ts b/src/resolvers/lendingStationResolvers.ts index 91d25bb..2a615c3 100644 --- a/src/resolvers/lendingStationResolvers.ts +++ b/src/resolvers/lendingStationResolvers.ts @@ -99,11 +99,6 @@ export default { isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) }, - LoanPeriod: { - loanTimes (parent: any) { - return parent.loanTimes ? parent.loanTimes : []; - } - }, DateRange: { from (parent: string) { return parent.replace(/^\[(.*),.*\)$/, '$1'); diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 309f9df..cbb0e5b 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -21,8 +21,10 @@ import { gql } from 'apollo-server-express'; export default gql` - "timestamp object YYYY-MM-ddThh:mm:ss.sssZ" + "date object YYYY-MM-dd" scalar Date + "timestamp object YYYY-MM-ddThh:mm:ss.sssZ" + scalar DateTime "only time hh-mm-ss" scalar Time """ @@ -82,7 +84,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } """ @@ -429,7 +431,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input ParticipantCreateInput { @@ -482,7 +484,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input WorkshopCreateInput { @@ -512,7 +514,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input WorkshopTypeCreateInput { @@ -532,7 +534,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input EngagementTypeCreateInput { @@ -557,7 +559,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input EngagementCreateInput { @@ -609,7 +611,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input EquipmentCreateInput { @@ -641,7 +643,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input EquipmentTypeCreateInput { @@ -674,7 +676,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input BikeEventCreateInput { @@ -714,7 +716,7 @@ export default gql` isLocked: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input BikeEventTypeUpdateInput { @@ -734,7 +736,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } "(dt. Anbieter)" @@ -768,7 +770,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input PersonCreateInput { @@ -795,7 +797,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input ContactInformationCreateInput { @@ -835,7 +837,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input OrganisationCreateInput { @@ -879,7 +881,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } """ @@ -913,13 +915,13 @@ export default gql` """ type LoanPeriod { generalRemark: String - "notes for each day of the week, starting on Monday" - notes: [String] - """ - Loan times from and until for each day of the week. - Starting with Monday from, Monday to, Tuesday from, ..., Sunday to - """ - loanTimes: [String] + mo: String + tu: String + we: String + th: String + fr: String + sa: String + su: String } """ @@ -927,13 +929,13 @@ export default gql` """ input LoanPeriodInput { generalRemark: String - "notes for each day of the week, starting on Monday" - notes: [String!] - """ - Loan times from and until for each day of the week. - Starting with Monday from, Monday to, Tuesday from, ..., Sunday to - """ - loanTimes: [String!] + mo: String + tu: String + we: String + th: String + fr: String + sa: String + su: String } type DateRange{ @@ -963,7 +965,7 @@ export default gql` isLockedByMe: Boolean! "null if not locked by other user" lockedBy: ID - lockedUntil: Date + lockedUntil: DateTime } input TimeFrameCreateInput { From 6162758638644f0830ec8eda7188725475f8ba48 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Thu, 3 Dec 2020 13:40:17 +0100 Subject: [PATCH 25/28] src/schema/*: cargoBike group required --- src/schema/type-defs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index cbb0e5b..e040362 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -40,7 +40,7 @@ export default gql` type CargoBike { id: ID! "see column A in info tabelle" - group: Group + group: Group! name: String! state: BikeState modelName: String From 8add40cd02ebbd1ea4d7f574b476d6bbd99ab343 Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Thu, 3 Dec 2020 15:32:45 +0100 Subject: [PATCH 26/28] check activity status for engagements when updating or creating engagements or participants dateRange of particpant must include dateRange of engagement. --- .gitignore | 1 + src/datasources/db/participantAPI.ts | 41 ++++++++++++++++++++++++++-- src/model/Participant.ts | 11 ++------ src/schema/type-defs.ts | 12 +++----- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 02551b4..80bc72c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules dist .env .idea +.dockerignore diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index 311ba26..270ec09 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -130,7 +130,7 @@ export class ParticipantAPI extends DataSource { } async engagementTypes (offset?: number, limit?: number) { - return await DBUtils.getAllEntity(this.connection, Engagement, 'e', offset, limit); + return await DBUtils.getAllEntity(this.connection, EngagementType, 'et', offset, limit); } async engagementTypeByEngagementId (id: number) { @@ -170,6 +170,7 @@ export class ParticipantAPI extends DataSource { * @param participant to be created */ async createParticipant (participant: any) { + genDateRange(participant); let inserts: any; await this.connection.transaction(async (entityManager: EntityManager) => { inserts = await entityManager.getRepository(Participant) @@ -201,8 +202,9 @@ export class ParticipantAPI extends DataSource { delete participant.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, Participant, 'p', participant.id, userId)) { - throw new GraphQLError('Participant is locked by another user'); + throw new UserInputError('Attempting to update locked resource'); } + genDateRange(participant); const workshops = participant.workshopIds; delete participant.workshopIds; await ActionLogger.log(entityManager, Participant, 'p', participant, userId); @@ -212,6 +214,17 @@ export class ParticipantAPI extends DataSource { .set({ ...participant }) .where('id = :id', { id: participant.id }) .execute().then(value => { if (value.affected !== 1) { throw new UserInputError('ID not found'); } }); + // check for engagements before or after dateRange + const engagements = await entityManager.getRepository(Engagement) + .createQueryBuilder('e') + .select() + .where('e."participantId" = :pid', { pid: participant.id }) + .andWhere('not :pdr @> e."dateRange"', { pdr: participant.dateRange }) + .getMany(); + if (engagements.length !== 0) { + throw new UserInputError('Engagements with ids: ' + engagements.map((e) => { return `${e.id} ,`; }) + ' are are outside of dataRange'); + } + // add and remove workshop relations workshops && await entityManager.getRepository(Participant) .createQueryBuilder('w') .relation(Participant, 'workshopIds') @@ -241,6 +254,16 @@ export class ParticipantAPI extends DataSource { if (overlapping.length > 0) { throw new UserInputError('Engagements with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping'); } + // check if participant is active + const participant = await entityManager.getRepository(Participant) + .createQueryBuilder('p') + .select() + .where('p.id = :pid', { pid: engagement.participantId }) + .andWhere('not p."dateRange" @> :edr', { edr: engagement.dateRange }) + .getOne(); + if (participant) { + throw new UserInputError('Participant ist not active in the specified dateRange'); + } inserts = await entityManager.getRepository(Engagement) .createQueryBuilder('engagement') .insert() @@ -280,6 +303,20 @@ export class ParticipantAPI extends DataSource { if (overlapping.length > 0) { throw new UserInputError('Engagements with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping'); } + // check if participant is active + if (engagement.dateRange && engagement.participantId) { + const participant = await entityManager.getRepository(Participant) + .createQueryBuilder('p') + .select() + .where('p.id = :pid', { pid: engagement.participantId }) + .andWhere('not p."dateRange" @> :edr', { edr: engagement.dateRange }) + .getOne(); + if (participant) { + throw new UserInputError('Participant ist not active in the specified dateRange'); + } + } else if (engagement.dateRange || engagement.dateRange) { + throw new UserInputError('Please specify participantId adn the dateRange'); + } await entityManager.getRepository(Engagement) .createQueryBuilder('engagement') .update() diff --git a/src/model/Participant.ts b/src/model/Participant.ts index dedf9b8..d623143 100644 --- a/src/model/Participant.ts +++ b/src/model/Participant.ts @@ -29,16 +29,9 @@ export class Participant implements Lockable { id: number; @Column({ - type: 'date', - default: () => 'CURRENT_DATE' + type: 'daterange' }) - start: Date; - - @Column({ - type: 'date', - nullable: true - }) - end: Date; + dateRange: Date[]; @OneToOne(type => ContactInformation, contactInformation => contactInformation.participantId, { nullable: false diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index e040362..b55e6a8 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -410,8 +410,7 @@ export default gql` """ type Participant { id: ID! - start: Date! - end: Date + dateRange: DateRange! contactInformation: ContactInformation! usernamefLotte: String usernameSlack: String @@ -436,8 +435,7 @@ export default gql` input ParticipantCreateInput { "if not set, CURRENT_DATE will be used" - start: Date - end: Date + dateRange: DateRangeInput! "must create contactinformation first, if you want to use new" contactInformationId: ID! usernamefLotte: String @@ -452,9 +450,7 @@ export default gql` input ParticipantUpdateInput { id: ID! - "if not set, CURRENT_DATE will be used" - start: Date - end: Date + dateRange: DateRangeInput "must create contactinformation first, if you want to use new" contactInformationId: ID usernamefLotte: String @@ -564,7 +560,7 @@ export default gql` input EngagementCreateInput { engagementTypeId: ID! - dateRange: DateRangeInput + dateRange: DateRangeInput! participantId: ID! cargoBikeId: ID! } From fb0ce305f6ec8c385d978346b07f2ce307b3988e Mon Sep 17 00:00:00 2001 From: leonnicolas <60091705+leonnicolas@users.noreply.github.com> Date: Thu, 3 Dec 2020 21:09:31 +0100 Subject: [PATCH 27/28] Create docker.yml --- .github/workflows/docker.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..6c3670c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,37 @@ + +name: Build Docker Image + +on: + push: + branches: [ main, dev ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + + - name: Copy Repo Files + uses: actions/checkout@v2 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + + - name: Login to Portus + uses: docker/login-action@v1 + with: + registry: https://flotte-docker-registry.spdns.org/ + username: ${{ secrets.PORTUS_USERNAME }} + password: ${{ secrets.PORTUS_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: flotte-docker-registry.spdns.org/apollo-server:latest From ad3ddf3218e609f0a82be887dc65491bf116815c Mon Sep 17 00:00:00 2001 From: leonnicolas Date: Fri, 4 Dec 2020 09:34:18 +0100 Subject: [PATCH 28/28] src/dataSource/db/lendingStation.ts: add generalRemark --- src/model/LendingStation.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/model/LendingStation.ts b/src/model/LendingStation.ts index 9c266de..9df1441 100644 --- a/src/model/LendingStation.ts +++ b/src/model/LendingStation.ts @@ -34,6 +34,12 @@ export class LoanPeriod { }) from: Date; + @Column({ + nullable: true, + type: 'text' + }) + generalRemark: string; + /** * validity for loanPeriods */