diff --git a/gulpfile.js b/gulpfile.js index b9147d4..1a3f5f7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,14 +1,14 @@ -const { src, dest, watch, series, task } = require('gulp') -const ts = require('gulp-typescript') -const del = require('delete') -const eslint = require('gulp-eslint') -const nodemon = require('gulp-nodemon') +const { src, dest, watch, series, task } = require('gulp'); +const ts = require('gulp-typescript'); +const del = require('delete'); +const eslint = require('gulp-eslint'); +const nodemon = require('gulp-nodemon'); /** * Clears the dist folder by deleting all files inside. * @param cb */ function clearDist (cb) { - del('dist/*', cb) + del('dist/*', cb); } /** @@ -16,11 +16,11 @@ function clearDist (cb) { * @returns {*} */ function compileTypescript () { - const tsProject = ts.createProject('tsconfig.json') - const tsResult = tsProject.src().pipe(tsProject()) + const tsProject = ts.createProject('tsconfig.json'); + const tsResult = tsProject.src().pipe(tsProject()); return tsResult // .pipe(minify()) - .pipe(dest('dist')) + .pipe(dest('dist')); } /** @@ -29,7 +29,7 @@ function compileTypescript () { */ function moveRemaining () { return src(['src/**/*', '!src/**/*.ts']) - .pipe(dest('dist')) + .pipe(dest('dist')); } function runEslint () { @@ -42,7 +42,7 @@ function runEslint () { .pipe(eslint.format()) // To have the process exit with an error code (1) on // lint error, return the stream and pipe to failAfterError last. - .pipe(eslint.failAfterError()) + .pipe(eslint.failAfterError()); } task('eslint', () => { return src(['src/**/*.ts']) @@ -54,30 +54,35 @@ task('eslint', () => { .pipe(eslint.format()) // To have the process exit with an error code (1) on // lint error, return the stream and pipe to failAfterError last. - .pipe(eslint.failAfterError()) -}) + .pipe(eslint.failAfterError()); +}); -task('default', series(clearDist, compileTypescript, moveRemaining)) +task('default', series(clearDist, compileTypescript, moveRemaining)); task('watch', () => { - runEslint() - compileTypescript() - watch('**/*.ts', runEslint) - watch('**/*.ts', compileTypescript) + runEslint(); + compileTypescript(); + watch('**/*.ts', runEslint); + watch('**/*.ts', compileTypescript); // watch(['src/**/*', '!src/**/*.ts'], moveRemaining()); nodemon({ script: 'dist/index.js', watch: ['dist/**/*.js'], ext: 'js' - }) -}) + }); +}); + +task('watchTs', () => { + compileTypescript(); + watch('**/*.ts', compileTypescript); +}); task('watchnolint', () => { - watch('**/*.ts', compileTypescript) + watch('**/*.ts', compileTypescript); // watch(['src/**/*', '!src/**/*.ts'], moveRemaining()); nodemon({ script: 'dist/index.js', watch: ['dist/**/*.js'], ext: 'js' - }) -}) + }); +}); diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index c0d17d5..7126b95 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -7,7 +7,7 @@ import { Equipment } from '../../model/Equipment'; import { Engagement } from '../../model/Engagement'; import { Provider } from '../../model/Provider'; import { TimeFrame } from '../../model/TimeFrame'; -import { LockUtils } from './utils'; +import { ActionLogger, LockUtils } from './utils'; import { EquipmentType } from '../../model/EquipmentType'; import { BikeEventType } from '../../model/BikeEventType'; @@ -54,7 +54,7 @@ export class CargoBikeAPI extends DataSource { async cargoBikesByProviderId (id: number) { return await this.connection .createQueryBuilder() - .relation(Provider, 'cargoBikes') + .relation(Provider, 'cargoBikeIds') .of(id) .loadMany(); } @@ -62,7 +62,7 @@ export class CargoBikeAPI extends DataSource { async cargoBikeByTimeFrameId (id: number) { return await this.connection.getRepository(TimeFrame) .createQueryBuilder('timeframe') - .relation(TimeFrame, 'cargoBike') + .relation(TimeFrame, 'cargoBikeId') .of(id) .loadOne(); } @@ -72,13 +72,6 @@ export class CargoBikeAPI extends DataSource { * @param param0 cargoBike to be updated */ async updateCargoBike (cargoBike: any, userId:number) { - // TODO 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 LockUtils.lockEntity(this.connection, CargoBike, 'cb', cargoBike.id, userId)) { - return new GraphQLError('Bike locked by other user'); - } const keepLock = cargoBike?.keepLock; delete cargoBike.keepLock; delete cargoBike.lendingStationId; @@ -88,6 +81,10 @@ export class CargoBikeAPI extends DataSource { delete cargoBike.equipmentTypeIds; } 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'); + } + await ActionLogger.log(entityManager, CargoBike, 'cb', cargoBike, userId); await entityManager.getRepository(CargoBike) .createQueryBuilder('cargobike') .update() @@ -138,6 +135,25 @@ export class CargoBikeAPI extends DataSource { .execute()).generatedMaps[0]; } + async updateBikeEvent (bikeEvent: any, userId: number) { + const keepLock = bikeEvent.keepLock; + 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'); + } + await ActionLogger.log(entityManager, BikeEvent, 'be', bikeEvent, userId); + await entityManager.getRepository(BikeEvent) + .createQueryBuilder('be') + .update() + .set({ ...bikeEvent }) + .where('id = :id', { id: bikeEvent.id }) + .execute(); + }); + !keepLock && await LockUtils.unlockEntity(this.connection, BikeEvent, 'be', bikeEvent.id, userId); + return await this.bikeEventById(bikeEvent.id); + } + async cargoBikeByEventId (id: number) { return await this.connection.getRepository(BikeEvent) .createQueryBuilder('be') @@ -173,6 +189,33 @@ export class CargoBikeAPI extends DataSource { .execute())?.generatedMaps[0]; } + async lockBikeEventType (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, BikeEventType, 'bet', id, userId); + } + + async unlockBikeEventType (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, BikeEventType, 'bet', id, userId); + } + + async updateBikeEventType (bikeEventType: any, userId: number) { + const keepLock = bikeEventType.keepLock; + 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'); + } + await ActionLogger.log(entityManager, BikeEventType, 'bet', bikeEventType, userId); + await entityManager.getRepository(BikeEventType) + .createQueryBuilder('bet') + .update() + .set({ ...bikeEventType }) + .where('id = :id', { id: bikeEventType.id }) + .execute(); + }); + !keepLock && await LockUtils.unlockEntity(this.connection, BikeEventType, 'bet', bikeEventType.id, userId); + return await this.bikeEventTypeById(bikeEventType.id); + } + async bikeEventTypes (offset: number, limit: number) { return await this.connection.getRepository(BikeEventType) .createQueryBuilder('bet') @@ -191,7 +234,7 @@ export class CargoBikeAPI extends DataSource { .getMany(); } - async findBikeEventTypeById (id: number) { + async bikeEventTypeById (id: number) { return await this.connection.getRepository(BikeEventType) .createQueryBuilder('bet') .select() @@ -219,11 +262,11 @@ export class CargoBikeAPI extends DataSource { * return bikeEvent including CargoBike * @param id of event */ - async findBikeEventById (id: number) { + async bikeEventById (id: number) { return await this.connection.getRepository(BikeEvent) .createQueryBuilder('bikeEvent') - .leftJoinAndSelect('bikeEvent.cargoBike', 'cargoBike') - .where('bikeEvent.id = :id', { id: id }) + .select() + .where('id = :id', { id: id }) .getOne(); } @@ -276,22 +319,15 @@ export class CargoBikeAPI extends DataSource { .values([equipment]) .returning('*') .execute(); - if (equipment.cargoBikeId) { - await this.connection - .createQueryBuilder() - .relation(Equipment, 'cargoBike') - .of(equipment.id) - .set(equipment.cargoBikeId); - } return this.equipmentById(inserts.identifiers[0].id); } async cargoBikeByEquipmentId (id: number) { - return (await this.connection.getRepository(Equipment) + return await this.connection.getRepository(Equipment) .createQueryBuilder('equipment') - .leftJoinAndSelect('equipment.cargoBike', 'cargoBike') - .where('equipment.id = :id', { id: id }) - .getOne())?.cargoBike; + .relation(Equipment, 'cargoBikeId') + .of(id) + .loadOne(); } async lockEquipment (id: number, userId: number) { @@ -308,33 +344,33 @@ export class CargoBikeAPI extends DataSource { * @param param0 struct with equipment properites */ async updateEquipment (equipment: any, userId: number) { - // 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 LockUtils.lockEntity(this.connection, Equipment, 'equipment', equipment.id, userId)) { - 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) - .createQueryBuilder('equipment') - .update() - .set({ ...equipment }) - .where('id = :id', { id: equipment.id }) - .returning('*') - .execute(); - if (cargoBikeId || cargoBikeId === null) { - await this.connection.getRepository(Equipment) - .createQueryBuilder() - .relation(Equipment, 'cargoBike') - .of(equipment.id) - .set(cargoBikeId); - !keepLock && LockUtils.unlockEntity(this.connection, Equipment, 'e', equipment.id, userId); - return this.equipmentById(equipment.id); + // const cargoBikeId = equipment.cargoBikeId; + // delete equipment.cargoBikeId; + 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'); + } + await ActionLogger.log(entityManager, Equipment, 'e', equipment, userId); + await entityManager.getRepository(Equipment) + .createQueryBuilder('equipment') + .update() + .set({ ...equipment }) + .where('id = :id', { id: equipment.id }) + .execute(); + /* if (cargoBikeId || cargoBikeId === null) { + await this.connection.getRepository(Equipment) + .createQueryBuilder() + .relation(Equipment, 'cargoBike') + .of(equipment.id) + .set(cargoBikeId); + } + + */ } + ); + !keepLock && await LockUtils.unlockEntity(this.connection, Equipment, 'e', equipment.id, userId); return this.equipmentById(equipment.id); } @@ -359,6 +395,32 @@ export class CargoBikeAPI extends DataSource { return inserts.generatedMaps[0]; } + async lockEquipmentType (id: number, userId : number) { + return await LockUtils.lockEntity(this.connection, EquipmentType, 'et', id, userId); + } + + async unlockEquipmentType (id: number, userId : number) { + return await LockUtils.unlockEntity(this.connection, EquipmentType, 'et', id, userId); + } + + async updateEquipmentType (equipmentType: any, userId: number) { + const keepLock = equipmentType.keepLock; + 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'); + } + await entityManager.getRepository(EquipmentType) + .createQueryBuilder('et') + .update() + .set({ ...equipmentType }) + .where('id = :id', { id: equipmentType.id }) + .execute(); + }); + !keepLock && await this.unlockEquipmentType(equipmentType.id, userId); + return await this.equipmentTypeById(equipmentType.id); + } + async equipmentTypeById (id: number) { return await this.connection.getRepository(EquipmentType) .createQueryBuilder('equipmentType') diff --git a/src/datasources/db/contactinformationAPI.ts b/src/datasources/db/contactinformationAPI.ts index 3a62d2c..4b596bb 100644 --- a/src/datasources/db/contactinformationAPI.ts +++ b/src/datasources/db/contactinformationAPI.ts @@ -1,7 +1,10 @@ import { DataSource } from 'apollo-datasource'; -import { Connection, getConnection } from 'typeorm'; +import { Connection, EntityManager, getConnection } from 'typeorm'; import { ContactInformation } from '../../model/ContactInformation'; import { Person } from '../../model/Person'; +import { ActionLogger, LockUtils } from './utils'; +import { GraphQLError } from 'graphql'; +import { LendingStation } from '../../model/LendingStation'; export class ContactInformationAPI extends DataSource { connection : Connection @@ -46,6 +49,33 @@ export class ContactInformationAPI extends DataSource { return inserts.generatedMaps[0]; } + async lockPerson (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, Person, 'p', id, userId); + } + + async unlockPerson (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, Person, 'p', id, userId); + } + + async updatePerson (person: any, userId: number) { + const keepLock = person.keepLock; + 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'); + } + await ActionLogger.log(entityManger, Person, 'p', person, userId); + await entityManger.getRepository(Person) + .createQueryBuilder('p') + .update() + .set({ ...person }) + .where('id = :id', { id: person.id }) + .execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('Id not found'); } }); + }); + !keepLock && await this.unlockPerson(person.id, userId); + return this.personById(person.id); + } + async persons (offset: number, limit: number) { return await this.connection.getRepository(Person) .createQueryBuilder('person') @@ -71,6 +101,22 @@ export class ContactInformationAPI extends DataSource { .loadOne(); } + async contactInternByLendingStationId (id: number) { + return this.connection.getRepository(LendingStation) + .createQueryBuilder('ls') + .relation(LendingStation, 'contactInformationInternId') + .of(id) + .loadOne(); + } + + async contactExternByLendingStationId (id: number) { + return this.connection.getRepository(LendingStation) + .createQueryBuilder('ls') + .relation(LendingStation, 'contactInformationExternId') + .of(id) + .loadOne(); + } + async createContactInformation (contactInformation: any) { const inserts = await this.connection.getRepository(ContactInformation) .createQueryBuilder('contactInformation') @@ -82,6 +128,33 @@ export class ContactInformationAPI extends DataSource { return inserts.generatedMaps[0]; } + async lockContactInformation (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, ContactInformation, 'ci', id, userId); + } + + async unlockContactInformation (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, ContactInformation, 'ci', id, userId); + } + + async updateContactInformation (contactInformation: any, userId: number) { + const keepLock = contactInformation.keepLock; + 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'); + } + await ActionLogger.log(entityManager, ContactInformation, 'ci', contactInformation, userId); + await entityManager.getRepository(ContactInformation) + .createQueryBuilder('ci') + .update() + .set({ ...contactInformation }) + .where('id = :id', { id: contactInformation.id }) + .execute(); + }); + !keepLock && await LockUtils.unlockEntity(this.connection, ContactInformation, 'ci', contactInformation.id, userId); + return await this.contactInformationById(contactInformation.id); + } + async contactInformationByPersonId (id: number) { const res = await this.connection.getRepository(ContactInformation) .createQueryBuilder('ci') diff --git a/src/datasources/db/lendingstationAPI.ts b/src/datasources/db/lendingstationAPI.ts index 12c4f67..54b75cc 100644 --- a/src/datasources/db/lendingstationAPI.ts +++ b/src/datasources/db/lendingstationAPI.ts @@ -5,7 +5,7 @@ import { Connection, EntityManager, getConnection, QueryFailedError } from 'type import { CargoBike } from '../../model/CargoBike'; import { LendingStation } from '../../model/LendingStation'; import { TimeFrame } from '../../model/TimeFrame'; -import { LockUtils } from './utils'; +import { ActionLogger, genDateRange, LockUtils } from './utils'; export class LendingStationAPI extends DataSource { connection : Connection @@ -14,12 +14,11 @@ export class LendingStationAPI extends DataSource { this.connection = getConnection(); } - async lendingStationById ({ id }: { id: any }) { - return await this.connection.manager - .createQueryBuilder() - .select('lendingStation') - .from(LendingStation, 'lendingStation') - .where('lendingStation.id = :id', { id: id }) + async lendingStationById (id:number) { + return await this.connection.getRepository(LendingStation) + .createQueryBuilder('ls') + .select() + .where('id = :id', { id: id }) .getOne(); } @@ -102,10 +101,6 @@ export class LendingStationAPI extends DataSource { return LockUtils.unlockEntity(this.connection, LendingStation, 'ls', id, uId); } - async lockTimeFrame (id: number, userId: number) { - return await LockUtils.lockEntity(this.connection, TimeFrame, 'tf', id, userId); - } - /** * Counts all timeframes with one lendingStation that overlap with today's date * @param id of lendingStation @@ -140,40 +135,38 @@ export class LendingStationAPI extends DataSource { */ async createLendingStation (lendingStation: any) { let inserts: any; - await this.connection.transaction(async entiyManager => { - inserts = await entiyManager.createQueryBuilder(LendingStation, 'lendingstation') + await this.connection.transaction(async entityManager => { + inserts = await entityManager.createQueryBuilder(LendingStation, 'lendingstation') .insert() .values([lendingStation]) .returning('*') .execute(); }); - - const newLendingStaion = inserts.generatedMaps[0]; - newLendingStaion.id = inserts.identifiers[0].id; - return newLendingStaion; + // when using the return values, the simple array has a different format and must treated in another way, so this is the more expansive solution + return await this.lendingStationById(inserts.generatedMaps[0].id); } /** * updates lendingStation and return updated lendingStation * @param param0 lendingStation to be updated */ - async updateLendingStation ({ lendingStation }:{ lendingStation: any }) { - const oldLendingStation = await this.connection.manager.createQueryBuilder() - .select('lendingStation') - .from(LendingStation, 'lendingStation') - .where('lendingStation.id = :id', { id: lendingStation.id }) - .getOne(); - if (oldLendingStation) { - await this.connection - .createQueryBuilder() - .update(LendingStation) + async updateLendingStation (lendingStation: any, userId: number) { + const keepLock = lendingStation.keepLock; + delete lendingStation.keepLock; + await this.connection.transaction(async (entityManager: EntityManager) => { + if (await LockUtils.isLocked(entityManager, LendingStation, 'ls', lendingStation.id, userId)) { + throw new GraphQLError('LendingStation is locked by another user'); + } + await ActionLogger.log(entityManager, LendingStation, 'ls', lendingStation, userId); + await entityManager.getRepository(LendingStation) + .createQueryBuilder('ls') + .update() .set({ ...lendingStation }) .where('id = :id', { id: lendingStation.id }) .execute(); - return this.lendingStationById({ id: lendingStation.id }); - } else { - return new GraphQLError('ID not in database'); - } + }); + !keepLock && await LockUtils.unlockEntity(this.connection, LendingStation, 'ls', lendingStation.id, userId); + return await this.lendingStationById(lendingStation.id); } async createTimeFrame (timeFrame: any) { @@ -202,17 +195,6 @@ export class LendingStationAPI extends DataSource { .returning('*') .values([timeFrame]) .execute(); - /* await entityManager.getRepository(TimeFrame) - .createQueryBuilder() - .relation(TimeFrame, 'cargoBike') - .of(inserts.identifiers[0].id) - .set(timeFrame.cargoBikeId); - await entityManager.getRepository(TimeFrame) - .createQueryBuilder() - .relation(TimeFrame, 'lendingStation') - .of(inserts.identifiers[0].id) - .set(timeFrame.lendingStationId); - */ }); } catch (e) { if (e instanceof UserInputError) { @@ -226,55 +208,46 @@ export class LendingStationAPI extends DataSource { return inserts.generatedMaps[0]; } - /* async updateTimeFrame (timeFrame: any) { - try { - await this.connection.transaction(async (entityManager: EntityManager) => { - if (timeFrame.to === undefined) { - timeFrame.to = ''; - } - timeFrame.dateRange = '[' + timeFrame.from + ',' + timeFrame.to + ')'; - // checking for overlapping time frames - const overlapping = await entityManager.getRepository(TimeFrame) - .createQueryBuilder('timeframe') - .update() - values([]) - .select([ - 'timeframe.id' - ]) - .where('timeframe."cargoBikeId" = :id', { id: timeFrame.cargoBikeId }) - .andWhere('timeframe."dateRange" && :tr', { tr: timeFrame.dateRange }) - .getMany(); - console.log(overlapping); - if (overlapping.length !== 0) { - throw new UserInputError('TimeFrames with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping'); - } - inserts = await entityManager.getRepository(TimeFrame) - .createQueryBuilder('timeframe') - .insert() - .returning('*') - .values([timeFrame]) - .execute(); - await entityManager.getRepository(TimeFrame) - .createQueryBuilder() - .relation(TimeFrame, 'cargoBike') - .of(inserts.identifiers[0].id) - .set(timeFrame.cargoBikeId); - await entityManager.getRepository(TimeFrame) - .createQueryBuilder() - .relation(TimeFrame, 'lendingStation') - .of(inserts.identifiers[0].id) - .set(timeFrame.lendingStationId); - }); - } catch (e) { - console.log(e); - if (e instanceof UserInputError) { - return e; - } else if (e instanceof QueryFailedError) { - return e; + async lockTimeFrame (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, TimeFrame, 'tf', id, userId); + } + + async unlockTimeFrame (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, TimeFrame, 'tf', id, userId); + } + + async updateTimeFrame (timeFrame: any, userId: number) { + const keepLock = timeFrame.keepLock; + 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'); } - return new ApolloError('Transaction could not be completed'); - } - inserts.generatedMaps[0].id = inserts.identifiers[0].id; - return inserts.generatedMaps[0]; - } */ + genDateRange(timeFrame); + await ActionLogger.log(entityManager, TimeFrame, 'tf', timeFrame, userId); + + await entityManager.getRepository(TimeFrame) + .createQueryBuilder('timeframe') + .select([ + 'timeframe.id' + ]) + .where('timeframe."cargoBikeId" = :id', { id: timeFrame.cargoBikeId }) + .andWhere('timeframe."dateRange" && :tr', { tr: timeFrame.dateRange }) + .andWhere('timeFrame.id != :tid', { tid: timeFrame.id }) + .getMany().then(overlapping => { + if (overlapping.length !== 0) { + throw new UserInputError('TimeFrames with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping'); + } + }); + await entityManager.getRepository(TimeFrame) + .createQueryBuilder('tf') + .update() + .set({ ...timeFrame }) + .where('id = :id', { id: timeFrame.id }) + .execute() + .then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + }); + !keepLock && await this.unlockTimeFrame(timeFrame.id, userId); + return this.timeFrameById(timeFrame.id); + } } diff --git a/src/datasources/db/participantAPI.ts b/src/datasources/db/participantAPI.ts index d3d8a28..5761549 100644 --- a/src/datasources/db/participantAPI.ts +++ b/src/datasources/db/participantAPI.ts @@ -4,8 +4,9 @@ import { ContactInformation } from '../../model/ContactInformation'; import { Engagement } from '../../model/Engagement'; import { Participant } from '../../model/Participant'; import { EngagementType } from '../../model/EngagementType'; -import { genDateRange } from './utils'; +import { ActionLogger, genDateRange, LockUtils } from './utils'; import { UserInputError } from 'apollo-server'; +import { GraphQLError } from 'graphql'; export class ParticipantAPI extends DataSource { connection : Connection @@ -14,11 +15,11 @@ export class ParticipantAPI extends DataSource { this.connection = getConnection(); } - async getParticipantById (id: number) { + async participantById (id: number) { return await this.connection.getRepository(Participant) .createQueryBuilder('participant') .select() - .where('participant.id = :id', { id: id }) + .where('id = :id', { id: id }) .getOne(); } @@ -146,7 +147,34 @@ export class ParticipantAPI extends DataSource { .returning('*') .execute(); }); - return this.getParticipantById(inserts?.identifiers[0].id); + return this.participantById(inserts?.identifiers[0].id); + } + + async lockParticipant (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, Participant, 'p', id, userId); + } + + async unlockParticipant (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, Participant, 'p', id, userId); + } + + async updateParticipant (participant: any, userId: number) { + const keepLock = participant.keepLock; + 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'); + } + await ActionLogger.log(entityManager, Participant, 'p', participant, userId); + await entityManager.getRepository(Participant) + .createQueryBuilder('p') + .update() + .set({ ...participant }) + .where('id = :id', { id: participant.id }) + .execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + }); + !keepLock && await this.unlockParticipant(participant.id, userId); + return await this.participantById(participant.id); } async createEngagement (engagement: any) { @@ -174,6 +202,46 @@ export class ParticipantAPI extends DataSource { return this.engagementById(inserts?.identifiers[0].id); } + async lockEngagement (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, Engagement, 'e', id, userId); + } + + async unlockEngagement (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, Engagement, 'e', id, userId); + } + + async updateEngagement (engagement: any, userId: number) { + const keepLock = engagement.keepLock; + delete engagement.keepLock; + 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'); + } + await ActionLogger.log(entityManager, Engagement, 'e', engagement, userId); + // check for overlapping engagements + const overlapping = await entityManager.getRepository(Engagement) + .createQueryBuilder('e') + .select() + .where('e."cargoBikeId" = :id', { id: engagement.cargoBikeId }) + .andWhere('e."dateRange" && :dr', { dr: engagement.dateRange }) + .andWhere('e."engagementTypeId" = :etId', { etId: engagement.engagementTypeId }) + .andWhere('e.id != :eid', { eid: engagement.id }) + .getMany(); + if (overlapping.length > 0) { + throw new UserInputError('Engagements with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping'); + } + await entityManager.getRepository(Engagement) + .createQueryBuilder('engagement') + .update() + .set({ ...engagement }) + .where('id = :id', { id: engagement.id }) + .execute(); + }); + !keepLock && await LockUtils.unlockEntity(this.connection, Engagement, 'e', engagement.id, userId); + return await this.engagementById(engagement.id); + } + async createEngagementType (engagementType: any) { const inserts = await this.connection.getRepository(EngagementType) .createQueryBuilder('et') @@ -184,4 +252,31 @@ export class ParticipantAPI extends DataSource { inserts.generatedMaps[0].id = inserts.identifiers[0].id; return inserts.generatedMaps[0]; } + + async lockEngagementType (id:number, userId: number) { + return await LockUtils.lockEntity(this.connection, EngagementType, 'e', id, userId); + } + + async unlockEngagementType (id:number, userId: number) { + return await LockUtils.unlockEntity(this.connection, EngagementType, 'e', id, userId); + } + + async updateEngagementType (engagementType: any, userId: number) { + const keepLock = engagementType.keepLock; + 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'); + } + await ActionLogger.log(entityManager, EngagementType, 'et', engagementType, userId); + await entityManager.getRepository(EngagementType) + .createQueryBuilder('et') + .update() + .set({ ...engagementType }) + .where('id = :id', { id: engagementType.id }) + .execute(); + }); + !keepLock && await LockUtils.unlockEntity(this.connection, EngagementType, 'et', engagementType.id, userId); + return await this.engagementTypeById(engagementType.id); + } } diff --git a/src/datasources/db/providerAPI.ts b/src/datasources/db/providerAPI.ts index 21cea62..a7ffcf5 100644 --- a/src/datasources/db/providerAPI.ts +++ b/src/datasources/db/providerAPI.ts @@ -4,6 +4,9 @@ import { Provider } from '../../model/Provider'; import { Organisation } from '../../model/Organisation'; import { UserInputError } from 'apollo-server'; import { CargoBike } from '../../model/CargoBike'; +import { LendingStation } from '../../model/LendingStation'; +import { ActionLogger, LockUtils } from './utils'; +import { GraphQLError } from 'graphql'; export class ProviderAPI extends DataSource { connection : Connection @@ -13,19 +16,19 @@ export class ProviderAPI extends DataSource { } async providerById (id: number) { - await this.connection.getRepository(Provider) + return await this.connection.getRepository(Provider) .createQueryBuilder('provider') .select() - .where('provider.id = :id', { id: id }) - .getOne().catch(() => { return null; }); + .where('id = :id', { id: id }) + .getOne(); } async provider (offset: number, limit: number) { return await this.connection.getRepository(Provider) .createQueryBuilder('provider') .select() - .offset(offset) - .limit(limit) + .skip(offset) + .take(limit) .getMany(); } @@ -70,6 +73,22 @@ export class ProviderAPI extends DataSource { .getOne(); } + async organisationByLendingStationId (id: number) { + return await this.connection.getRepository(LendingStation) + .createQueryBuilder('ls') + .relation(LendingStation, 'organisationId') + .of(id) + .loadOne(); + } + + async lendingStationByOrganisationId (id: number) { + return await this.connection.getRepository(Organisation) + .createQueryBuilder('o') + .relation(Organisation, 'lendingStations') + .of(id) + .loadMany(); + } + async contactInformationByOrganisationId (id: number) { return await this.connection.getRepository(Organisation) .createQueryBuilder('o') @@ -107,6 +126,40 @@ export class ProviderAPI extends DataSource { return ret; } + async lockProvider (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, Provider, 'p', id, userId); + } + + async unlockProvider (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, Provider, 'p', id, userId); + } + + async updateProvider (provider: any, userId: number) { + const keepLock = provider.keepLock; + delete provider.keepLock; + const cargoBikes = provider.cargoBikeIds; + 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'); + } + await ActionLogger.log(entityManager, Provider, 'p', provider, userId); + await entityManager.getRepository(Provider) + .createQueryBuilder('p') + .update() + .set({ ...provider }) + .where('id = :id', { id: provider.id }) + .execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + await entityManager.getRepository(Provider) + .createQueryBuilder('p') + .relation(Provider, 'cargoBikeIds') + .of(provider.id) + .add(cargoBikes); + }); + !keepLock && await this.unlockProvider(provider.id, userId); + return await this.providerById(provider.id); + } + async createOrganisation (organisation: any) { let inserts: any = null; await this.connection.transaction(async (entityManager: EntityManager) => { @@ -115,11 +168,34 @@ export class ProviderAPI extends DataSource { .insert() .values([organisation]) .execute(); - await entityManager.getRepository(Organisation).createQueryBuilder() - .relation(Organisation, 'providerId') - .of(inserts.identifiers[0].id) - .set(organisation.providerId); }); return inserts.generatedMaps[0]; } + + async lockOrganisation (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, Organisation, 'o', id, userId); + } + + async unlockOrganisation (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, Organisation, 'o', id, userId); + } + + async updateOrganisation (organisation: any, userId: number) { + const keepLock = organisation.keepLock; + 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'); + } + await ActionLogger.log(entityManager, Organisation, 'o', organisation, userId); + await entityManager.getRepository(Organisation) + .createQueryBuilder('o') + .update() + .set({ ...organisation }) + .where('id = :id', { id: organisation.id }) + .execute(); + }); + !keepLock && await LockUtils.unlockEntity(this.connection, Organisation, 'o', organisation.id, userId); + return this.organisationById(organisation.id); + } } diff --git a/src/datasources/db/utils.ts b/src/datasources/db/utils.ts index fab64ac..d402930 100644 --- a/src/datasources/db/utils.ts +++ b/src/datasources/db/utils.ts @@ -1,11 +1,9 @@ -import { Connection, ObjectType } from 'typeorm'; -import { CargoBike, Lockable } from '../../model/CargoBike'; +import { Connection, EntityManager, ObjectType } from 'typeorm'; +import { Lockable } from '../../model/CargoBike'; import { GraphQLError } from 'graphql'; +import { ActionLog } from '../../model/ActionLog'; export function genDateRange (struct: any) { - if (struct.to === undefined) { - struct.to = ''; - } if (struct.to === undefined) { struct.to = ''; } @@ -13,6 +11,9 @@ export function genDateRange (struct: any) { if (struct.from === undefined) { delete struct.dateRange; } + // delete these keys, so the struct can be used to update the engagement entity + delete struct.from; + delete struct.to; } /** @@ -115,8 +116,8 @@ export class LockUtils { * @param req * @param dataSources */ - static async isLocked (connection: Connection, target: ObjectType, alias: string, id: number, userId: number) { - const lock = await connection.getRepository(CargoBike) + static async isLocked (connection: EntityManager, target: ObjectType, alias: string, id: number, userId: number) { + const lock = await connection.getRepository(target) .createQueryBuilder(alias) .select([ alias + '.lockedUntil', @@ -152,3 +153,61 @@ export class LockUtils { return result === 1; } } + +export class ActionLogger { + 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 + const ret :string[] = []; + Object.keys(updates).forEach(value => { + if (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() + '"'); + }); + } else { + ret.push(alias + '."' + value + '"'); + } + }); + return ret; + } + + static async log (em: EntityManager, target: ObjectType, alias: string, updates: any, userId: number) { + const oldValues = await em.getRepository(target).createQueryBuilder(alias) + .select(this.buildSelect(updates, alias)) + .where('id = :id', { id: updates.id }) + .getRawOne().then(value => { + if (value === undefined) { + throw new GraphQLError('Id not found'); + } + return value; + }); // use getRawOne to also get ids of related entities + + Object.keys(oldValues).forEach(value => { + if (value.match(alias + '_')) { + oldValues[value.replace(alias + '_', '')] = oldValues[value]; + delete oldValues[value]; + } + }); + // TODO: check if new values are different from old note: the commented section will probably fail for nested objects. + /* + const newValues = { ...updates }; // copy updates to mimic call by value + Object.keys(updates).forEach((key, i) => { + // eslint-disable-next-line eqeqeq + if (newValues[key] == oldValues[key]) { + delete newValues[key]; + delete oldValues[key]; + } + }); + */ + const logEntry : ActionLog = { + userId: userId, + entity: target.name, + entriesOld: JSON.stringify(oldValues), + entriesNew: JSON.stringify(updates) + }; + await em.getRepository(ActionLog) + .createQueryBuilder('al') + .insert() + .values([logEntry]) + .execute(); + } +} diff --git a/src/datasources/db/workshopAPI.ts b/src/datasources/db/workshopAPI.ts index 9c954bd..05a016c 100644 --- a/src/datasources/db/workshopAPI.ts +++ b/src/datasources/db/workshopAPI.ts @@ -1,7 +1,10 @@ import { DataSource } from 'apollo-datasource'; -import { Connection, getConnection } from 'typeorm'; +import { Connection, EntityManager, getConnection } from 'typeorm'; import { WorkshopType } from '../../model/WorkshopType'; import { Workshop } from '../../model/Workshop'; +import { ActionLogger, LockUtils } from './utils'; +import { UserInputError } from 'apollo-server-express'; +import { GraphQLError } from 'graphql'; export class WorkshopAPI extends DataSource { connection: Connection @@ -21,6 +24,34 @@ export class WorkshopAPI extends DataSource { return inserts.generatedMaps[0]; } + async lockWorkshop (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, Workshop, 'w', id, userId); + } + + async unlockWorkshop (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, Workshop, 'w', id, userId); + } + + async updateWorkshop (workshop: any, userId: number) { + const keepLock = workshop.keepLock; + 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'); + } + await ActionLogger.log(entityManger, Workshop, 'w', workshop, userId); + await entityManger.getRepository(Workshop) + .createQueryBuilder('w') + .update() + .set({ ...workshop }) + .where('id = :id', { id: workshop.id }) + .execute() + .then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + }); + !keepLock && await this.unlockWorkshop(workshop.id, userId); + return await this.workshopById(workshop.id); + } + async createWorkshopType (workshopType: any) { const inserts = await this.connection.getRepository(WorkshopType) .createQueryBuilder('wt') @@ -31,6 +62,34 @@ export class WorkshopAPI extends DataSource { return inserts.generatedMaps[0]; } + async lockWorkshopType (id: number, userId: number) { + return await LockUtils.lockEntity(this.connection, WorkshopType, 'wt', id, userId); + } + + async unlockWorkshopType (id: number, userId: number) { + return await LockUtils.unlockEntity(this.connection, WorkshopType, 'wt', id, userId); + } + + async updateWorkshopType (workshopType : any, userId: number) { + const keepLock = workshopType.keepLock; + 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'); + } + await ActionLogger.log(entityManager, WorkshopType, 'wt', workshopType, userId); + await entityManager.getRepository(WorkshopType) + .createQueryBuilder('wt') + .update() + .set({ ...workshopType }) + .where('id = :id', { id: workshopType.id }) + .execute() + .then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } }); + }); + !keepLock && await this.unlockWorkshopType(workshopType.id, userId); + return await this.workshopTypeById(workshopType.id); + } + async workshopTypeById (id: number) { return await this.connection.getRepository(WorkshopType) .createQueryBuilder('wt') diff --git a/src/index.ts b/src/index.ts index 370f838..3de3fa1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import { EquipmentType } from './model/EquipmentType'; import { BikeEventType } from './model/BikeEventType'; import { WorkshopAPI } from './datasources/db/workshopAPI'; import workshopResolvers from './resolvers/workshopResolvers'; +import { ActionLog } from './model/ActionLog'; require('dotenv').config(); @@ -84,7 +85,8 @@ createConnection({ EngagementType, Workshop, Person, - WorkshopType + WorkshopType, + ActionLog ], synchronize: true, logging: false diff --git a/src/model/ActionLog.ts b/src/model/ActionLog.ts new file mode 100644 index 0000000..c502678 --- /dev/null +++ b/src/model/ActionLog.ts @@ -0,0 +1,26 @@ +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity() +export class ActionLog { + @PrimaryGeneratedColumn() + id?: number; + + @CreateDateColumn() + date?: Date; + + @Column() + userId: number; + + @Column() + entity: string; + + @Column({ + type: 'text' + }) + entriesOld: string; + + @Column({ + type: 'text' + }) + entriesNew: string; +} diff --git a/src/model/CargoBike.ts b/src/model/CargoBike.ts index 6543e45..7b6dd82 100644 --- a/src/model/CargoBike.ts +++ b/src/model/CargoBike.ts @@ -154,7 +154,7 @@ export class CargoBike implements Lockable { @Column() name: string; - @OneToMany(type => Equipment, equipment => equipment.cargoBike, { + @OneToMany(type => Equipment, equipment => equipment.cargoBikeId, { nullable: true, eager: true }) diff --git a/src/model/Engagement.ts b/src/model/Engagement.ts index 621e819..05aa5d9 100644 --- a/src/model/Engagement.ts +++ b/src/model/Engagement.ts @@ -39,30 +39,6 @@ export class Engagement implements Lockable { }) dateRange: Date[]; - @Column({ - type: 'boolean', - default: false - }) - roleCoordinator: boolean; - - @Column({ - type: 'boolean', - default: false - }) - roleMentor: boolean; - - @Column({ - type: 'boolean', - default: false - }) - roleAmbulance: boolean; - - @Column({ - type: 'boolean', - default: false - }) - roleBringer: boolean; - @Column({ nullable: true, type: 'timestamp' diff --git a/src/model/Equipment.ts b/src/model/Equipment.ts index a4d9b9b..976beb3 100644 --- a/src/model/Equipment.ts +++ b/src/model/Equipment.ts @@ -23,7 +23,7 @@ export class Equipment implements Lockable { @JoinColumn({ name: 'cargoBikeId', referencedColumnName: 'id' }) - cargoBike: CargoBike; + cargoBikeId: number; @Column({ type: 'timestamp', diff --git a/src/model/LendingStation.ts b/src/model/LendingStation.ts index 00a567e..1fcb973 100644 --- a/src/model/LendingStation.ts +++ b/src/model/LendingStation.ts @@ -25,7 +25,8 @@ export class LoanPeriod { to: Date; @Column({ - type: 'simple-array' + type: 'simple-array', + nullable: true }) loanTimes: string[]; } diff --git a/src/model/Organisation.ts b/src/model/Organisation.ts index 5e24fb7..e83ddca 100644 --- a/src/model/Organisation.ts +++ b/src/model/Organisation.ts @@ -35,7 +35,7 @@ export class Organisation implements Lockable { @Column({ nullable: true }) - registerNo: string; + associationNo: string; @Column(type => Address) address: Address; diff --git a/src/model/TimeFrame.ts b/src/model/TimeFrame.ts index c02686d..d1a6a44 100644 --- a/src/model/TimeFrame.ts +++ b/src/model/TimeFrame.ts @@ -15,7 +15,7 @@ export class TimeFrame implements Lockable { }) dateRange: Date[]; - @ManyToOne(type => LendingStation, lendingStation => lendingStation.timeFrames) + @ManyToOne(type => LendingStation, lendingStation => lendingStation.timeFrames, { nullable: false }) @JoinColumn({ name: 'lendingStationId' }) @@ -26,7 +26,7 @@ export class TimeFrame implements Lockable { }) note: string; - @ManyToOne(type => CargoBike, cargoBike => cargoBike.timeFrames) + @ManyToOne(type => CargoBike, cargoBike => cargoBike.timeFrames, { nullable: false }) @JoinColumn({ name: 'cargoBikeId' }) diff --git a/src/resolvers/cargobikeResolver.ts b/src/resolvers/cargobikeResolver.ts index b1023f0..4e558d0 100644 --- a/src/resolvers/cargobikeResolver.ts +++ b/src/resolvers/cargobikeResolver.ts @@ -27,14 +27,14 @@ export default { }, bikeEventById: (_:any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBikeEvent)) { - return dataSources.cargoBikeAPI.findBikeEventById(id); + return dataSources.cargoBikeAPI.bikeEventById(id); } else { return new GraphQLError('Insufficient Permissions'); } }, bikeEventTypeByd: (_:any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBikeEvent)) { - return dataSources.cargoBikeAPI.findBikeEventTypeById(id); + return dataSources.cargoBikeAPI.bikeEventTypeById(id); } else { return new GraphQLError('Insufficient Permissions'); } @@ -229,6 +229,13 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + updateBikeEvent: (_: any, { bikeEvent }: { bikeEvent: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteBikeEvent)) { + return dataSources.cargoBikeAPI.updateBikeEvent(bikeEvent, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, createEquipment: (_: any, { equipment }: { equipment: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteEquipment)) { return dataSources.cargoBikeAPI.createEquipment({ equipment }); @@ -264,12 +271,54 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + lockEquipmentType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEquipmentType)) { + return dataSources.cargoBikeAPI.lockEquipmentType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockEquipmentType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEquipmentType)) { + return dataSources.cargoBikeAPI.unlockEquipmentType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateEquipmentType: (_: any, { equipmentType }: { equipmentType: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEquipmentType)) { + return dataSources.cargoBikeAPI.updateEquipmentType(equipmentType, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, createBikeEventType: (_: any, { name }: { name: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteEventType)) { return dataSources.cargoBikeAPI.createBikeEventType(name); } else { return new GraphQLError('Insufficient Permissions'); } + }, + lockBikeEventType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEventType)) { + return dataSources.cargoBikeAPI.lockBikeEventType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockBikeEventType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEventType)) { + return dataSources.cargoBikeAPI.unlockBikeEventType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateBikeEventType: (_: any, { bikeEventType }: { bikeEventType: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEventType)) { + return dataSources.cargoBikeAPI.updateBikeEventType(bikeEventType, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } } } }; diff --git a/src/resolvers/contactinformationResolvers.ts b/src/resolvers/contactinformationResolvers.ts index c9b8851..2e09595 100644 --- a/src/resolvers/contactinformationResolvers.ts +++ b/src/resolvers/contactinformationResolvers.ts @@ -68,6 +68,48 @@ export default { } else { return new GraphQLError('Insufficient Permissions'); } + }, + lockPerson: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.lockPerson(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockPerson: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.unlockPerson(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updatePerson: (_: any, { person }: { person: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.updatePerson(person, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + lockContactInformation: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.lockContactInformation(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockContactInformation: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.unlockContactInformation(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateContactInformation: (_: any, { contactInformation }: { contactInformation: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WritePerson)) { + return dataSources.contactInformationAPI.updateContactInformation(contactInformation, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } } } }; diff --git a/src/resolvers/lendingstationResolvers.ts b/src/resolvers/lendingstationResolvers.ts index 6fa4668..a1df1da 100644 --- a/src/resolvers/lendingstationResolvers.ts +++ b/src/resolvers/lendingstationResolvers.ts @@ -7,7 +7,7 @@ export default { Query: { lendingStationById: (_: any, { id }: { id: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadLendingStation)) { - return dataSources.lendingStationAPI.lendingStationById({ id }); + return dataSources.lendingStationAPI.lendingStationById(id); } else { return new GraphQLError('Insufficient Permissions'); } @@ -56,11 +56,32 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + contactInformationIntern (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { + if (req.permissions.includes(Permission.ReadPerson)) { + return dataSources.contactInformationAPI.contactInternByLendingStationId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + contactInformationExtern (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { + if (req.permissions.includes(Permission.ReadPerson)) { + return dataSources.contactInformationAPI.contactExternByLendingStationId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + organisation (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { + if (req.permissions.includes(Permission.ReadOrganisation)) { + return dataSources.providerAPI.organisationByLendingStationId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, isLocked: (parent: any, __: any, { dataSources, req }: { dataSources: any; req: any }) => isLocked(parent, { dataSources, req }) }, LoanPeriod: { loanTimes (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) { - return parent.loanTimes.split(','); + return parent.loanTimes ? parent.loanTimes : []; } }, TimeFrame: { @@ -111,7 +132,7 @@ export default { }, updateLendingStation: (_: any, { lendingStation }:{ lendingStation: LendingStation }, { dataSources, req }:{dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteLendingStation)) { - return dataSources.lendingStationAPI.updateLendingStation({ lendingStation }); + return dataSources.lendingStationAPI.updateLendingStation(lendingStation, req.userId); } else { return new GraphQLError('Insufficient Permissions'); } @@ -129,6 +150,20 @@ export default { } else { return new GraphQLError('Insufficient Permissions'); } + }, + unlockTimeFrame: (_: any, { id }:{ id: number }, { dataSources, req }:{dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteTimeFrame)) { + return dataSources.lendingStationAPI.unlockTimeFrame(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateTimeFrame: (_: any, { timeFrame }:{ timeFrame: LendingStation }, { dataSources, req }:{dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteTimeFrame)) { + return dataSources.lendingStationAPI.updateTimeFrame(timeFrame, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } } } }; diff --git a/src/resolvers/participantResolvers.ts b/src/resolvers/participantResolvers.ts index 25c00d7..d6c5033 100644 --- a/src/resolvers/participantResolvers.ts +++ b/src/resolvers/participantResolvers.ts @@ -103,6 +103,27 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + lockParticipant: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteParticipant)) { + return dataSources.participantAPI.lockeParticipant(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockParticipant: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteParticipant)) { + return dataSources.participantAPI.unlockeParticipant(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateParticipant: (_: any, { participant }: { participant: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteParticipant)) { + return dataSources.participantAPI.updateParticipant(participant, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, createEngagement: (_: any, { engagement }: { engagement: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteEngagement)) { return dataSources.participantAPI.createEngagement(engagement); @@ -110,6 +131,48 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + lockEngagement: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEngagement)) { + return dataSources.participantAPI.lockEngagement(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockEngagement: (_: any, { id }: {id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEngagement)) { + return dataSources.participantAPI.unlockngagement(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateEngagement: (_: any, { engagement }: { engagement: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEngagement)) { + return dataSources.participantAPI.updateEngagement(engagement, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + lockEngagementType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEngagementType)) { + return dataSources.participantAPI.lockEngagementType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockEngagementType: (_: any, { id }: {id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEngagementType)) { + return dataSources.participantAPI.unlockngagementType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateEngagementType: (_: any, { engagementType }: { engagementType: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteEngagementType)) { + return dataSources.participantAPI.updateEngagementType(engagementType, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, createEngagementType: (_: any, { engagementType }: { engagementType: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteEngagementType)) { return dataSources.participantAPI.createEngagementType(engagementType); diff --git a/src/resolvers/providerResolvers.ts b/src/resolvers/providerResolvers.ts index 3d660b1..862e08e 100644 --- a/src/resolvers/providerResolvers.ts +++ b/src/resolvers/providerResolvers.ts @@ -72,22 +72,71 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, + lendingStations: (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.ReadLendingStation)) { + return dataSources.providerAPI.lendingStationByOrganisationId(parent.id); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, isLocked: (parent: any, __: any, { dataSources, req }: { dataSources: any; req: any }) => isLocked(parent, { dataSources, req }) }, Mutation: { - createProvider: (_: any, { provider }: { provider: number }, { dataSources, req }: { dataSources: any, req: any }) => { + createProvider: (_: any, { provider }: { provider: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteProvider)) { return dataSources.providerAPI.createProvider(provider); } else { return new GraphQLError('Insufficient Permissions'); } }, + lockProvider: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteProvider)) { + return dataSources.providerAPI.lockProvider(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockProvider: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteProvider)) { + return dataSources.providerAPI.unlockProvider(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateProvider: (_: any, { provider }: { provider: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteProvider)) { + return dataSources.providerAPI.updateProvider(provider, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, createOrganisation: (_: any, { organisation }: { organisation: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteOrganisation)) { return dataSources.providerAPI.createOrganisation(organisation); } else { return new GraphQLError('Insufficient Permissions'); } + }, + lockOrganisation: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteOrganisation)) { + return dataSources.providerAPI.lockOrganisation(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockOrganisation: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteOrganisation)) { + return dataSources.providerAPI.unlockOrganisation(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateOrganisation: (_: any, { organisation }: { organisation: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteOrganisation)) { + return dataSources.providerAPI.updateOrganisation(organisation, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } } } }; diff --git a/src/resolvers/workshopResolvers.ts b/src/resolvers/workshopResolvers.ts index 8f3a910..ac322e2 100644 --- a/src/resolvers/workshopResolvers.ts +++ b/src/resolvers/workshopResolvers.ts @@ -61,12 +61,54 @@ export default { return new GraphQLError('Insufficient Permissions'); } }, - createWorkshopType: (_: any, { workshopType }: { workshopType: number }, { dataSources, req }: { dataSources: any, req: any }) => { + lockWorkshop: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteWorkshop)) { + return dataSources.workshopAPI.lockWorkshop(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockWorkshop: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteWorkshop)) { + return dataSources.workshopAPI.unlockWorkshop(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateWorkshop: (_: any, { workshop }: { workshop: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteWorkshop)) { + return dataSources.workshopAPI.updateWorkshop(workshop, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + createWorkshopType: (_: any, { workshopType }: { workshopType: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteWorkshopType)) { return dataSources.workshopAPI.createWorkshopType(workshopType); } else { return new GraphQLError('Insufficient Permissions'); } + }, + lockWorkshopType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteWorkshopType)) { + return dataSources.workshopAPI.lockWorkshopType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + unlockWorkshopType: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteWorkshopType)) { + return dataSources.workshopAPI.unlockWorkshopType(id, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } + }, + updateWorkshopType: (_: any, { workshopType }: { workshopType: any }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.WriteWorkshopType)) { + return dataSources.workshopAPI.updateWorkshopType(workshopType, req.userId); + } else { + return new GraphQLError('Insufficient Permissions'); + } } } }; diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index a586fb4..0afe6cc 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -344,6 +344,23 @@ input ParticipantCreateInput { memberCoreTeam: Boolean } +input ParticipantUpdateInput { + id: ID! + "if not set, CURRENT_DATE will be used" + start: Date + end: Date + "must create contactinformation first, if you want to use new" + contactInformationId: ID + usernamefLotte: String + usernameSlack: String + "default: false" + memberADFC: Boolean + locationZIPs: [String] + "default: false" + memberCoreTeam: Boolean + keepLock: Boolean +} + type Workshop { id: ID! title: String! @@ -367,6 +384,17 @@ input WorkshopCreateInput { trainer2Id: ID } +input WorkshopUpdateInput { + id: ID! + title: String + description: String + date: Date! + workshopTypeId: ID + trainer1Id: ID + trainer2Id: ID + keepLock: Boolean +} + type WorkshopType { id: ID! name: String! @@ -380,6 +408,11 @@ input WorkshopTypeCreateInput { name: String! } +input WorkshopTypeUpdateInput { + id: ID! + name: String +} + type EngagementType { id: ID! name: String! @@ -395,6 +428,13 @@ input EngagementTypeCreateInput { description: String } +input EngagementTypeUpdateInput { + id: ID! + name: String + description: String + keepLock: Boolean +} + type Engagement { id: ID! engagementType: EngagementType! @@ -402,14 +442,6 @@ type Engagement { to: Date participant: Participant! cargoBike: CargoBike! - roleCoordinator: Boolean! - roleEmployeeADFC: Boolean! - """ - Wahr, wenn die Person Pate ist. - """ - roleMentor: Boolean! - roleAmbulance: Boolean! - roleBringer: Boolean! isLocked: Boolean! "null if not locked by other user" lockedBy: ID @@ -424,19 +456,15 @@ input EngagementCreateInput { to: Date participantId: ID! cargoBikeId: ID! - "default: false" - roleCoordinator: Boolean - "default: false" - roleEmployeeADFC: Boolean - """ - Wahr, wenn die Person Pate ist. - default: false - """ - roleMentor: Boolean - "default: false" - roleAmbulance: Boolean - "default: false" - roleBringer: Boolean +} +input EngagementUpdateInput { + id: ID! + engagementTypeId: ID + from: Date + to: Date + participantId: ID + cargoBikeId: ID + keepLock: Boolean } type Taxes { @@ -511,6 +539,7 @@ input EquipmentTypeUpdateInput { id: ID! name: String description: String + keepLock: Boolean } "An Event is a point in time, when the state of the bike somehow changed." @@ -547,6 +576,22 @@ input BikeEventCreateInput { remark: String } +input BikeEventUpdateInput { + id: ID! + bikeEventTypeId: ID + cargoBikeId: ID + responsibleId: ID + relatedId: ID + date: Date + description: String + """ + Path to documents + """ + documents: [String] + remark: String + keepLock: Boolean +} + type BikeEventType { id: ID! name: String! @@ -554,9 +599,10 @@ type BikeEventType { lockedUntil: Date } -input BikeEventTypeInput { +input BikeEventTypeUpdateInput { id: ID! name: String + keepLock: Boolean } "(dt. Anbieter) bezieht sich auf die Beziehung einer Person oder Organisation zum Lastenrad" @@ -580,6 +626,16 @@ input ProviderCreateInput { cargoBikeIds: [ID] } +input ProviderUpdateInput { + id: ID! + formName: String + privatePersonId: ID + organisationId: ID + "cargoBikes are added, you can not take existing relations away. use update cargoBike or add bike to another provider instead" + cargoBikeIds: [ID] + keepLock: Boolean +} + """ A Person can have several instances of contact information. The reason for this is, that some people have info for interns and externals that are different. @@ -600,6 +656,13 @@ input PersonCreateInput { firstName: String! } +input PersonUpdateInput { + id: ID! + name: String + firstName: String + keepLock: Boolean +} + type ContactInformation { id: ID! person: Person! @@ -631,6 +694,7 @@ input ContactInformationUpdateInput { email: String email2: String note: String + keepLock: Boolean } type Organisation { @@ -655,17 +719,27 @@ type Organisation { input OrganisationCreateInput { address: AddressCreateInput! name: String! - "(dt. Ausleihstation)" - lendingStationIds: [ID] "registration number of association" associationNo: String! "If Club, at what court registered" registeredAt: String - providerId: ID contactInformationId: ID otherData: String } +input OrganisationUpdateInput { + id: ID! + address: AddressCreateInput + name: String + "registration number of association" + associationNo: String + "If Club, at what court registered" + registeredAt: String + contactInformationId: ID + otherData: String + keepLock: Boolean +} + "(dt. Standort)" type LendingStation { id: ID! @@ -678,6 +752,7 @@ type LendingStation { cargoBikes: [CargoBike] "Total amount of cargoBikes currently assigned to the lending station" numCargoBikes: Int! + organisation: Organisation isLocked: Boolean! "null if not locked by other user" lockedBy: ID @@ -693,6 +768,7 @@ input LendingStationCreateInput { contactInformationExternId: ID address: AddressCreateInput! loanPeriod: LoanPeriodInput + organisationId: ID } """ @@ -701,9 +777,12 @@ If you want to create LendingStation with cargoBikes, use createTimeFrame and se input LendingStationUpdateInput { id: ID! name: String - contactInformation: [ContactInformationUpdateInput] + contactInformationInternId: ID + contactInformationExternId: ID address: AddressUpdateInput loanPeriod: LoanPeriodInput + organisationId: ID + keepLock: Boolean } """ @@ -763,8 +842,8 @@ input TimeFrameUpdateInput { from: Date to: Date note: String - lendingStation: ID - cargoBike: ID + lendingStationId: ID + cargoBikeId: ID keepLock: Boolean } @@ -824,7 +903,7 @@ type Query { bikeEventTypes(offset: Int!, limit: Int!): [BikeEventType] bikeEventTypeByd(id: ID!): BikeEventType bikeEvents(offset: Int!, limit: Int!): [BikeEvent]! - bikeEventById(id:ID!): BikeEvent! + bikeEventById(id:ID!): BikeEvent } type Mutation { @@ -851,6 +930,9 @@ type Mutation { "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! createEquipmentType(equipmentType: EquipmentTypeCreateInput!): EquipmentType! + lockEquipmentType(id: ID!): EquipmentType! + unlockEquipmentType(id: ID!): Boolean! + updateEquipmentType(equipmentType: EquipmentTypeUpdateInput!): EquipmentType! """ LENDINGSTATION creates new lendingStation and returns lendingStation with new ID @@ -861,29 +943,62 @@ type Mutation { "updates lendingStation of given ID with supplied fields and returns updated lendingStation" updateLendingStation(lendingStation: LendingStationUpdateInput!): LendingStation! createTimeFrame(timeFrame: TimeFrameCreateInput!): TimeFrame! - lockTimeFrame(id: ID!): TimeFrame + lockTimeFrame(id: ID!): TimeFrame! + unlockTimeFrame(id: ID!): Boolean! + updateTimeFrame(timeFrame: TimeFrameUpdateInput!): TimeFrame! """ BIKEEVENT """ createBikeEventType(name: String!): BikeEventType! + lockBikeEventType(id: ID!): BikeEventType! + unlockBikeEventType(id:ID!): Boolean! + updateBikeEventType(bikeEventType: BikeEventTypeUpdateInput!): BikeEventType! "creates new BikeEvent" createBikeEvent(bikeEvent: BikeEventCreateInput!): BikeEvent! lockBikeEventById(id: ID!): BikeEvent unlockBikeEventById(id: ID!): Boolean! + updateBikeEvent(bikeEvent: BikeEventUpdateInput!): BikeEvent """ PARTICIPANTS """ createParticipant(participant: ParticipantCreateInput!): Participant! + lockParticipant(id: ID!): Participant! + unlockParticipant(id: ID!): Boolean + updateParticipant(participant: ParticipantUpdateInput!): Participant! createWorkshopType(workshopType: WorkshopTypeCreateInput!): WorkshopType! + lockWorkshopType(id: ID!): WorkshopType! + unlockWorkshopType(id: ID!): Boolean! + updateWorkshopType(workshopType: WorkshopTypeUpdateInput!): WorkshopType! createWorkshop(workshop: WorkshopCreateInput!): Workshop! + lockWorkshop(id: ID!): Workshop! + unlockWorkshop(id: ID!): Boolean! + updateWorkshop(workshop: WorkshopUpdateInput!): Workshop! "create new contactInfo" createContactInformation(contactInformation: ContactInformationCreateInput!): ContactInformation! + lockContactInformation(id: ID!): ContactInformation! + unlockContactInformation(id: ID!): Boolean! + updateContactInformation(contactInformation: ContactInformationUpdateInput!): ContactInformation! createPerson(person: PersonCreateInput!): Person! + lockPerson(id: ID!): Person! + unlockPerson(id: ID!): Person! + updatePerson(person: PersonUpdateInput!): Person! + lockEngagement(id: ID!): Engagement! + unlockEngagement(id: ID!): Boolean! + updateEngagement(engagement: EngagementUpdateInput!): Engagement! createEngagementType(engagementType: EngagementTypeCreateInput!): EngagementType! + lockEngagementType(id: ID!): EngagementType! + unlockEngagementType(id: ID!): Boolean! + updateEngagementType(engagementType: EngagementTypeUpdateInput!): EngagementType! "create Engagement" createEngagement(engagement: EngagementCreateInput): Engagement! createProvider(provider: ProviderCreateInput!): Provider! + lockProvider(id: ID!): Provider! + unlockProvider(id: ID!): Boolean! + updateProvider(provider: ProviderUpdateInput!): Provider! createOrganisation(organisation: OrganisationCreateInput!): Organisation! + lockOrganisation(id: ID!): Organisation! + unlockOrganisation(id: ID!): Boolean! + updateOrganisation(organisation: OrganisationUpdateInput!): Organisation! } `;