diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index ff47082..40f7093 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -20,7 +20,6 @@ This file is part of fLotte-API-Server. import { DataSource } from 'apollo-datasource'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { CargoBike } from '../../model/CargoBike'; -import { GraphQLError } from 'graphql'; import { BikeEvent } from '../../model/BikeEvent'; import { Equipment } from '../../model/Equipment'; import { Engagement } from '../../model/Engagement'; @@ -29,8 +28,9 @@ import { TimeFrame } from '../../model/TimeFrame'; import { ActionLogger, DBUtils, genBoxDimensions, LockUtils } from './utils'; import { EquipmentType } from '../../model/EquipmentType'; import { BikeEventType } from '../../model/BikeEventType'; -import { UserInputError } from 'apollo-server-express'; import { Actions } from '../../model/ActionLog'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; +import { NotFoundError } from '../../errors/NotFoundError'; /** * extended datasource to feed resolvers with data about cargoBikes @@ -83,10 +83,16 @@ export class CargoBikeAPI extends DataSource { } async lockCargoBike (id: number, userId: number) { + if (!await this.connection.getRepository(CargoBike).findOne(id)) { + throw new NotFoundError('CargoBike', 'id', id); + } return await LockUtils.lockEntity(this.connection, CargoBike, 'cb', id, userId); } async unlockCargoBike (id: number, userId: number) { + if (!await this.connection.getRepository(CargoBike).findOne(id)) { + throw new NotFoundError('CargoBike', 'id', id); + } return await LockUtils.unlockEntity(this.connection, CargoBike, 'cb', id, userId); } @@ -107,7 +113,7 @@ export class CargoBikeAPI extends DataSource { 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'); + throw new ResourceLockedError('CargoBike'); } await ActionLogger.log(entityManager, CargoBike, 'cb', cargoBike, userId); await entityManager.getRepository(CargoBike) @@ -116,11 +122,13 @@ export class CargoBikeAPI extends DataSource { .set({ ...cargoBike }) .where('id = :id', { id: cargoBike.id }) .execute(); + equipmentTypeIds && await entityManager.getRepository(CargoBike) .createQueryBuilder('cb') .relation(CargoBike, 'equipmentTypeIds') .of(cargoBike.id) .addAndRemove(equipmentTypeIds, await this.equipmentTypeByCargoBikeId(cargoBike.id)); + equipmentIds && await entityManager.getRepository(CargoBike) .createQueryBuilder('cb') .relation(CargoBike, 'equipmentIds') @@ -134,7 +142,7 @@ export class CargoBikeAPI extends DataSource { async deleteCargoBike (id: number, userId: number) { return await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, CargoBike, 'cb', id, userId)) { - throw new UserInputError('Attempting to soft delete locked resource'); + throw new ResourceLockedError('CargoBike', 'Attempting to soft delete locked resource'); } await ActionLogger.log(entityManager, CargoBike, 'bg', { id: id }, userId, Actions.SOFT_DELETE); return await entityManager.getRepository(CargoBike) @@ -189,7 +197,7 @@ export class CargoBikeAPI extends DataSource { delete bikeEvent.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, BikeEvent, 'be', bikeEvent.id, userId)) { - throw new GraphQLError('BikeEvent locked by other user'); + throw new ResourceLockedError('BikeEvents'); } await ActionLogger.log(entityManager, BikeEvent, 'be', bikeEvent, userId); await entityManager.getRepository(BikeEvent) @@ -267,7 +275,7 @@ export class CargoBikeAPI extends DataSource { delete bikeEventType.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, BikeEventType, 'bet', bikeEventType.id, userId)) { - throw new GraphQLError('BikeEventType locked by other user'); + throw new ResourceLockedError('BikeEventType'); } await ActionLogger.log(entityManager, BikeEventType, 'bet', bikeEventType, userId); await entityManager.getRepository(BikeEventType) @@ -405,7 +413,7 @@ export class CargoBikeAPI extends DataSource { delete equipment.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, Equipment, 'equipment', equipment.id, userId)) { - return new GraphQLError('Equipment is locked by other user'); + throw new ResourceLockedError('Equipment'); } await ActionLogger.log(entityManager, Equipment, 'e', equipment, userId); await entityManager.getRepository(Equipment) @@ -453,7 +461,7 @@ export class CargoBikeAPI extends DataSource { delete equipmentType.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, EquipmentType, 'et', equipmentType.id, userId)) { - throw new GraphQLError('EquipmentType is locked by other user'); + throw new ResourceLockedError('EquipmentType'); } await ActionLogger.log(entityManager, EquipmentType, 'et', equipmentType, userId); await entityManager.getRepository(EquipmentType) diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index e3e9a28..10e7959 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -22,8 +22,9 @@ import { Connection, EntityManager, getConnection } from 'typeorm'; import { ContactInformation } from '../../model/ContactInformation'; import { Person } from '../../model/Person'; import { ActionLogger, DBUtils, LockUtils } from './utils'; -import { GraphQLError } from 'graphql'; import { LendingStation } from '../../model/LendingStation'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; +import { NotFoundError } from '../../errors/NotFoundError'; export class ContactInformationAPI extends DataSource { connection : Connection @@ -68,7 +69,7 @@ export class ContactInformationAPI extends DataSource { delete person.keepLock; await this.connection.transaction(async (entityManger: EntityManager) => { if (await LockUtils.isLocked(entityManger, Person, 'p', person.id, userId)) { - throw new GraphQLError('Person is locker by another user'); + throw new ResourceLockedError('Person'); } await ActionLogger.log(entityManger, Person, 'p', person, userId); await entityManger.getRepository(Person) @@ -76,7 +77,11 @@ export class ContactInformationAPI extends DataSource { .update() .set({ ...person }) .where('id = :id', { id: person.id }) - .execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('Id not found'); } }); + .execute().then(value => { + if (value.affected !== 1) { + throw new NotFoundError('Person', 'id', person.id); + } + }); }); !keepLock && await this.unlockPerson(person.id, userId); return this.personById(person.id); @@ -146,7 +151,7 @@ export class ContactInformationAPI extends DataSource { delete contactInformation.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, ContactInformation, 'ci', contactInformation.id, userId)) { - throw new GraphQLError('ContactInformation is locked by other user'); + throw new ResourceLockedError('ContactInformation'); } await ActionLogger.log(entityManager, ContactInformation, 'ci', contactInformation, userId); await entityManager.getRepository(ContactInformation) diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index 52c08ad..02ab251 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -24,6 +24,7 @@ import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; import { TimeFrame } from '../../model/TimeFrame'; import { ActionLogger, genDateRange, DBUtils, LockUtils } from './utils'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; export class LendingStationAPI extends DataSource { connection : Connection @@ -163,7 +164,7 @@ export class LendingStationAPI extends DataSource { delete lendingStation.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, LendingStation, 'ls', lendingStation.id, userId)) { - throw new UserInputError('Attempting to update locked resource'); + throw new ResourceLockedError('LendingStation', 'Attempting to update locked resource'); } await ActionLogger.log(entityManager, LendingStation, 'ls', lendingStation, userId); await entityManager.getRepository(LendingStation) @@ -222,7 +223,7 @@ export class LendingStationAPI extends DataSource { delete timeFrame.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, TimeFrame, 'tf', timeFrame.id, userId)) { - throw new UserInputError('Attempting to update locked resource'); + throw new ResourceLockedError('TimeFrame', 'Attempting to update locked resource'); } genDateRange(timeFrame); await ActionLogger.log(entityManager, TimeFrame, 'tf', timeFrame, userId); diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index 270ec09..8efd5b8 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -26,6 +26,7 @@ import { EngagementType } from '../../model/EngagementType'; import { ActionLogger, DBUtils, genDateRange, LockUtils } from './utils'; import { UserInputError } from 'apollo-server-express'; import { GraphQLError } from 'graphql'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; export class ParticipantAPI extends DataSource { connection : Connection @@ -202,7 +203,7 @@ 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 UserInputError('Attempting to update locked resource'); + throw new ResourceLockedError('Participant', 'Attempting to update locked resource'); } genDateRange(participant); const workshops = participant.workshopIds; @@ -288,7 +289,7 @@ export class ParticipantAPI extends DataSource { genDateRange(engagement); await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, Engagement, 'e', engagement.id, userId)) { - throw new GraphQLError('Engagement is locked by other user'); + throw new ResourceLockedError('Engagement'); } await ActionLogger.log(entityManager, Engagement, 'e', engagement, userId); // check for overlapping engagements @@ -356,7 +357,7 @@ export class ParticipantAPI extends DataSource { delete engagementType.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, EngagementType, 'et', engagementType.id, userId)) { - throw new GraphQLError('EngagementType is locked by other user'); + throw new ResourceLockedError('EngagementType'); } await ActionLogger.log(entityManager, EngagementType, 'et', engagementType, userId); await entityManager.getRepository(EngagementType) diff --git a/src/datasources/db/providerAPI.ts b/src/datasources/db/providerAPI.ts index 56df367..30fabc3 100644 --- a/src/datasources/db/providerAPI.ts +++ b/src/datasources/db/providerAPI.ts @@ -25,7 +25,8 @@ import { UserInputError } from 'apollo-server-express'; import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; import { ActionLogger, DBUtils, LockUtils } from './utils'; -import { GraphQLError } from 'graphql'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; +import { NotFoundError } from '../../errors/NotFoundError'; export class ProviderAPI extends DataSource { connection : Connection @@ -150,7 +151,7 @@ export class ProviderAPI extends DataSource { delete provider.cargoBikeIds; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, Provider, 'p', provider.id, userId)) { - throw new GraphQLError('Provider is locked by another user'); + throw new ResourceLockedError('Provider'); } await ActionLogger.log(entityManager, Provider, 'p', provider, userId); await entityManager.getRepository(Provider) @@ -158,7 +159,11 @@ export class ProviderAPI extends DataSource { .update() .set({ ...provider }) .where('id = :id', { id: provider.id }) - .execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + .execute().then(value => { + if (value.affected !== 1) { + throw new NotFoundError('Provider', 'id', provider.id); + } + }); await entityManager.getRepository(Provider) .createQueryBuilder('p') .relation(Provider, 'cargoBikeIds') @@ -198,7 +203,7 @@ export class ProviderAPI extends DataSource { delete organisation.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, Organisation, 'o', organisation.id, userId)) { - throw new GraphQLError('Organisation is locked by another user'); + throw new ResourceLockedError('Organisation'); } await ActionLogger.log(entityManager, Organisation, 'o', organisation, userId); await entityManager.getRepository(Organisation) diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index 7262417..d377977 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -21,6 +21,8 @@ import { Connection, EntityManager, ObjectType } from 'typeorm'; import { Lockable } from '../../model/CargoBike'; import { ActionLog, Actions } from '../../model/ActionLog'; import { UserInputError } from 'apollo-server-express'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; +import { NotFoundError } from '../../errors/NotFoundError'; export function genDateRange (struct: any) { if (!struct.dateRange || !struct.dateRange.from) { @@ -100,7 +102,7 @@ export class DBUtils { 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'); + throw new ResourceLockedError('Connection', 'Attempting to delete locked resource'); } await ActionLogger.log(entityManger, target, alias, { id: id }, userId, Actions.DELETE); return await entityManger.getRepository(target) @@ -158,7 +160,7 @@ export class LockUtils { .select() .where(alias + '.id = :id', { id: id }) .getOne().catch(() => { - throw new UserInputError('ID not found'); + throw new NotFoundError(target.name, 'id', id); }); } @@ -301,7 +303,7 @@ export class ActionLogger { .where('id = :id', { id: updates.id }) .getRawOne().then(value => { if (value === undefined) { - throw new UserInputError('Id not found'); + throw new NotFoundError(target.name, 'id', updates.id); } return value; }); // use getRawOne to also get ids of related entities diff --git a/src/datasources/db/workshopAPI.ts b/src/datasources/db/workshopAPI.ts index c6ba9d8..9c0e510 100644 --- a/src/datasources/db/workshopAPI.ts +++ b/src/datasources/db/workshopAPI.ts @@ -22,9 +22,9 @@ import { Connection, EntityManager, getConnection } from 'typeorm'; import { WorkshopType } from '../../model/WorkshopType'; import { Workshop } from '../../model/Workshop'; import { ActionLogger, DBUtils, LockUtils } from './utils'; -import { UserInputError } from 'apollo-server-express'; -import { GraphQLError } from 'graphql'; import { Participant } from '../../model/Participant'; +import { ResourceLockedError } from '../../errors/ResourceLockedError'; +import { NotFoundError } from '../../errors/NotFoundError'; export class WorkshopAPI extends DataSource { connection: Connection @@ -57,7 +57,7 @@ export class WorkshopAPI extends DataSource { delete workshop.keepLock; await this.connection.transaction(async (entityManger: EntityManager) => { if (await LockUtils.isLocked(entityManger, Workshop, 'w', workshop.id, userId)) { - throw new UserInputError('Attempting to update locked resource'); + throw new ResourceLockedError('Workshop', 'Attempting to update locked resource'); } await ActionLogger.log(entityManger, Workshop, 'w', workshop, userId); await entityManger.getRepository(Workshop) @@ -66,7 +66,11 @@ export class WorkshopAPI extends DataSource { .set({ ...workshop }) .where('id = :id', { id: workshop.id }) .execute() - .then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + .then(value => { + if (value.affected !== 1) { + throw new NotFoundError('Workshop', 'id', workshop.id); + } + }); }); !keepLock && await this.unlockWorkshop(workshop.id, userId); return await this.workshopById(workshop.id); @@ -99,7 +103,7 @@ export class WorkshopAPI extends DataSource { delete workshopType.keepLock; await this.connection.transaction(async (entityManager: EntityManager) => { if (await LockUtils.isLocked(entityManager, WorkshopType, 'wt', workshopType.id, userId)) { - throw new UserInputError('Attempting to update locked resource'); + throw new ResourceLockedError('WorkshopType', 'Attempting to update locked resource'); } await ActionLogger.log(entityManager, WorkshopType, 'wt', workshopType, userId); await entityManager.getRepository(WorkshopType) @@ -108,7 +112,11 @@ export class WorkshopAPI extends DataSource { .set({ ...workshopType }) .where('id = :id', { id: workshopType.id }) .execute() - .then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + .then(value => { + if (value.affected !== 1) { + throw new NotFoundError('WorkshopType', 'id', workshopType.id); + } + }); }); !keepLock && await this.unlockWorkshopType(workshopType.id, userId); return await this.workshopTypeById(workshopType.id); diff --git a/src/errors/NotFoundError.ts b/src/errors/NotFoundError.ts new file mode 100644 index 0000000..d0d1ecf --- /dev/null +++ b/src/errors/NotFoundError.ts @@ -0,0 +1,7 @@ +import { ApolloError } from 'apollo-server-express'; + +export class NotFoundError extends ApolloError { + constructor (type: string, identifierParam: string, identifierValue: any) { + super(`${type} with ${identifierParam} = '${identifierValue}' not found.`, 'ENTITY_NOT_FOUND'); + } +} diff --git a/src/errors/ResourceLockedError.ts b/src/errors/ResourceLockedError.ts new file mode 100644 index 0000000..1b45eed --- /dev/null +++ b/src/errors/ResourceLockedError.ts @@ -0,0 +1,11 @@ +import { ApolloError } from 'apollo-server-express'; + +export class ResourceLockedError extends ApolloError { + constructor (resourceName: String, additionalContext?: string) { + if (additionalContext) { + super(`The Resource ${resourceName} is currently locked: ${additionalContext}`, 'LOCKED_ERROR'); + } else { + super(`The Resource ${resourceName} is currently locked`, 'LOCKED_ERROR'); + } + } +} diff --git a/tests/testQueries.ts b/tests/testQueries.ts index 623fc75..5615cc5 100644 --- a/tests/testQueries.ts +++ b/tests/testQueries.ts @@ -20,9 +20,15 @@ export const CREATE_CARGO_BIKE = gql` dimensionsAndLoad: { hasCoverBox: true lockable:false - boxLength: 0.1 - boxWidth: 0.2 - boxHeight:0.3 + boxLengthRange: { + min: 0, + } + boxWidthRange: { + min: 0.2 + } + boxHeightRange: { + min: 0.3 + } maxWeightBox: 1.1 maxWeightLuggageRack: 1.2 maxWeightTotal: 1.3