Merge branch 'develop' of github.com:fLotte-meets-HWR-DB/apollo-server into develop

pull/28/head
trivernis 4 years ago
commit 38f5a8a1fe
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -0,0 +1,37 @@
name: Build Docker Image
on:
push:
branches: [ main, dev ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Copy Repo Files
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Portus
uses: docker/login-action@v1
with:
registry: https://flotte-docker-registry.spdns.org/
username: ${{ secrets.PORTUS_USERNAME }}
password: ${{ secrets.PORTUS_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: flotte-docker-registry.spdns.org/apollo-server:latest

2
.gitignore vendored

@ -2,4 +2,4 @@ node_modules
dist
.env
.idea
.nyc_output
.nyc_output

@ -4,14 +4,16 @@ Apollo server written in typescript that handles business logic.
[![Build Status](https://travis-ci.com/fLotte-meets-HWR-DB/apollo-server.svg?token=YfRmpHAXqyUafCgSEexw&branch=main)](https://travis-ci.com/fLotte-meets-HWR-DB/apollo-server)
## Assumptions
Userserver and postgres are running e.g. with Julius' Docker Compose.
The [flotte-user-management server](https://github.com/fLotte-meets-HWR-DB/flotte-user-management) and postgres are running. Set the [environment variables](#Environment-Variables) accordingly.
## Usage
### Docker
You can build and run a docker image with
```bash
docker build -t <image name> .
docker run --rm -p 4000:4000 <image name>
docker run --rm -p 4000:4000 <image name> -e ...
```
### Compile and run
Don't forget to pass all the [environment variables](#Environment-Variables) with the -e option.
### Compile and Run
Install gulp if not installed
```bash
npm -g gulp
@ -21,25 +23,28 @@ npm install
gulp
npm start
```
You can set the [environment variables](#Environment-Variables) in a _.env_ file.
### For Development
Install node\_modules and gulp
```bash
npm -g gulp
npm install
```
And start gulp in watch mode
Start gulp in watch mode to recompile the type script
```bash
gulp watch
gulp watchTs
```
This will watch *.ts files in _./src_ and recompile to _./dist_ and finally restart the server.
This will watch *.ts files in _./src_ and recompile to _./dist_. You will have to restart the server yourself.
## Environment Variables
The following environment variables can be used to configure the server:
```bash
RPC_HOST=host:port
NODE_ENV=development/porduction
NODE_ENV=develop/production
POSTGRES_CONNECTION_URL=postgres://username:password@host:port/database_name
```
- __RPC_HOST__ is used for the connection with the userserver.
- __RPC_HOST__ is used for the connection with the [flotte-user-management server](https://github.com/fLotte-meets-HWR-DB/flotte-user-management).
- __NODE_ENV__ will not check authentication if set to development
- __POSTGRES_CONNECTION_URL__ for connection with the postgres database
If the API server cannot connect to the [flotte-user-management server](https://github.com/fLotte-meets-HWR-DB/flotte-user-management) or the postgres data base. It will try to reconnect in an endless loop.

@ -26,7 +26,7 @@ import { Equipment } from '../../model/Equipment';
import { Engagement } from '../../model/Engagement';
import { Provider } from '../../model/Provider';
import { TimeFrame } from '../../model/TimeFrame';
import { ActionLogger, deleteEntity, LockUtils } from './utils';
import { ActionLogger, DBUtils, genBoxDimensions, LockUtils } from './utils';
import { EquipmentType } from '../../model/EquipmentType';
import { BikeEventType } from '../../model/BikeEventType';
import { UserInputError } from 'apollo-server-express';
@ -42,14 +42,8 @@ export class CargoBikeAPI extends DataSource {
this.connection = getConnection();
}
async getCargoBikes (offset: number, limit: number) {
return await this.connection.createQueryBuilder()
.select('cargoBike')
.from(CargoBike, 'cargoBike')
.orderBy('name', 'ASC')
.offset(offset)
.limit(limit)
.getMany();
async getCargoBikes (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, CargoBike, 'cb', offset, limit);
}
/**
@ -104,12 +98,13 @@ export class CargoBikeAPI extends DataSource {
async updateCargoBike (cargoBike: any, userId:number) {
const keepLock = cargoBike?.keepLock;
delete cargoBike.keepLock;
delete cargoBike.lendingStationId;
let equipmentTypeIds: any = null;
if (cargoBike.equipmentTypeIds) {
equipmentTypeIds = cargoBike.equipmentTypeIds;
delete cargoBike.equipmentTypeIds;
}
const equipmentTypeIds = cargoBike?.equipmentTypeIds;
delete cargoBike?.equipmentTypeIds;
const equipmentIds = cargoBike?.equipmentIds;
delete cargoBike?.equipmentIds;
// generate ranges for box dimensions
genBoxDimensions(cargoBike);
await this.connection.transaction(async (entityManager: EntityManager) => {
if (await LockUtils.isLocked(entityManager, CargoBike, 'cb', cargoBike.id, userId)) {
throw new GraphQLError('CargoBike locked by other user');
@ -125,7 +120,12 @@ export class CargoBikeAPI extends DataSource {
.createQueryBuilder('cb')
.relation(CargoBike, 'equipmentTypeIds')
.of(cargoBike.id)
.addAndRemove(equipmentTypeIds, await this.equipmentTypeByCargoBikeId(cargoBike.id)); // TODO remove all existing relations
.addAndRemove(equipmentTypeIds, await this.equipmentTypeByCargoBikeId(cargoBike.id));
equipmentIds && await entityManager.getRepository(CargoBike)
.createQueryBuilder('cb')
.relation(CargoBike, 'equipmentIds')
.of(cargoBike.id)
.addAndRemove(equipmentIds, await this.equipmentByCargoBikeId(cargoBike.id));
});
!keepLock && await LockUtils.unlockEntity(this.connection, CargoBike, 'cb', cargoBike.id, userId);
return await this.findCargoBikeById(cargoBike.id);
@ -150,8 +150,9 @@ export class CargoBikeAPI extends DataSource {
* created CargoBike and returns created bike with new ID
* @param param0 cargoBike to be created
*/
async createCargoBike ({ cargoBike }: { cargoBike: any }) {
async createCargoBike (cargoBike: any) {
let inserts: any = {};
genBoxDimensions(cargoBike);
await this.connection.transaction(async (entityManager:any) => {
inserts = await entityManager.getRepository(CargoBike)
.createQueryBuilder('cb')
@ -164,6 +165,11 @@ export class CargoBikeAPI extends DataSource {
.relation(CargoBike, 'equipmentTypeIds')
.of(inserts.identifiers[0].id)
.add(cargoBike.equipmentTypeIds);
cargoBike?.equipmentIds && await entityManager.getRepository(CargoBike)
.createQueryBuilder('cb')
.relation(CargoBike, 'equipmentIds')
.of(inserts.identifiers[0].id)
.add(cargoBike.equipmentIds);
});
inserts.generatedMaps[0].id = inserts?.identifiers[0].id;
return inserts?.generatedMaps[0];
@ -198,11 +204,11 @@ export class CargoBikeAPI extends DataSource {
}
async deleteBikeEventType (id: number, userId: number) {
return await deleteEntity(this.connection, BikeEventType, 'bet', id, userId);
return await DBUtils.deleteEntity(this.connection, BikeEventType, 'bet', id, userId);
}
async deleteBikeEvent (id: number, userId: number) {
return await deleteEntity(this.connection, BikeEvent, 'be', id, userId);
return await DBUtils.deleteEntity(this.connection, BikeEvent, 'be', id, userId);
}
async cargoBikeByEventId (id: number) {
@ -221,14 +227,22 @@ export class CargoBikeAPI extends DataSource {
.loadOne();
}
async bikeEventsByCargoBikeId (id: number, offset: number = 0, limit:number = 100) {
return await this.connection.getRepository(CargoBike)
.createQueryBuilder('cb')
.skip(offset)
.take(limit)
.relation(CargoBike, 'bikeEvents')
.of(id)
.loadMany();
async bikeEventsByCargoBikeId (id: number, offset?: number, limit?: number) {
if (offset === null || limit === null) {
return await this.connection.getRepository(CargoBike)
.createQueryBuilder('cb')
.relation(CargoBike, 'bikeEvents')
.of(id)
.loadMany();
} else {
return await this.connection.getRepository(CargoBike)
.createQueryBuilder('cb')
.skip(offset)
.take(limit)
.relation(CargoBike, 'bikeEvents')
.of(id)
.loadMany();
}
}
async createBikeEventType (bikeEventType: any) {
@ -267,22 +281,12 @@ export class CargoBikeAPI extends DataSource {
return await this.bikeEventTypeById(bikeEventType.id);
}
async bikeEventTypes (offset: number, limit: number) {
return await this.connection.getRepository(BikeEventType)
.createQueryBuilder('bet')
.select()
.skip(offset)
.take(limit)
.getMany();
async bikeEventTypes (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, BikeEventType, 'bet', offset, limit);
}
async bikeEvents (offset: number, limit: number) {
return await this.connection.getRepository(BikeEvent)
.createQueryBuilder('be')
.select()
.skip(offset)
.take(limit)
.getMany();
async bikeEvents (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, BikeEvent, 'be', offset, limit);
}
async bikeEventTypeById (id: number) {
@ -343,12 +347,22 @@ export class CargoBikeAPI extends DataSource {
* @param limit
* @param id
*/
async equipmentByCargoBikeId (offset: number, limit: number, id: number) {
return await this.connection.getRepository(Equipment)
.createQueryBuilder('equipment')
.select()
.where('equipment."cargoBikeId" = :id', { id: id })
.getMany();
async equipmentByCargoBikeId (id: number, offset?: number, limit?: number) {
if (offset == null || limit === null) {
return await this.connection.getRepository(Equipment)
.createQueryBuilder('equipment')
.select()
.where('equipment."cargoBikeId" = :id', { id: id })
.getMany();
} else {
return await this.connection.getRepository(Equipment)
.createQueryBuilder('equipment')
.select()
.where('equipment."cargoBikeId" = :id', { id: id })
.skip(offset)
.take(limit)
.getMany();
}
}
async createEquipment ({ equipment }: { equipment: any }) {
@ -406,17 +420,11 @@ export class CargoBikeAPI extends DataSource {
}
async deleteEquipment (id: number, userId: number) {
return await deleteEntity(this.connection, Equipment, 'e', id, userId);
return await DBUtils.deleteEntity(this.connection, Equipment, 'e', id, userId);
}
async getEquipment (offset: number, limit: number) {
return await this.connection.getRepository(Equipment)
.createQueryBuilder('equipment')
.leftJoinAndSelect('equipment.cargoBike', 'cargoBike')
.orderBy('title', 'ASC')
.offset(offset)
.limit(limit)
.getMany();
async getEquipment (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Equipment, 'e', offset, limit);
}
async createEquipmentType (equipmentType: any) {
@ -460,7 +468,7 @@ export class CargoBikeAPI extends DataSource {
}
async deleteEquipmentType (id:number, userId: number) {
return await deleteEntity(this.connection, EquipmentType, 'et', id, userId);
return await DBUtils.deleteEntity(this.connection, EquipmentType, 'et', id, userId);
}
async equipmentTypeById (id: number) {
@ -471,13 +479,8 @@ export class CargoBikeAPI extends DataSource {
.getOne();
}
async equipmentTypes (offset: number, limit: number) {
return await this.connection.getRepository(EquipmentType)
.createQueryBuilder('et')
.select()
.skip(offset)
.take(limit)
.getMany();
async equipmentTypes (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, EquipmentType, 'et', offset, limit);
}
async equipmentTypeByCargoBikeId (id: number) {

@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource';
import { Connection, EntityManager, getConnection } from 'typeorm';
import { ContactInformation } from '../../model/ContactInformation';
import { Person } from '../../model/Person';
import { ActionLogger, deleteEntity, LockUtils } from './utils';
import { ActionLogger, DBUtils, LockUtils } from './utils';
import { GraphQLError } from 'graphql';
import { LendingStation } from '../../model/LendingStation';
@ -32,13 +32,8 @@ export class ContactInformationAPI extends DataSource {
this.connection = getConnection();
}
async contactInformation (offset: number, limit: number) {
return await this.connection.getRepository(ContactInformation)
.createQueryBuilder('ci')
.select()
.offset(offset)
.limit(limit)
.getMany();
async contactInformation (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, ContactInformation, 'ci', offset, limit);
}
async contactInformationById (id: number) {
@ -88,16 +83,11 @@ export class ContactInformationAPI extends DataSource {
}
async deletePerson (id: number, userId: number) {
return await deleteEntity(this.connection, Person, 'p', id, userId);
return await DBUtils.deleteEntity(this.connection, Person, 'p', id, userId);
}
async persons (offset: number, limit: number) {
return await this.connection.getRepository(Person)
.createQueryBuilder('person')
.select()
.skip(offset)
.take(limit)
.getMany();
async persons (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Person, 'p', offset, limit);
}
async personById (id: number) {
@ -171,7 +161,7 @@ export class ContactInformationAPI extends DataSource {
}
async deleteContactInformation (id: number, userId: number) {
return await deleteEntity(this.connection, ContactInformation, 'ci', id, userId);
return await DBUtils.deleteEntity(this.connection, ContactInformation, 'ci', id, userId);
}
async contactInformationByPersonId (id: number) {

@ -19,12 +19,11 @@ This file is part of fLotte-API-Server.
import { DataSource } from 'apollo-datasource';
import { UserInputError } from 'apollo-server-express';
import { GraphQLError } from 'graphql';
import { Connection, EntityManager, getConnection } from 'typeorm';
import { CargoBike } from '../../model/CargoBike';
import { LendingStation } from '../../model/LendingStation';
import { TimeFrame } from '../../model/TimeFrame';
import { ActionLogger, deleteEntity, genDateRange, LockUtils } from './utils';
import { ActionLogger, genDateRange, DBUtils, LockUtils } from './utils';
export class LendingStationAPI extends DataSource {
connection : Connection
@ -44,14 +43,8 @@ export class LendingStationAPI extends DataSource {
/**
* get all lendingStations
*/
async lendingStations (offset: number, limit: number) {
return await this.connection.getRepository(LendingStation)
.createQueryBuilder('lendingStation')
.select()
.offset(offset)
.limit(limit)
.orderBy('name', 'ASC')
.getMany() || new GraphQLError('Internal Server Error: could not query data from table lendingStation');
async lendingStations (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, LendingStation, 'ls', offset, limit);
}
/**
@ -79,13 +72,8 @@ export class LendingStationAPI extends DataSource {
.loadOne();
}
async timeFrames (offset: number, limit: number) {
return await this.connection.getRepository(TimeFrame)
.createQueryBuilder('timeframe')
.select()
.offset(offset)
.limit(limit)
.getMany() || [];
async timeFrames (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, TimeFrame, 'tf', offset, limit);
}
async timeFramesByCargoBikeId (id: number) {
@ -190,15 +178,12 @@ export class LendingStationAPI extends DataSource {
}
async deleteLendingStationById (id: number, userId: number) {
return await deleteEntity(this.connection, LendingStation, 'ls', id, userId);
return await DBUtils.deleteEntity(this.connection, LendingStation, 'ls', id, userId);
}
async createTimeFrame (timeFrame: any) {
return await this.connection.transaction(async (entityManager: EntityManager) => {
if (timeFrame.to === undefined) {
timeFrame.to = '';
}
timeFrame.dateRange = '[' + timeFrame.from + ',' + timeFrame.to + ')';
genDateRange(timeFrame);
// checking for overlapping time frames
const overlapping = await entityManager.getRepository(TimeFrame)
.createQueryBuilder('timeframe')
@ -268,6 +253,6 @@ export class LendingStationAPI extends DataSource {
}
async deleteTimeFrame (id: number, userId: number) {
return await deleteEntity(this.connection, TimeFrame, 'tf', id, userId);
return await DBUtils.deleteEntity(this.connection, TimeFrame, 'tf', id, userId);
}
}

@ -23,7 +23,7 @@ import { ContactInformation } from '../../model/ContactInformation';
import { Engagement } from '../../model/Engagement';
import { Participant } from '../../model/Participant';
import { EngagementType } from '../../model/EngagementType';
import { ActionLogger, deleteEntity, genDateRange, LockUtils } from './utils';
import { ActionLogger, DBUtils, genDateRange, LockUtils } from './utils';
import { UserInputError } from 'apollo-server-express';
import { GraphQLError } from 'graphql';
@ -42,13 +42,8 @@ export class ParticipantAPI extends DataSource {
.getOne();
}
async getParticipants (offset: number, limit: number) {
return await this.connection.getRepository(Participant)
.createQueryBuilder('participant')
.select()
.offset(offset)
.limit(limit)
.getMany();
async getParticipants (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Participant, 'p', offset, limit);
}
async participantByEngagementId (id: number) {
@ -84,17 +79,24 @@ export class ParticipantAPI extends DataSource {
.getMany();
}
async engagementByCargoBikeId (offset: number, limit: number, id: number) {
return await this.connection.getRepository(Engagement)
.createQueryBuilder('engagement')
.select()
.where('engagement."cargoBikeId" = :id', {
id: id
})
.skip(offset)
.take(limit)
.orderBy('engagement."dateRange"', 'DESC')
.getMany();
async engagementByCargoBikeId (id: number, offset?: number, limit?: number) {
if (limit === null || offset === null) {
return await this.connection.getRepository(Engagement)
.createQueryBuilder('engagement')
.select()
.where('engagement."cargoBikeId" = :id', { id: id })
.orderBy('engagement."dateRange"', 'DESC')
.getMany();
} else {
return await this.connection.getRepository(Engagement)
.createQueryBuilder('engagement')
.select()
.where('engagement."cargoBikeId" = :id', { id: id })
.skip(offset)
.take(limit)
.orderBy('engagement."dateRange"', 'DESC')
.getMany();
}
}
async currentEngagementByCargoBikeId (id: number) {
@ -107,13 +109,8 @@ export class ParticipantAPI extends DataSource {
.getMany();
}
async engagements (offset: number, limit: number) {
return await this.connection.getRepository(Engagement)
.createQueryBuilder('e')
.select()
.skip(offset)
.take(limit)
.getMany();
async engagements (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Engagement, 'e', offset, limit);
}
async engagementById (id: number) {
@ -132,13 +129,8 @@ export class ParticipantAPI extends DataSource {
.getOne();
}
async engagementTypes (offset: number, limit: number) {
return await this.connection.getRepository(EngagementType)
.createQueryBuilder('et')
.select()
.skip(offset)
.take(limit)
.getMany();
async engagementTypes (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, EngagementType, 'et', offset, limit);
}
async engagementTypeByEngagementId (id: number) {
@ -178,6 +170,7 @@ export class ParticipantAPI extends DataSource {
* @param participant to be created
*/
async createParticipant (participant: any) {
genDateRange(participant);
let inserts: any;
await this.connection.transaction(async (entityManager: EntityManager) => {
inserts = await entityManager.getRepository(Participant)
@ -187,7 +180,7 @@ export class ParticipantAPI extends DataSource {
.values([participant])
.returning('*')
.execute();
await entityManager.getRepository(Participant)
participant.workshopIds && await entityManager.getRepository(Participant)
.createQueryBuilder('w')
.relation(Participant, 'workshopIds')
.of(participant.id)
@ -209,8 +202,9 @@ export class ParticipantAPI extends DataSource {
delete participant.keepLock;
await this.connection.transaction(async (entityManager: EntityManager) => {
if (await LockUtils.isLocked(entityManager, Participant, 'p', participant.id, userId)) {
throw new GraphQLError('Participant is locked by another user');
throw new UserInputError('Attempting to update locked resource');
}
genDateRange(participant);
const workshops = participant.workshopIds;
delete participant.workshopIds;
await ActionLogger.log(entityManager, Participant, 'p', participant, userId);
@ -219,8 +213,19 @@ export class ParticipantAPI extends DataSource {
.update()
.set({ ...participant })
.where('id = :id', { id: participant.id })
.execute().then(value => { if (value.affected !== 1) { throw new GraphQLError('ID not found'); } });
await entityManager.getRepository(Participant)
.execute().then(value => { if (value.affected !== 1) { throw new UserInputError('ID not found'); } });
// check for engagements before or after dateRange
const engagements = await entityManager.getRepository(Engagement)
.createQueryBuilder('e')
.select()
.where('e."participantId" = :pid', { pid: participant.id })
.andWhere('not :pdr @> e."dateRange"', { pdr: participant.dateRange })
.getMany();
if (engagements.length !== 0) {
throw new UserInputError('Engagements with ids: ' + engagements.map((e) => { return `${e.id} ,`; }) + ' are are outside of dataRange');
}
// add and remove workshop relations
workshops && await entityManager.getRepository(Participant)
.createQueryBuilder('w')
.relation(Participant, 'workshopIds')
.of(participant.id)
@ -231,7 +236,7 @@ export class ParticipantAPI extends DataSource {
}
async deleteParticipant (id: number, userId: number) {
return await deleteEntity(this.connection, Participant, 'p', id, userId);
return await DBUtils.deleteEntity(this.connection, Participant, 'p', id, userId);
}
async createEngagement (engagement: any) {
@ -249,6 +254,16 @@ export class ParticipantAPI extends DataSource {
if (overlapping.length > 0) {
throw new UserInputError('Engagements with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping');
}
// check if participant is active
const participant = await entityManager.getRepository(Participant)
.createQueryBuilder('p')
.select()
.where('p.id = :pid', { pid: engagement.participantId })
.andWhere('not p."dateRange" @> :edr', { edr: engagement.dateRange })
.getOne();
if (participant) {
throw new UserInputError('Participant ist not active in the specified dateRange');
}
inserts = await entityManager.getRepository(Engagement)
.createQueryBuilder('engagement')
.insert()
@ -288,6 +303,20 @@ export class ParticipantAPI extends DataSource {
if (overlapping.length > 0) {
throw new UserInputError('Engagements with ids: ' + overlapping.map((e) => { return e.id + ', '; }) + 'are overlapping');
}
// check if participant is active
if (engagement.dateRange && engagement.participantId) {
const participant = await entityManager.getRepository(Participant)
.createQueryBuilder('p')
.select()
.where('p.id = :pid', { pid: engagement.participantId })
.andWhere('not p."dateRange" @> :edr', { edr: engagement.dateRange })
.getOne();
if (participant) {
throw new UserInputError('Participant ist not active in the specified dateRange');
}
} else if (engagement.dateRange || engagement.dateRange) {
throw new UserInputError('Please specify participantId adn the dateRange');
}
await entityManager.getRepository(Engagement)
.createQueryBuilder('engagement')
.update()
@ -300,7 +329,7 @@ export class ParticipantAPI extends DataSource {
}
async deleteEngagement (id: number, userId: number) {
return await deleteEntity(this.connection, Engagement, 'e', id, userId);
return await DBUtils.deleteEntity(this.connection, Engagement, 'e', id, userId);
}
async createEngagementType (engagementType: any) {
@ -342,6 +371,6 @@ export class ParticipantAPI extends DataSource {
}
async deleteEngagementType (id: number, userId: number) {
return await deleteEntity(this.connection, EngagementType, 'et', id, userId);
return await DBUtils.deleteEntity(this.connection, EngagementType, 'et', id, userId);
}
}

@ -24,7 +24,7 @@ import { Organisation } from '../../model/Organisation';
import { UserInputError } from 'apollo-server-express';
import { CargoBike } from '../../model/CargoBike';
import { LendingStation } from '../../model/LendingStation';
import { ActionLogger, deleteEntity, LockUtils } from './utils';
import { ActionLogger, DBUtils, LockUtils } from './utils';
import { GraphQLError } from 'graphql';
export class ProviderAPI extends DataSource {
@ -42,13 +42,8 @@ export class ProviderAPI extends DataSource {
.getOne();
}
async provider (offset: number, limit: number) {
return await this.connection.getRepository(Provider)
.createQueryBuilder('provider')
.select()
.skip(offset)
.take(limit)
.getMany();
async provider (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Provider, 'p', offset, limit);
}
async providerByOrganisationId (id: number) {
@ -75,13 +70,8 @@ export class ProviderAPI extends DataSource {
.loadOne();
}
async organisations (offset: number, limit: number) {
return await this.connection.getRepository(Organisation)
.createQueryBuilder('o')
.select()
.skip(offset)
.limit(limit)
.getMany();
async organisations (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Organisation, 'o', offset, limit);
}
async organisationById (id: number) {
@ -180,7 +170,7 @@ export class ProviderAPI extends DataSource {
}
async deleteProvider (id: number, userId: number) {
return await deleteEntity(this.connection, Provider, 'p', id, userId);
return await DBUtils.deleteEntity(this.connection, Provider, 'p', id, userId);
}
async createOrganisation (organisation: any) {
@ -223,6 +213,6 @@ export class ProviderAPI extends DataSource {
}
async deleteOrganisation (id: number, userId: number) {
return await deleteEntity(this.connection, Organisation, 'o', id, userId);
return await DBUtils.deleteEntity(this.connection, Organisation, 'o', id, userId);
}
}

@ -23,49 +23,136 @@ import { ActionLog, Actions } from '../../model/ActionLog';
import { UserInputError } from 'apollo-server-express';
export function genDateRange (struct: any) {
if (struct.to === undefined) {
struct.to = '';
}
struct.dateRange = '[' + struct.from + ',' + struct.to + ')';
if (struct.from === undefined) {
if (!struct.dateRange || !struct.dateRange.from) {
delete struct.dateRange;
return;
} else if (!struct.dateRange?.to) {
struct.dateRange.to = '';
} else if (struct.dateRange.to === struct.dateRange.from) {
throw new UserInputError('Date Range can not be empty, provide different dates.');
}
// delete these keys, so the struct can be used to update the engagement entity
delete struct.from;
delete struct.to;
struct.dateRange = `[${struct.dateRange.from},${struct.dateRange.to})`;
}
/**
* This function helps prepare the cargoBike struct, to be used in an update or create.
* It creates the numrange attributes than can be understood by postgres.
* @param range
*/
function genNumRange (range: { min: number, max: number}) :string {
if (!range || (!range.max && !range.min)) {
return null;
} else if (range.min === null || range.min === undefined) {
range.min = range.max;
} else if (range.max === null || range.max === undefined) {
range.max = range.min;
}
if (range.min < 0) {
throw new UserInputError('Minimal value must be greater or equal to 0');
}
return `[${range.min},${range.max}]`;
}
/**
* This function prepares the cargoBike struct, to be used in an update or create.
* It creates the numrange attributes than can be understood by postgres.
* @param cargoBike
*/
export function genBoxDimensions (cargoBike: any) {
if (!cargoBike.dimensionsAndLoad) { return; }
cargoBike.dimensionsAndLoad.boxLengthRange = genNumRange(cargoBike.dimensionsAndLoad.boxLengthRange);
cargoBike.dimensionsAndLoad.boxWidthRange = genNumRange(cargoBike.dimensionsAndLoad.boxWidthRange);
cargoBike.dimensionsAndLoad.boxHeightRange = genNumRange(cargoBike.dimensionsAndLoad.boxHeightRange);
}
/**
* Can be used in resolvers to specify if entry is locked by other user.
* Can be used in resolvers to specify, if entry is locked by other user.
* Returns true if locked by other user.
* @param parent
* @param dataSources
* @param req user request
* @param req
*/
export function isLocked (parent: any, { req }: { req: any }) {
return req.userId !== parent.lockedBy && new Date() <= new Date(parent.lockedUntil);
}
/**
* Can be used in resolvers to specify, if entry is locked by the current user.
* @param parent
* @param req
*/
export function isLockedByMe (parent: any, { req }: { req: any }) {
return req.userId === parent.lockedBy && new Date() <= new Date(parent.lockedUntil);
}
export async function deleteEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Boolean> {
return await connection.transaction(async (entityManger: EntityManager) => {
if (await LockUtils.isLocked(entityManger, target, alias, id, userId)) {
throw new UserInputError('Attempting to delete locked resource');
/**
* Some utility functions for the database
*/
export class DBUtils {
/**
* Delete any instance of an entity that implements the Lockable interface.
* It must implement the interface, so it can be be ensured, that the instance is not locked by another user.
* @param connection
* @param target
* @param alias
* @param id
* @param userId
*/
static async deleteEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Boolean> {
return await connection.transaction(async (entityManger: EntityManager) => {
if (await LockUtils.isLocked(entityManger, target, alias, id, userId)) {
throw new UserInputError('Attempting to delete locked resource');
}
await ActionLogger.log(entityManger, target, alias, { id: id }, userId, Actions.DELETE);
return await entityManger.getRepository(target)
.createQueryBuilder(alias)
.delete()
.where('id = :id', { id: id })
.execute().then(value => value.affected === 1);
});
}
/**
* Return all instances of the given entity called target.
* When offset or limit is not specified, both values are ignored.
* @param connection
* @param target
* @param alias
* @param offset
* @param limit
*/
static async getAllEntity (connection: Connection, target: ObjectType<any>, alias: string, offset?: number, limit?: number) {
if (offset === null || limit === null) {
return await connection.getRepository(target)
.createQueryBuilder(alias)
.select()
.getMany();
} else {
return await connection.getRepository(target)
.createQueryBuilder(alias)
.select()
.skip(offset)
.take(limit)
.getMany();
}
await ActionLogger.log(entityManger, target, alias, { id: id }, userId, Actions.DELETE);
return await entityManger.getRepository(target)
.createQueryBuilder(alias)
.delete()
.where('id = :id', { id: id })
.execute().then(value => value.affected === 1);
});
}
}
/**
* Some static functions for the locking feature.
*/
export class LockUtils {
static async findById (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number): Promise<Lockable> {
/**
* A helper function to find an instance of any entity that implements Lockable.
* It will throw an error, if nothing is found.
* Using this function only makes sense to use in the context of locking because there is no point in locking
* an instance that does not exist.
* @param connection
* @param target
* @param alias
* @param id
* @private
*/
private static async findById (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number): Promise<Lockable> {
return await connection.getRepository(target)
.createQueryBuilder(alias)
.select()
@ -75,6 +162,17 @@ export class LockUtils {
});
}
/**
* Lock an instance of an entity target that implements the Lockable interface and return that instance.
* If lock could not be set, it will still return the entity.
* If lock was set or not can be obtained by the field isLockedByMe in the graphql interface,
* or the the fields lockedBy and lockedUntil in the database.
* @param connection
* @param target
* @param alias
* @param id
* @param userId
*/
static async lockEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Lockable> {
const lock = await connection.getRepository(target)
.createQueryBuilder(alias)
@ -103,6 +201,17 @@ export class LockUtils {
return await this.findById(connection, target, alias, id);
}
/**
* Unlock an instance of an entity target that implements the Lockable interface and return that instance.
* If lock could not be unset, it will still return the entity.
* If lock was set or not can be obtained by the field isLockedByMe in the graphql interface,
* or the the fields lockedBy and lockedUntil in the database.
* @param connection
* @param target
* @param alias
* @param id
* @param userId
*/
static async unlockEntity (connection: Connection, target: ObjectType<Lockable>, alias: string, id: number, userId: number): Promise<Lockable> {
await connection.getRepository(target)
.createQueryBuilder(alias)
@ -145,7 +254,18 @@ export class LockUtils {
}
}
/**
* Some utility function for the logging features.
*/
export class ActionLogger {
/**
* Create array of strings, that can be used to select them form the database.
* If you want to avoid logging all old values, for an update, but only the ones that are updated,
* use this function. If updates are null, ['*'] will be returned. Use this for delete actions.
* @param updates
* @param alias
* @private
*/
private static buildSelect (updates: any, alias: string) : string[] {
// this hacky shit makes it possible to select subfields like the address or insurance data. Only one layer at the moment
if (updates === null) {
@ -156,15 +276,25 @@ export class ActionLogger {
// sometimes updates[value] is an array, e.g. timePeriods that are saved as a simple array in postgres
if (updates[value] && typeof updates[value] === 'object' && !Array.isArray(updates[value])) {
Object.keys(updates[value]).forEach(subValue => {
ret.push(alias + '."' + value + subValue[0].toUpperCase() + subValue.substr(1).toLowerCase() + '"');
ret.push(`${alias}."${value}${subValue[0].toUpperCase()}${subValue.substr(1).toLowerCase()}"`);
});
} else {
ret.push(alias + '."' + value + '"');
ret.push(`${alias}."${value}"`);
}
});
return ret;
}
/**
* Insert an entry in the log. The log ist just another entity in the database.
* You can only use this in a transaction. So you have to pass an entity manager.
* @param em
* @param target
* @param alias
* @param updates
* @param userId
* @param action
*/
static async log (em: EntityManager, target: ObjectType<any>, alias: string, updates: any, userId: number, action: Actions = Actions.UPDATE) {
const oldValues = await em.getRepository(target).createQueryBuilder(alias)
.select(this.buildSelect(updates, alias))

@ -21,7 +21,7 @@ import { DataSource } from 'apollo-datasource';
import { Connection, EntityManager, getConnection } from 'typeorm';
import { WorkshopType } from '../../model/WorkshopType';
import { Workshop } from '../../model/Workshop';
import { ActionLogger, deleteEntity, LockUtils } from './utils';
import { ActionLogger, DBUtils, LockUtils } from './utils';
import { UserInputError } from 'apollo-server-express';
import { GraphQLError } from 'graphql';
import { Participant } from '../../model/Participant';
@ -73,7 +73,7 @@ export class WorkshopAPI extends DataSource {
}
async deleteWorkshop (id: number, userId: number) {
return await deleteEntity(this.connection, Workshop, 'w', id, userId);
return await DBUtils.deleteEntity(this.connection, Workshop, 'w', id, userId);
}
async createWorkshopType (workshopType: any) {
@ -115,7 +115,7 @@ export class WorkshopAPI extends DataSource {
}
async deleteWorkshopType (id: number, userId: number) {
return await deleteEntity(this.connection, WorkshopType, 'wt', id, userId);
return await DBUtils.deleteEntity(this.connection, WorkshopType, 'wt', id, userId);
}
async workshopTypeById (id: number) {
@ -126,13 +126,8 @@ export class WorkshopAPI extends DataSource {
.getOne();
}
async workshopTypes (offset: number, limit: number) {
return await this.connection.getRepository(WorkshopType)
.createQueryBuilder('w')
.select()
.skip(offset)
.take(limit)
.getMany();
async workshopTypes (offset?: number, limit?: number) {
return DBUtils.getAllEntity(this.connection, WorkshopType, 'wt', offset, limit);
}
async workshopById (id: number) {
@ -148,13 +143,8 @@ export class WorkshopAPI extends DataSource {
* @param offset
* @param limit
*/
async workshops (offset: number, limit: number) {
return await this.connection.getRepository(Workshop)
.createQueryBuilder('w')
.select()
.skip(offset)
.take(limit)
.getMany();
async workshops (offset?: number, limit?: number) {
return await DBUtils.getAllEntity(this.connection, Workshop, 'w', offset, limit);
}
async trainer1ByWorkshopId (id: number) {

@ -30,7 +30,6 @@ import {
DeleteDateColumn
} from 'typeorm';
import { Provider } from './Provider';
import { Participant } from './Participant';
import { InsuranceData } from './InsuranceData';
import { TimeFrame } from './TimeFrame';
import { Taxes } from './Taxes';
@ -66,7 +65,9 @@ export interface Lockable {
}
export class Security {
@Column()
@Column({
nullable: true
})
frameNumber: string;
@Column({
@ -90,13 +91,19 @@ export class Security {
adfcCoding: string;
}
export class TechnicalEquipment {
@Column()
@Column({
nullable: true
})
bicycleShift: string;
@Column()
@Column({
nullable: true
})
isEBike: boolean;
@Column()
@Column({
nullable: true
})
hasLightSystem: boolean;
@Column({
@ -106,44 +113,55 @@ export class TechnicalEquipment {
}
export class DimensionsAndLoad {
@Column()
@Column({
nullable: true
})
hasCoverBox: boolean;
@Column()
@Column({
nullable: true
})
lockable:boolean;
@Column({
type: 'decimal'
type: 'numrange',
nullable: true
})
boxLength: number;
boxLengthRange: string;
@Column({
type: 'decimal'
type: 'numrange',
nullable: true
})
boxWidth: number;
boxWidthRange: string;
@Column({
type: 'decimal'
type: 'numrange',
nullable: true
})
boxHeight: number;
boxHeightRange: string;
@Column({
type: 'decimal'
type: 'decimal',
nullable: true
})
maxWeightBox: number;
maxWeightBox: string;
@Column({
type: 'decimal'
type: 'decimal',
nullable: true
})
maxWeightLuggageRack: number;
@Column({
type: 'decimal'
type: 'decimal',
nullable: true
})
maxWeightTotal: number;
@Column({
type: 'decimal'
type: 'decimal',
nullable: true
})
bikeLength: number;
@ -174,23 +192,27 @@ export class CargoBike implements Lockable {
@PrimaryGeneratedColumn()
id: number;
@DeleteDateColumn()
deleteDate: Date;
@Column({
type: 'enum',
enum: Group
})
group: Group;
@Column()
@Column({
unique: true
})
name: string;
@Column({
nullable: true
})
state: string;
@OneToMany(type => Equipment, equipment => equipment.cargoBikeId, {
nullable: true,
eager: true
})
equipment: Equipment[];
equipmentIds: number[];
// Equipment that is not unique and is supposed to be selected out of a list e.g. drop down
@ManyToMany(type => EquipmentType, equipmentType => equipmentType.cargoBikeIds)
@ -245,19 +267,32 @@ export class CargoBike implements Lockable {
})
description: string;
@Column()
@Column({
nullable: true
})
modelName: string;
@Column()
@Column({
nullable: true
})
numberOfWheels: number;
@Column()
@Column({
type: 'boolean',
nullable: true
})
forCargo: boolean;
@Column()
@Column({
type: 'boolean',
nullable: true
})
forChildren: boolean;
@Column()
@Column({
type: 'int',
nullable: true
})
numberOfChildren: number;
@Column(type => TechnicalEquipment)
@ -267,6 +302,7 @@ export class CargoBike implements Lockable {
dimensionsAndLoad: DimensionsAndLoad;
@Column({
type: 'int',
nullable: true
})
lockedBy: number;

@ -42,9 +42,7 @@ export class ContactInformation implements Lockable {
})
participantId: number;
@Column(type => {
return Address;
})
@Column(type => { return Address; })
address: Address;
@Column({

@ -26,7 +26,9 @@ export class EngagementType implements Lockable {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Column({
unique: true
})
name: string;
@Column({

@ -26,7 +26,9 @@ export class Equipment implements Lockable {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Column({
unique: true
})
serialNo: string;
@Column()
@ -37,7 +39,7 @@ export class Equipment implements Lockable {
})
description: string;
@ManyToOne(type => CargoBike, cargoBike => cargoBike.equipment, {
@ManyToOne(type => CargoBike, cargoBike => cargoBike.equipmentIds, {
nullable: true
})
@JoinColumn({

@ -25,7 +25,9 @@ export class EquipmentType implements Lockable {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Column({
unique: true
})
name: string;
@Column({

@ -20,22 +20,34 @@ This file is part of fLotte-API-Server.
import { Column } from 'typeorm';
export class InsuranceData {
@Column()
@Column({
nullable: true
})
name: string;
@Column()
@Column({
nullable: true
})
benefactor: string;
@Column()
@Column({
nullable: true
})
billing: string;
@Column()
@Column({
nullable: true
})
noPnP: string;
@Column()
@Column({
nullable: true
})
maintenanceResponsible: string;
@Column()
@Column({
nullable: true
})
maintenanceBenefactor: string;
@Column({

@ -34,6 +34,12 @@ export class LoanPeriod {
})
from: Date;
@Column({
nullable: true,
type: 'text'
})
generalRemark: string;
/**
* validity for loanPeriods
*/
@ -44,10 +50,39 @@ export class LoanPeriod {
to: Date;
@Column({
type: 'simple-array',
nullable: true
})
loanTimes: string[];
mo: string;
@Column({
nullable: true
})
tu: string;
@Column({
nullable: true
})
we: string;
@Column({
nullable: true
})
th: string;
@Column({
nullable: true
})
fr: string;
@Column({
nullable: true
})
sa: string;
@Column({
nullable: true
})
su: string;
}
@Entity()

@ -28,7 +28,9 @@ export class Organisation implements Lockable {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Column({
unique: true
})
name: string;
@OneToMany(type => LendingStation, lendingStation => lendingStation.organisationId)

@ -29,16 +29,9 @@ export class Participant implements Lockable {
id: number;
@Column({
type: 'date',
default: () => 'CURRENT_DATE'
type: 'daterange'
})
start: Date;
@Column({
type: 'date',
nullable: true
})
end: Date;
dateRange: Date[];
@OneToOne(type => ContactInformation, contactInformation => contactInformation.participantId, {
nullable: false

@ -18,10 +18,11 @@ This file is part of fLotte-API-Server.
*/
import { Lockable } from './CargoBike';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn, Unique } from 'typeorm';
import { ContactInformation } from './ContactInformation';
@Entity()
@Unique(['firstName', 'name'])
export class Person implements Lockable {
@PrimaryGeneratedColumn()
id: number;

@ -25,7 +25,9 @@ export enum OrganisationArea {
ZB = 'ZB'
}
export class Taxes {
@Column()
@Column({
nullable: true
})
costCenter: string;
@Column({

@ -26,7 +26,9 @@ export class WorkshopType implements Lockable {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Column({
unique: true
})
name: string;
@OneToMany(type => Workshop, workshop => workshop.workshopTypeId)

@ -30,14 +30,14 @@ export default {
throw new PermissionError();
}
},
cargoBikes: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
cargoBikes: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.cargoBikeAPI.getCargoBikes(offset, limit);
} else {
throw new PermissionError();
}
},
bikeEvents: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
bikeEvents: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBikeEvent)) {
return dataSources.cargoBikeAPI.bikeEvents(offset, limit);
} else {
@ -58,14 +58,14 @@ export default {
throw new PermissionError();
}
},
bikeEventTypes: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
bikeEventTypes: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBikeEvent)) {
return dataSources.cargoBikeAPI.bikeEventTypes(offset, limit);
} else {
throw new PermissionError();
}
},
equipment: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
equipment: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadEquipment)) {
return dataSources.cargoBikeAPI.getEquipment(offset, limit);
} else {
@ -79,7 +79,7 @@ export default {
throw new PermissionError();
}
},
equipmentTypes: (_:any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
equipmentTypes: (_:any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadEquipment)) {
return dataSources.cargoBikeAPI.equipmentTypes(offset, limit);
} else {
@ -102,23 +102,23 @@ export default {
throw new PermissionError();
}
},
engagement (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) {
engagement (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadEngagement)) {
return dataSources.participantAPI.engagementByCargoBikeId(offset, limit, parent.id);
return dataSources.participantAPI.engagementByCargoBikeId(parent.id, offset, limit);
} else {
throw new PermissionError();
}
},
participants (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) { // TODO should be done with engagements
participants (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) { // TODO should be done with engagements
if (req.permissions.includes(Permission.ReadParticipant)) {
return dataSources.participantAPI.participantsByCargoBikeId(parent.id);
} else {
throw new PermissionError();
}
},
equipment (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) {
equipment (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadEquipment)) {
return dataSources.cargoBikeAPI.equipmentByCargoBikeId(offset, limit, parent.id);
return dataSources.cargoBikeAPI.equipmentByCargoBikeId(parent.id, offset, limit);
} else {
throw new PermissionError();
}
@ -130,15 +130,13 @@ export default {
throw new PermissionError();
}
},
bikeEvents (parent: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) {
bikeEvents (parent: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadBikeEvent)) {
return dataSources.cargoBikeAPI.bikeEventsByCargoBikeId(parent.id, offset, limit);
} else {
throw new PermissionError();
}
},
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }),
timeFrames (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadTimeFrame)) {
return dataSources.lendingStationAPI.timeFramesByCargoBikeId(parent.id);
@ -159,8 +157,17 @@ export default {
} else {
throw new PermissionError();
}
},
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req })
},
NumRange: {
min: (parent: string) => {
return parent.replace(/^\[(.*),.*]$/, '$1');
},
max: (parent: string) => {
return parent.replace(/^\[.*,(.*)]$/, '$1');
}
},
Equipment: {
cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) {
@ -173,6 +180,10 @@ export default {
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req })
},
EquipmentType: {
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req })
},
BikeEvent: {
cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadBike)) {
@ -212,7 +223,7 @@ export default {
Mutation: {
createCargoBike: (_: any, { cargoBike }: { cargoBike: any }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.WriteBike)) {
return dataSources.cargoBikeAPI.createCargoBike({ cargoBike });
return dataSources.cargoBikeAPI.createCargoBike(cargoBike);
} else {
throw new PermissionError();
}

@ -24,7 +24,7 @@ import { PermissionError } from '../errors/PermissionError';
export default {
Query: {
contactInformation: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
contactInformation: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadPerson)) {
return dataSources.contactInformationAPI.contactInformation(offset, limit);
} else {
@ -45,7 +45,7 @@ export default {
throw new PermissionError();
}
},
persons: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
persons: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadPerson)) {
return dataSources.contactInformationAPI.persons(offset, limit);
} else {

@ -31,7 +31,7 @@ export default {
throw new PermissionError();
}
},
lendingStations: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
lendingStations: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadLendingStation)) {
return dataSources.lendingStationAPI.lendingStations(offset, limit);
} else {
@ -45,7 +45,7 @@ export default {
throw new PermissionError();
}
},
timeFrames: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
timeFrames: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadTimeFrame)) {
return dataSources.lendingStationAPI.timeFrames(offset, limit);
} else {
@ -99,19 +99,15 @@ export default {
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req })
},
LoanPeriod: {
loanTimes (parent: any) {
return parent.loanTimes ? parent.loanTimes : [];
DateRange: {
from (parent: string) {
return parent.replace(/^\[(.*),.*\)$/, '$1');
},
to (parent: string) {
return parent.replace(/^\[.*,(.*)\)$/, '$1');
}
},
TimeFrame: {
from (parent: any) {
return (parent.dateRange as string).split(',')[0].replace('[', '');
},
to (parent: any) {
const str = (parent.dateRange as string).split(',')[1].replace(')', '');
return (str.length > 0) ? str : null;
},
cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.cargoBikeAPI.cargoBikeByTimeFrameId(parent.id);

@ -30,7 +30,7 @@ export default {
throw new PermissionError();
}
},
participants: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
participants: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadParticipant)) {
return dataSources.participantAPI.getParticipants(offset, limit);
} else {
@ -44,7 +44,7 @@ export default {
throw new PermissionError();
}
},
engagements: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
engagements: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadEngagement)) {
return dataSources.participantAPI.engagements(offset, limit);
} else {
@ -58,7 +58,7 @@ export default {
throw new PermissionError();
}
},
engagementTypes: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
engagementTypes: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadEngagement)) {
return dataSources.participantAPI.engagementTypes(offset, limit);
} else {
@ -113,13 +113,6 @@ export default {
throw new PermissionError();
}
},
from (parent: any) {
return (parent.dateRange as string).split(',')[0].replace('[', '');
},
to (parent: any) {
const str = (parent.dateRange as string).split(',')[1].replace(')', '');
return (str.length > 0) ? str : null;
},
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req })
},

@ -23,7 +23,7 @@ import { PermissionError } from '../errors/PermissionError';
export default {
Query: {
providers: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
providers: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadProvider)) {
return dataSources.providerAPI.provider(offset, limit);
} else {
@ -37,7 +37,7 @@ export default {
throw new PermissionError();
}
},
organisations: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
organisations: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadOrganisation)) {
return dataSources.providerAPI.organisations(offset, limit);
} else {

@ -31,7 +31,7 @@ export default {
throw new PermissionError();
}
},
workshopTypes: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
workshopTypes: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadWorkshop)) {
return dataSources.workshopAPI.workshopTypes(offset, limit);
} else {
@ -45,7 +45,7 @@ export default {
throw new PermissionError();
}
},
workshops: (_: any, { offset, limit }: { offset: number, limit: number }, { dataSources, req }: { dataSources: any, req: any }) => {
workshops: (_: any, { offset, limit }: { offset?: number, limit?: number }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadWorkshop)) {
return dataSources.workshopAPI.workshops(offset, limit);
} else {

@ -21,27 +21,38 @@ import { gql } from 'apollo-server-express';
export default gql`
"timestamp object YYYY-MM-ddThh:mm:ss.sssZ"
"date object YYYY-MM-dd"
scalar Date
"timestamp object YYYY-MM-ddThh:mm:ss.sssZ"
scalar DateTime
"only time hh-mm-ss"
scalar Time
"""
is of american format [-]$[0-9]+.[0-9][0-9]
commas every three digits and . for decimals with 2 digits after the .
There can be a leading -.
There is a currency signe at the first position or second position if - is set.
The kind of currency depends on the database.
"""
scalar Money
"The CargoBike type is central to the graph. You could call it the root."
type CargoBike {
id: ID!
"see column A in info tabelle"
group: Group
name: String
group: Group!
name: String!
state: BikeState
modelName: String
numberOfWheels: Int
forCargo: Boolean
forChildren: Boolean
numberOfChildren: Int!
numberOfChildren: Int
"""
Safety is a custom type, that stores information about security features.
TODO: Should this be called Security?
"""
security: Security!
security: Security
"""
Does not refer to an extra table in the database.
"""
@ -49,9 +60,11 @@ export default gql`
"""
Does not refer to an extra table in the database.
"""
dimensionsAndLoad: DimensionsAndLoad!
dimensionsAndLoad: DimensionsAndLoad
"If offset or limit is not provided, both values are ignored"
bikeEvents(offset: Int, limit: Int): [BikeEvent]
equipment(offset: Int!, limit: Int!): [Equipment]
"If offset or limit is not provided, both values are ignored"
equipment(offset: Int, limit: Int): [Equipment]
"Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2"
equipmentType: [EquipmentType]
"Sticker State"
@ -60,19 +73,29 @@ export default gql`
provider: Provider
"all participants currently engaged with the cargoBike"
participants: [Participant]
insuranceData: InsuranceData!
insuranceData: InsuranceData
lendingStation: LendingStation
taxes: Taxes
currentEngagements: [Engagement]
engagement(offset: Int!, limit: Int!): [Engagement]
"If offset or limit is not provided, both values are ignored"
engagement(offset: Int, limit: Int): [Engagement]
timeFrames: [TimeFrame]
isLocked: Boolean!
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
"""
Status of the CargoBike. More fields will be added, or removed.
"""
enum BikeState {
ACTIVE
INACTIVE
INPREPARATION
}
"""
if you want to add bike to a lending station, create a new timeFrame with to: Date = null
"""
@ -80,42 +103,52 @@ export default gql`
"see column A in info tabelle"
group: Group!
name: String!
modelName: String!
numberOfWheels: Int!
forCargo: Boolean!
forChildren: Boolean!
numberOfChildren: Int!
state: BikeState
modelName: String
numberOfWheels: Int
forCargo: Boolean
forChildren: Boolean
numberOfChildren: Int
"""
Safety is a custom type, that stores information about security features.
TODO: Should this be called Security?
"""
security: SecurityCreateInput!
security: SecurityCreateInput
"""
Does not refer to an extra table in the database.
"""
technicalEquipment: TechnicalEquipmentCreateInput!
technicalEquipment: TechnicalEquipmentCreateInput
"""
Does not refer to an extra table in the database.
"""
dimensionsAndLoad: DimensionsAndLoadCreateInput!
"Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2"
dimensionsAndLoad: DimensionsAndLoadCreateInput
"""
Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2
When set to null or [], no relations will be added.
"""
equipmentTypeIds: [ID]
"""
Refers to unique equipment
When set to null or [], no relations will be added.
When specified id is in a relation with another bike, this relation will be deleted.
"""
equipmentIds: [ID]
"Sticker State"
stickerBikeNameState: StickerBikeNameState
note: String
providerId: ID
insuranceData: InsuranceDataCreateInput!
taxes: TaxesCreateInput!
insuranceData: InsuranceDataCreateInput
taxes: TaxesCreateInput
}
"""
if you want to add bike to a lending station, create a new timeFrame with to: Date = null
If you want to add bike to a lending station, create a new timeFrame with to: Date = null
"""
input CargoBikeUpdateInput {
id: ID!
"see column A in info tabelle"
group: Group
name: String
state: BikeState
modelName: String
numberOfWheels: Int
forCargo: Boolean
@ -123,7 +156,6 @@ export default gql`
numberOfChildren: Int
"""
Safety is a custom type, that stores information about security features.
TODO: Should this be called Security?
"""
security: SecurityUpdateInput
"""
@ -136,9 +168,19 @@ export default gql`
dimensionsAndLoad: DimensionsAndLoadUpdateInput
"""
Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2
If set, ols relations will be over written. Set [] to delete all
When set to null, field will be ignored.
When set to [], all relations will be deleted.
Else all realtions will be deleted and the specified relations will be added.
"""
equipmentTypeIds: [ID]
"""
Refers to unique equipment
When set to null, field will be ignored.
When set to [], all relations will be deleted.
Else all realtions will be deleted and the specified relations will be added.
When specified id is in a relation with another bike, this relation will be deleted.
"""
equipmentIds: [ID]
"Sticker State"
stickerBikeNameState: StickerBikeNameState
note: String
@ -153,15 +195,15 @@ export default gql`
"""
Eventually, this field will become an enum or a separate data table and user can choose from a pool of insurance companies.
"""
name: String!
benefactor: String!
billing: String!
noPnP: String!
name: String
benefactor: String
billing: String
noPnP: String
"eg. Anbieter, flotte, eigenleistung"
maintenanceResponsible: String!
maintenanceBenefactor: String!
maintenanceResponsible: String
maintenanceBenefactor: String
maintenanceAgreement: String
hasFixedRate: Boolean!
hasFixedRate: Boolean
fixedRate: Float
"""
Projektzuschuss:
@ -171,7 +213,7 @@ export default gql`
There is a currency signe at the first position or second position if - is set.
The kind of currency depends on the database.
"""
projectAllowance: String
projectAllowance: Money
notes: String
}
@ -179,15 +221,15 @@ export default gql`
"""
Eventually, this field will become an enum or a separate data table and user can choose from a pool of insurance companies.
"""
name: String!
benefactor: String!
billing: String!
noPnP: String!
name: String
benefactor: String
billing: String
noPnP: String
"eg. Anbieter, flotte, eigenleistung"
maintenanceResponsible: String!
maintenanceBenefactor: String!
maintenanceResponsible: String
maintenanceBenefactor: String
maintenanceAgreement: String
hasFixedRate: Boolean!
hasFixedRate: Boolean
fixedRate: Float
"""
Projektzuschuss:
@ -197,7 +239,7 @@ export default gql`
You can pass a currency signe at the first position or second position of + or - is set.
The kind of currency depends on the database.
"""
projectAllowance: String
projectAllowance: Money
notes: String
}
@ -223,37 +265,51 @@ export default gql`
You can pass a currency signe at the first position or second position of + or - is set.
The kind of currency depends on the database.
"""
projectAllowance: String
projectAllowance: Money
notes: String
}
type NumRange {
min: Float
max: Float
}
"""
If min or max is omitted, the omitted value will be the same as the other given value
So if you pass one as null, both values with be over written with null.
"""
input NumRangeInput {
min: Float
max: Float
}
"How are the dimensions and how much weight can handle a bike. This data is merged in the CargoBike table and the BikeModel table."
type DimensionsAndLoad {
hasCoverBox: Boolean!
hasCoverBox: Boolean
"cover box can be locked"
lockable: Boolean!
boxLength: Float!
boxWidth: Float!
boxHeight: Float!
maxWeightBox: Float!
maxWeightLuggageRack: Float!
maxWeightTotal: Float!
bikeLength: Float!
lockable: Boolean
boxLengthRange: NumRange
boxWidthRange: NumRange
boxHeightRange: NumRange
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
bikeLength: Float
bikeWidth: Float
bikeHeight: Float
bikeWeight: Float
}
input DimensionsAndLoadCreateInput {
hasCoverBox: Boolean!
lockable: Boolean!
boxLength: Float!
boxWidth: Float!
boxHeight: Float!
maxWeightBox: Float!
maxWeightLuggageRack: Float!
maxWeightTotal: Float!
bikeLength: Float!
hasCoverBox: Boolean
lockable: Boolean
boxLengthRange: NumRangeInput
boxWidthRange: NumRangeInput
boxHeightRange: NumRangeInput
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
bikeLength: Float
bikeWidth: Float
bikeHeight: Float
bikeWeight: Float
@ -262,9 +318,9 @@ export default gql`
input DimensionsAndLoadUpdateInput {
hasCoverBox: Boolean
lockable: Boolean
boxLength: Float
boxWidth: Float
boxHeight: Float
boxLengthRange: NumRangeInput
boxWidthRange: NumRangeInput
boxHeightRange: NumRangeInput
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
@ -280,16 +336,16 @@ export default gql`
So no id needed for mutation. One Mutation for the CargoBike will be enough.
"""
type TechnicalEquipment {
bicycleShift: String!
isEBike: Boolean!
hasLightSystem: Boolean!
bicycleShift: String
isEBike: Boolean
hasLightSystem: Boolean
specialFeatures: String
}
input TechnicalEquipmentCreateInput {
bicycleShift: String!
isEBike: Boolean!
hasLightSystem: Boolean!
bicycleShift: String
isEBike: Boolean
hasLightSystem: Boolean
specialFeatures: String
}
@ -306,7 +362,7 @@ export default gql`
So no id needed for mutation. One Mutation for the CargoBike will be enough.
"""
type Security {
frameNumber: String!
frameNumber: String
keyNumberFrameLock: String
keyNumberAXAChain: String
policeCoding: String
@ -314,7 +370,7 @@ export default gql`
}
input SecurityCreateInput {
frameNumber: String!
frameNumber: String
keyNumberFrameLock: String
keyNumberAXAChain: String
policeCoding: String
@ -354,8 +410,7 @@ export default gql`
"""
type Participant {
id: ID!
start: Date!
end: Date
dateRange: DateRange!
contactInformation: ContactInformation!
usernamefLotte: String
usernameSlack: String
@ -375,13 +430,12 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input ParticipantCreateInput {
"if not set, CURRENT_DATE will be used"
start: Date
end: Date
dateRange: DateRangeInput!
"must create contactinformation first, if you want to use new"
contactInformationId: ID!
usernamefLotte: String
@ -396,9 +450,7 @@ export default gql`
input ParticipantUpdateInput {
id: ID!
"if not set, CURRENT_DATE will be used"
start: Date
end: Date
dateRange: DateRangeInput
"must create contactinformation first, if you want to use new"
contactInformationId: ID
usernamefLotte: String
@ -428,7 +480,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input WorkshopCreateInput {
@ -458,7 +510,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input WorkshopTypeCreateInput {
@ -478,7 +530,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input EngagementTypeCreateInput {
@ -496,43 +548,38 @@ export default gql`
type Engagement {
id: ID!
engagementType: EngagementType!
from: Date!
to: Date
dateRange: DateRange!
participant: Participant!
cargoBike: CargoBike!
isLocked: Boolean!
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input EngagementCreateInput {
engagementTypeId: ID!
"will use CURRENT_DATE if not set"
from: Date
"will use infinit if not set"
to: Date
dateRange: DateRangeInput!
participantId: ID!
cargoBikeId: ID!
}
input EngagementUpdateInput {
id: ID!
engagementTypeId: ID
from: Date
to: Date
dateRange: DateRangeInput
participantId: ID
cargoBikeId: ID
keepLock: Boolean
}
type Taxes {
costCenter: String!
costCenter: String
organisationArea: OrganisationArea
}
input TaxesCreateInput {
costCenter: String!
costCenter: String
organisationArea: OrganisationArea
}
@ -560,7 +607,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input EquipmentCreateInput {
@ -592,11 +639,11 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input EquipmentTypeCreateInput {
name: String
name: String!
description: String
}
@ -607,7 +654,7 @@ export default gql`
keepLock: Boolean
}
"An Event is a point in time, when the state of the bike somehow changed."
"An Event is a point in time concerning one cargo bike of an event type. For example a chain swap."
type BikeEvent {
id: ID!
bikeEventType: BikeEventType!
@ -625,7 +672,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input BikeEventCreateInput {
@ -663,7 +710,9 @@ export default gql`
name: String!
isLockedByMe: Boolean!
isLocked: Boolean!
lockedUntil: Date
"null if not locked by other user"
lockedBy: ID
lockedUntil: DateTime
}
input BikeEventTypeUpdateInput {
@ -683,7 +732,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
"(dt. Anbieter)"
@ -717,7 +766,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input PersonCreateInput {
@ -744,7 +793,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input ContactInformationCreateInput {
@ -784,7 +833,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input OrganisationCreateInput {
@ -828,7 +877,7 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
"""
@ -862,13 +911,13 @@ export default gql`
"""
type LoanPeriod {
generalRemark: String
"notes for each day of the week, starting on Monday"
notes: [String]
"""
Loan times from and until for each day of the week.
Starting with Monday from, Monday to, Tuesday from, ..., Sunday to
"""
loanTimes: [String]
mo: String
tu: String
we: String
th: String
fr: String
sa: String
su: String
}
"""
@ -876,22 +925,35 @@ export default gql`
"""
input LoanPeriodInput {
generalRemark: String
"notes for each day of the week, starting on Monday"
notes: [String!]
mo: String
tu: String
we: String
th: String
fr: String
sa: String
su: String
}
type DateRange{
from: Date!
"will be infinity of not omitted"
to: Date
}
input DateRangeInput {
"format YYYY-MM-dd"
from: Date!
"""
Loan times from and until for each day of the week.
Starting with Monday from, Monday to, Tuesday from, ..., Sunday to
format YYYY-MM-dd
will be infinity of not omitted
"""
loanTimes: [String!]
to: Date
}
"(dt. Zeitscheibe) When was a bike where"
type TimeFrame {
id: ID!
"format YYYY-MM-dd"
from: Date!
"format YYYY-MM-dd"
to: Date
dateRange: DateRange!
note: String
lendingStation: LendingStation!
cargoBike: CargoBike!
@ -899,12 +961,11 @@ export default gql`
isLockedByMe: Boolean!
"null if not locked by other user"
lockedBy: ID
lockedUntil: Date
lockedUntil: DateTime
}
input TimeFrameCreateInput {
from: Date!
to: Date
dateRange: DateRangeInput!
note: String
lendingStationId: ID!
cargoBikeId: ID!
@ -912,8 +973,7 @@ export default gql`
input TimeFrameUpdateInput {
id: ID!
from: Date
to: Date
dateRange: DateRangeInput
note: String
lendingStationId: ID
cargoBikeId: ID
@ -953,41 +1013,55 @@ export default gql`
type Query {
"Will (eventually) return all properties of cargo bike"
cargoBikeById(id:ID!): CargoBike
"returns cargoBikes ordered by name ascending, relations are not loaded, use cargoBikeById instead"
cargoBikes(offset: Int!, limit: Int!): [CargoBike!]!
"Returns cargoBikes ordered by name ascending. If offset or limit is not provided, both values are ignored."
cargoBikes(offset: Int, limit: Int): [CargoBike!]!
engagementById(id: ID!): Engagement
engagements(offset: Int!, limit: Int!): [Engagement!]!
"If offset or limit is not provided, both values are ignored"
engagements(offset: Int, limit: Int): [Engagement!]!
engagementTypeById(id: ID!): EngagementType
engagementTypes(offset: Int!, limit: Int!): [EngagementType!]!
"If offset or limit is not provided, both values are ignored"
engagementTypes(offset: Int, limit: Int): [EngagementType!]!
"equipment by id, will return null if id not found"
equipmentById(id: ID!): Equipment
equipment(offset: Int!, limit: Int!): [Equipment!]!
"If offset or limit is not provided, both values are ignored"
equipment(offset: Int, limit: Int): [Equipment!]!
equipmentTypeById(id: ID!): EquipmentType
equipmentTypes(offset: Int!, limit: Int!): [EquipmentType!]!
"If offset or limit is not provided, both values are ignored"
equipmentTypes(offset: Int, limit: Int): [EquipmentType!]!
"return null if id not found"
providerById(id:ID!): Provider
"Returns providers with pagination"
providers(offset: Int!, limit: Int!): [Provider!]!
"Returns providers with pagination. If offset or limit is not provided, both values are ignored"
providers(offset: Int, limit: Int): [Provider!]!
"participant by id"
participantById(id:ID!): Participant
participants(offset: Int!, limit: Int!): [Participant!]!
"If offset or limit is not provided, both values are ignored"
participants(offset: Int, limit: Int): [Participant!]!
workshopTypeById(id: ID!): WorkshopType
workshopTypes(offset: Int!, limit: Int!): [WorkshopType!]!
"If offset or limit is not provided, both values are ignored"
workshopTypes(offset: Int, limit: Int): [WorkshopType!]!
workshopById(id: ID!): Workshop
workshops(offset: Int!, limit: Int!): [Workshop!]!
"If offset or limit is not provided, both values are ignored"
workshops(offset: Int, limit: Int): [Workshop!]!
lendingStationById(id:ID!): LendingStation
lendingStations(offset: Int!, limit: Int!): [LendingStation!]!
"If offset or limit is not provided, both values are ignored"
lendingStations(offset: Int, limit: Int): [LendingStation!]!
organisationById(id: ID!): Organisation
organisations(offset: Int!, limit: Int!): [Organisation!]!
"If offset or limit is not provided, both values are ignored"
organisations(offset: Int, limit: Int): [Organisation!]!
timeFrameById(id: ID!): TimeFrame
timeFrames(offset: Int!, limit: Int!): [TimeFrame!]!
"If offset or limit is not provided, both values are ignored"
timeFrames(offset: Int, limit: Int): [TimeFrame!]!
contactInformationById(id: ID!): ContactInformation
contactInformation(offset: Int!, limit: Int!): [ContactInformation!]!
"If offset or limit is not provided, both values are ignored"
contactInformation(offset: Int, limit: Int): [ContactInformation!]!
personById(id: ID!): Person
persons(offset: Int!, limit: Int!): [Person!]
bikeEventTypes(offset: Int!, limit: Int!): [BikeEventType!]
"If offset or limit is not provided, both values are ignored"
persons(offset: Int, limit: Int): [Person!]
"If offset or limit is not provided, both values are ignored"
bikeEventTypes(offset: Int, limit: Int): [BikeEventType!]
bikeEventTypeByd(id: ID!): BikeEventType
bikeEvents(offset: Int!, limit: Int!): [BikeEvent!]!
"If offset or limit is not provided, both values are ignored"
bikeEvents(offset: Int, limit: Int): [BikeEvent!]!
bikeEventById(id:ID!): BikeEvent
"actionLog for current user"
actionLog: [ActionLog!]
@ -1062,23 +1136,23 @@ export default gql`
"""
createParticipant(participant: ParticipantCreateInput!): Participant!
lockParticipant(id: ID!): Participant!
unlockParticipant(id: ID!): Boolean
unlockParticipant(id: ID!): Participant
updateParticipant(participant: ParticipantUpdateInput!): Participant!
deleteParticipant(id: ID!): Boolean!
createWorkshopType(workshopType: WorkshopTypeCreateInput!): WorkshopType!
lockWorkshopType(id: ID!): WorkshopType!
unlockWorkshopType(id: ID!): Boolean!
unlockWorkshopType(id: ID!): WorkshopType!
updateWorkshopType(workshopType: WorkshopTypeUpdateInput!): WorkshopType!
deleteWorkshopType(id: ID!): Boolean!
createWorkshop(workshop: WorkshopCreateInput!): Workshop!
lockWorkshop(id: ID!): Workshop!
unlockWorkshop(id: ID!): Boolean!
unlockWorkshop(id: ID!): Workshop!
updateWorkshop(workshop: WorkshopUpdateInput!): Workshop!
deleteWorkshop(id: ID!): Boolean!
"create new contactInfo"
createContactInformation(contactInformation: ContactInformationCreateInput!): ContactInformation!
lockContactInformation(id: ID!): ContactInformation!
unlockContactInformation(id: ID!): Boolean!
unlockContactInformation(id: ID!): ContactInformation!
updateContactInformation(contactInformation: ContactInformationUpdateInput!): ContactInformation!
deleteContactInformation(id: ID!): Boolean!
createPerson(person: PersonCreateInput!): Person!
@ -1089,22 +1163,22 @@ export default gql`
"create Engagement"
createEngagement(engagement: EngagementCreateInput): Engagement!
lockEngagement(id: ID!): Engagement!
unlockEngagement(id: ID!): Boolean!
unlockEngagement(id: ID!): Engagement!
updateEngagement(engagement: EngagementUpdateInput!): Engagement!
deleteEngagement(id: ID!): Boolean!
createEngagementType(engagementType: EngagementTypeCreateInput!): EngagementType!
lockEngagementType(id: ID!): EngagementType!
unlockEngagementType(id: ID!): Boolean!
unlockEngagementType(id: ID!): EngagementType!
updateEngagementType(engagementType: EngagementTypeUpdateInput!): EngagementType!
deleteEngagementType(id: ID!): Boolean!
createProvider(provider: ProviderCreateInput!): Provider!
lockProvider(id: ID!): Provider!
unlockProvider(id: ID!): Boolean!
unlockProvider(id: ID!): Provider!
updateProvider(provider: ProviderUpdateInput!): Provider!
deleteProvider(id: ID!): Boolean!
createOrganisation(organisation: OrganisationCreateInput!): Organisation!
lockOrganisation(id: ID!): Organisation!
unlockOrganisation(id: ID!): Boolean!
unlockOrganisation(id: ID!): Organisation!
updateOrganisation(organisation: OrganisationUpdateInput!): Organisation!
deleteOrganisation(id: ID!): Boolean!
}

Loading…
Cancel
Save