diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index b800a28..29a025f 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -163,6 +163,10 @@ export class CargoBikeAPI extends DataSource { * @param param0 cargoBike to be updated */ async updateCargoBike (cargoBike: any, req: any, dataSources: any) { + // TODO let lock cargoBike can return error to save one sql query, this will be a complex sql query + if (!await this.checkId(CargoBike, 'cargobike', cargoBike.id)) { + return new GraphQLError('ID not found'); + } if (!await this.lockCargoBike(cargoBike.id, req, dataSources)) { return new GraphQLError('Bike locked by other user'); } @@ -244,6 +248,17 @@ export class CargoBikeAPI extends DataSource { .getOne(); } + async checkId (target: ObjectType, alias: string, id: number) { + const result = await this.connection.getRepository(target) + .createQueryBuilder(alias) + .select([ + alias + '.id' + ]) + .where('id = :id', { id: id }) + .getCount(); + return result === 1; + } + // think this can go async findEquipmentJoinBikeById (id: number) { return await this.connection.getRepository(Equipment) @@ -289,7 +304,15 @@ export class CargoBikeAPI extends DataSource { } async lockEquipment (id: number, req: any, dataSources: any) { - return this.lockEntity(Equipment, 'equipment', id, req, dataSources); + if (await this.lockEntity(Equipment, 'equipment', id, req, dataSources)) { + return this.findEquipmentById(id); + } else { + return new GraphQLError('Equipment locked by other user'); + } + } + + async unlockEquipment (id: number, req: any, dataSources: any) { + return await this.unlockEntity(Equipment, 'equipment', id, req, dataSources); } /** @@ -298,9 +321,15 @@ export class CargoBikeAPI extends DataSource { * @param param0 struct with equipment properites */ async updateEquipment (equipment: any, req: any, dataSources: any) { + // TODO let lock cargoBike can return error to save one sql query, this will be a complex sql query + if (!await this.checkId(Equipment, 'alias', equipment.id)) { + return new GraphQLError('ID not found in DB'); + } if (!await this.lockEntity(Equipment, 'equipment', equipment.id, req, dataSources)) { return new GraphQLError('Equipment locked by other user'); } + const keepLock = equipment.keepLock; + delete equipment.keepLock; const cargoBikeId = equipment.cargoBikeId; delete equipment.cargoBikeId; await this.connection.getRepository(Equipment) @@ -316,7 +345,8 @@ export class CargoBikeAPI extends DataSource { .relation(Equipment, 'cargoBike') .of(equipment.id) .set(cargoBikeId); - return this.findEquipmentJoinBikeById(equipment.id); + !keepLock && this.unlockCargoBike(equipment.id, req, dataSources); + return this.findEquipmentById(equipment.id); } return this.findEquipmentById(equipment.id); } diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index d433c2b..2745e72 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -1,7 +1,9 @@ import { DataSource } from 'apollo-datasource'; +import { GraphQLError } from 'graphql'; import { Connection, getConnection } from 'typeorm'; import { ContactInformation } from '../../model/ContactInformation'; import { ContactPerson } from '../../model/ContactPerson'; +import { LendingStation } from '../../model/LendingStation'; export class ContactInformationAPI extends DataSource { connection : Connection @@ -26,6 +28,15 @@ export class ContactInformationAPI extends DataSource { .getCount(); } + async contactInformation (offset: number, limit: number) { + return await this.connection.getRepository(ContactInformation) + .createQueryBuilder('contactinformation') + .select() + .offset(offset) + .limit(limit) + .getMany(); + } + async contactInformationById (id: number) { return await this.connection.getRepository(ContactInformation) .createQueryBuilder('contactInformation') @@ -34,12 +45,12 @@ export class ContactInformationAPI extends DataSource { .getOne(); } - async contactPersonByLendingStationId (id: number) { - return await this.connection.getRepository(ContactPerson) - .createQueryBuilder('contactPerson') - .leftJoinAndSelect('contactPerson.lendingStation', 'lendingStation') - .where('"lendingStation".id = :id', { id: id }) - .getMany().catch(() => { return []; }); + async contactPersonsByLendingStationId (id: number) { + return await this.connection + .createQueryBuilder() + .relation(LendingStation, 'contactPersons') + .of(id) + .loadMany(); } async contactInformationByContactPersonId (id: number) { @@ -47,22 +58,27 @@ export class ContactInformationAPI extends DataSource { .createQueryBuilder('contactPerson') .leftJoinAndSelect('contactPerson.contactInformation', 'contactInformation') .where('"contactPerson".id = :id', { id: id }) - .getOne())?.contactInformation; + .getOne())?.contactInformation || new GraphQLError('ContactPerson has no ContactInformtion'); } async createContactPerson (contactPerson: any) { if (await this.contactInformationById(contactPerson.contactInformationId)) { - const inserts = await this.connection.getRepository(ContactPerson) - .createQueryBuilder('contactPerson') - .insert() - .values([contactPerson]) - .returning('*') - .execute(); - await this.connection.getRepository(ContactPerson) - .createQueryBuilder('contactPerson') - .relation(ContactPerson, 'contactInformation') - .of(inserts.identifiers[0].id) - .set(contactPerson.contactInformationId); + let inserts: any; + try { + await this.connection.transaction(async entiyManager => { + inserts = await entiyManager.createQueryBuilder(ContactPerson, 'contactPerson') + .insert() + .values([contactPerson]) + .returning('*') + .execute(); + await entiyManager.createQueryBuilder() + .relation(ContactPerson, 'contactInformation') + .of(inserts.identifiers[0].id) + .set(contactPerson.contactInformationId); + }); + } catch (e: any) { + return new GraphQLError('Transaction could not be completed'); + } return this.contactPersonById(inserts.identifiers[0].id); } else { return null; diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index a84b91c..b2ee2be 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -70,14 +70,24 @@ export class LendingStationAPI extends DataSource { * creates new lendingStation and returns new lendingStation with its new id * @param param0 new lendingStation */ - async createLendingStation ({ lendingStation }:{ lendingStation: any }) { - const inserts = await this.connection.manager - .createQueryBuilder() - .insert() - .into(LendingStation) - .values([lendingStation]) - .returning('*') - .execute(); + async createLendingStation (lendingStation: any) { + let inserts: any; + try { + await this.connection.transaction(async entiyManager => { + inserts = await entiyManager.createQueryBuilder(LendingStation, 'lendingstation') + .insert() + .values([lendingStation]) + .returning('*') + .execute(); + await entiyManager.getRepository(LendingStation) + .createQueryBuilder('lendingstation') + .relation(LendingStation, 'contactPersons') + .of(lendingStation.id) + .add(lendingStation?.contactPersonIds.map((e: any) => { return Number(e); })); + }); + } catch (e :any) { + return new GraphQLError('Transaction could not be completed'); + } const newLendingStaion = inserts.generatedMaps[0]; newLendingStaion.id = inserts.identifiers[0].id; return newLendingStaion; diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts deleted file mode 100644 index f81e6c6..0000000 --- a/src/datasources/db/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* -import {Sequelize} from 'sequelize-typescript'; - -export class DButils { - dbclient: any; - constructor(){ - this.dbclient = new Sequelize({ - database: 'apollo', - dialect: 'postgres', - username: 'postgres', - password: 'apollo', - storage: ':memory:', - models: [__dirname + '/models'], // or [Player, Team], - }); -} -} -*/ diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index f5e0b94..5da5190 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -32,6 +32,7 @@ export enum StickerBikeNameState { } export interface Lockable { + id: number, lockedBy: number, lockedUntil: Date } diff --git a/src/model/ContactPerson.ts b/src/model/ContactPerson.ts index 4e2107b..cb3e0b6 100644 --- a/src/model/ContactPerson.ts +++ b/src/model/ContactPerson.ts @@ -14,7 +14,7 @@ export class ContactPerson { @ManyToMany(type => LendingStation, lendingStation => lendingStation.contactPersons, { nullable: true }) - lendingStation: LendingStation; + lendingStations: LendingStation[]; @ManyToMany(type => Provider, provider => provider.contactPersons, { nullable: true diff --git a/src/resolvers/cargobikeResolver.ts b/src/resolvers/cargobikeResolver.ts index 00730d8..ae98c3d 100644 --- a/src/resolvers/cargobikeResolver.ts +++ b/src/resolvers/cargobikeResolver.ts @@ -114,6 +114,13 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + unlockEquipment: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteBike)) { + return dataSources.cargoBikeAPI.unlockEquipment(id, req, dataSources); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, updateEquipment: (_: any, { equipment }: { equipment: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteBike)) { return dataSources.cargoBikeAPI.updateEquipment(equipment, req, dataSources); diff --git a/src/resolvers/contactinformationResolvers.ts b/src/resolvers/contactinformationResolvers.ts index 7c7eb72..6abb6a0 100644 --- a/src/resolvers/contactinformationResolvers.ts +++ b/src/resolvers/contactinformationResolvers.ts @@ -2,6 +2,15 @@ import { GraphQLError } from 'graphql'; import { Permission } from '../datasources/userserver/permission'; export default { + Query: { + contactInformation: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteBike)) { + return dataSources.contactInformationAPI.contactInformation(offset, limit); + } else { + return new GraphQLError('Insufficient Permissions'); + } + } + }, ContactPerson: { contactInformation: (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBike)) { diff --git a/src/resolvers/lendingstationResolvers.ts b/src/resolvers/lendingstationResolvers.ts index 3682c0c..201c732 100644 --- a/src/resolvers/lendingstationResolvers.ts +++ b/src/resolvers/lendingstationResolvers.ts @@ -21,7 +21,7 @@ export default { }, LendingStation: { contactPersons (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { - return dataSources.contactInformationAPI.contactPersonByLendingStationId(parent.id); + return dataSources.contactInformationAPI.contactPersonsByLendingStationId(parent.id); }, timeFrames (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { return dataSources.lendingStationAPI.timeFramesByLendingStationId(parent.id); @@ -36,7 +36,7 @@ export default { Mutation: { createLendingStation: (_: any, { lendingStation }:{ lendingStation: LendingStation }, { dataSources, req }:{dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteBike)) { - return dataSources.lendingStationAPI.createLendingStation({ lendingStation }); + return dataSources.lendingStationAPI.createLendingStation(lendingStation); } else { return new GraphQLError('Insufficient Permissions'); } diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 0afe3b3..37128f5 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -2,7 +2,10 @@ import { gql } from 'apollo-server'; export default gql` +"timestamp object YYYY-MM-ddThh:mm:ss.sssZ" scalar Date +"only time hh-mm-ss" +scalar Time "The CargoBike type is central to the graph. You could call it the root." type CargoBike { @@ -347,6 +350,8 @@ input EquipmentUpdateInput { description: String investable: Boolean cargoBikeId: ID + "will keep Bike locked if set to true, default = false" + keepLock: Boolean } "An Event is a point in time, when the state of the bike somehow changed." @@ -598,10 +603,10 @@ type LendingStation { input LendingStationCreateInput { name: String! - contactInformation: [ContactInformationCreateInput]! + contactPersonIds: [ID]! address: AddressCreateInput! loanPeriods: LoanPeriodsInput - timeFrames: [TimeFrameCreateInput]! + timeFrameIds: [ID]! } input LendingStationUpdateInput { @@ -705,7 +710,7 @@ type Query { participants(offset: Int!, limit: Int!): [ Participant]! lendingStationById(id:ID!): LendingStation lendingStations(offset: Int!, limit: Int!): [LendingStation]! - contactInformation: [ContactInformation]! + contactInformation(offset: Int!, limit: Int!): [ContactInformation]! "returns BikeEvent with CargoBike" bikeEventById(id:ID!): BikeEvent! } @@ -713,16 +718,18 @@ type Query { type Mutation { "creates new cargoBike and returns cargobike with new ID" createCargoBike(cargoBike: CargoBikeCreateInput!): CargoBike! - "lock cargoBike returns bike if bike is not locked or Error" + "lock cargoBike returns bike if bike is not locked and locks bike or Error if bike cannot be locked" lockCargoBikeById(id: ID!): CargoBike! - "unlock cargoBike" + "unlock cargoBike, returns true if Bike does not exist" unlockCargoBikeById(id: ID!): Boolean! "updates cargoBike of given ID with supplied fields and returns updated cargoBike" updateCargoBike(cargoBike: CargoBikeUpdateInput!): CargoBike! "creates new peace of unique Equipment" createEquipment(equipment: EquipmentCreateInput!): Equipment! "lock equipment returns true if bike is not locked or if it doesnt exist" - lockEquipmentById(id: ID!): Boolean! + lockEquipmentById(id: ID!): Equipment! + "unlock Equopment, returns true if Bike does not exist" + unlockEquipment(id: ID!): Boolean! "update Equipment, returns updated equipment. CargoBike will be null, if cargoBikeId is not set. Pass null for cargoBikeIs to delete the relation" updateEquipment(equipment: EquipmentUpdateInput!): Equipment! "creates new lendingStation and returns lendingStation with new ID"