diff --git a/Dockerfile b/Dockerfile index c7e451f..4ed180b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ FROM node:14.14.0-alpine3.10 AS builder -RUN npm --version WORKDIR / COPY ./src /src diff --git a/src/app.ts b/src/app.ts index 9e5d13f..0b5f44c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -56,6 +56,7 @@ import { ActionLog } from './model/ActionLog'; import actionLogResolvers from './resolvers/actionLogResolvers'; import { ActionLogAPI } from './datasources/db/actionLogAPI'; import bodyParser from 'body-parser'; +import { CopyConfig } from './model/CopyConfig'; const cors = require('cors'); require('dotenv').config(); @@ -83,7 +84,8 @@ export function getConnectionOptions (): ConnectionOptions { Workshop, Person, WorkshopType, - ActionLog + ActionLog, + CopyConfig ], synchronize: true, logging: false @@ -127,8 +129,12 @@ export async function getApp (connOptions: ConnectionOptions) { } } + let cargoBikeAPI: CargoBikeAPI; try { await createConnection(connOptions); + // init copy config + cargoBikeAPI = new CargoBikeAPI(); + await cargoBikeAPI.populateCopyConfig(); } catch (err) { console.error(err); } @@ -145,7 +151,7 @@ export async function getApp (connOptions: ConnectionOptions) { ], typeDefs, dataSources: () => ({ - cargoBikeAPI: new CargoBikeAPI(), + cargoBikeAPI: cargoBikeAPI, lendingStationAPI: new LendingStationAPI(), participantAPI: new ParticipantAPI(), contactInformationAPI: new ContactInformationAPI(), diff --git a/src/datasources/db/cargobikeAPI.ts b/src/datasources/db/cargobikeAPI.ts index 40f7093..0a1c804 100644 --- a/src/datasources/db/cargobikeAPI.ts +++ b/src/datasources/db/cargobikeAPI.ts @@ -18,6 +18,7 @@ This file is part of fLotte-API-Server. */ import { DataSource } from 'apollo-datasource'; +import { ApolloError } from 'apollo-server-express'; import { Connection, EntityManager, getConnection } from 'typeorm'; import { CargoBike } from '../../model/CargoBike'; import { BikeEvent } from '../../model/BikeEvent'; @@ -31,6 +32,7 @@ import { BikeEventType } from '../../model/BikeEventType'; import { Actions } from '../../model/ActionLog'; import { ResourceLockedError } from '../../errors/ResourceLockedError'; import { NotFoundError } from '../../errors/NotFoundError'; +import { CopyConfig } from '../../model/CopyConfig'; /** * extended datasource to feed resolvers with data about cargoBikes @@ -42,6 +44,55 @@ export class CargoBikeAPI extends DataSource { this.connection = getConnection(); } + private async addCopyConfig (key: string, value: boolean) { + return await this.connection.getRepository(CopyConfig) + .createQueryBuilder('cc') + .insert() + .values([{ key, value }]) + .execute(); + } + + /** + * populate CopyConfig with default values + */ + async populateCopyConfig () { + if (await this.connection.getRepository(CopyConfig) + .createQueryBuilder('cc') + .select() + .getCount() === 0) { + const config: CopyConfig[] = [ + { key: 'id', value: false }, + { key: 'group', value: true }, + { key: 'name', value: true }, + { key: 'state', value: true }, + { key: 'equipmentIds', value: false }, + { key: 'equipmentTypeIds', value: false }, + { key: 'security', value: false }, + { key: 'stickerBikeNameState', value: true }, + { key: 'note', value: true }, + { key: 'providerId', value: false }, + { key: 'bikeEvents', value: false }, + { key: 'insuranceData', value: true }, + { key: 'timeFrames', value: false }, + { key: 'engagement', value: false }, + { key: 'taxes', value: true }, + { key: 'description', value: true }, + { key: 'modelName', value: true }, + { key: 'numberOfWheels', value: true }, + { key: 'forCargo', value: true }, + { key: 'forChildren', value: true }, + { key: 'numberOfChildren', value: true }, + { key: 'technicalEquipment', value: true }, + { key: 'dimensionsAndLoad', value: true } + ]; + await this.connection.getRepository(CopyConfig) + .createQueryBuilder('cc') + .insert() + .values(config) + .execute(); + } + } + async getCargoBikes (offset?: number, limit?: number) { return await DBUtils.getAllEntity(this.connection, CargoBike, 'cb', offset, limit); } @@ -50,12 +101,14 @@ export class CargoBikeAPI extends DataSource { * Finds cargo bike by id, returns null if id was not found * @param id */ - async findCargoBikeById (id: number) { + async findCargoBikeById (id: number) : Promise { return await this.connection.getRepository(CargoBike) .createQueryBuilder('cb') .select() .where('id = :id', { id }) - .getOne(); + .getOne().catch(() => { + throw new NotFoundError('CargoBike', 'id', id); + }); } async findCargoBikeByEngagementId (id: number) { @@ -498,4 +551,38 @@ export class CargoBikeAPI extends DataSource { .of(id) .loadMany(); } + + async copyCargoBikeById (id: number) { + // load keys + const keys = await this.connection.getRepository(CopyConfig) + .createQueryBuilder('cc') + .select() + .getMany(); + const cargoBike: any = await this.findCargoBikeById(id); + keys.forEach(value => { + if (value.value === false && value.key !== 'id') { + delete cargoBike[value.key]; + } + }); + return cargoBike; + } + + async editCopyConfig (key: string, value: boolean) : Promise { + return await this.connection.getRepository(CopyConfig) + .createQueryBuilder('cc') + .update() + .set({ value: value }) + .where('key = :key', { key: key }) + .returning('*') + .execute().then((v) => { + if (v.affected !== 1) { + throw new NotFoundError('CopyConfig', 'key', key); + } else { + return true; + } + }, + (e) => { + throw new ApolloError(e); + }); + } } diff --git a/src/datasources/userserver/permission.ts b/src/datasources/userserver/permission.ts index 2506882..cd66efd 100644 --- a/src/datasources/userserver/permission.ts +++ b/src/datasources/userserver/permission.ts @@ -61,7 +61,8 @@ export enum Permission { DeleteWorkshopType = 'WORKSHOP_TYPE_DELETE', DeleteEventType = 'EVENT_TYPE_DELETE', DeleteEquipmentType = 'EQUIPMENT_TYPE_DELETE', - DeleteEngagementType = 'ENGAGEMENT_TYPE_DELETE' + DeleteEngagementType = 'ENGAGEMENT_TYPE_DELETE', + EditCopyConfig = 'EDIT_COPY_CONFIG' } // Permissions where the creation will be requested on startup @@ -237,5 +238,9 @@ export const requiredPermissions = [ { name: Permission.DeleteEngagementType, description: 'Allows to delete engagement types' + }, + { + name: Permission.EditCopyConfig, + description: 'Allow to edit the copy config for cargo bikes' } ]; diff --git a/src/model/CopyConfig.ts b/src/model/CopyConfig.ts new file mode 100644 index 0000000..adb8b51 --- /dev/null +++ b/src/model/CopyConfig.ts @@ -0,0 +1,34 @@ +/* +Copyright (C) 2020 Leon Löchner + +This file is part of fLotte-API-Server. + + fLotte-API-Server is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + fLotte-API-Server is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with fLotte-API-Server. If not, see . +*/ + +/* eslint no-unused-vars: "off" */ + +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class CopyConfig { + @PrimaryColumn() + key: string; + + @Column({ + type: 'boolean', + default: true + }) + value: boolean; +} diff --git a/src/model/Participant.ts b/src/model/Participant.ts index d623143..c592fec 100644 --- a/src/model/Participant.ts +++ b/src/model/Participant.ts @@ -44,7 +44,7 @@ export class Participant implements Lockable { @Column({ nullable: true }) - usernameflotte: string; + usernamefLotte: string; @Column({ nullable: true diff --git a/src/resolvers/cargoBikeResolver.ts b/src/resolvers/cargoBikeResolver.ts index d008537..da64451 100644 --- a/src/resolvers/cargoBikeResolver.ts +++ b/src/resolvers/cargoBikeResolver.ts @@ -51,7 +51,7 @@ export default { throw new PermissionError(); } }, - bikeEventTypeByd: (_:any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { + bikeEventTypeById: (_:any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadBikeEvent)) { return dataSources.cargoBikeAPI.bikeEventTypeById(id); } else { @@ -92,6 +92,13 @@ export default { } else { throw new PermissionError(); } + }, + copyCargoBikeById: (_:any, { id }: {id:number}, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.ReadBike)) { + return dataSources.cargoBikeAPI.copyCargoBikeById(id); + } else { + throw new PermissionError(); + } } }, CargoBike: { @@ -395,6 +402,13 @@ export default { } else { throw new PermissionError(); } + }, + editCopyConfig: (_: any, { key, value }: { key: string, value: boolean }, { dataSources, req }: { dataSources: any, req: any }) => { + if (req.permissions.includes(Permission.EditCopyConfig)) { + return dataSources.cargoBikeAPI.editCopyConfig(key, value); + } else { + throw new PermissionError(); + } } } }; diff --git a/src/resolvers/participantResolvers.ts b/src/resolvers/participantResolvers.ts index f79b572..ac26082 100644 --- a/src/resolvers/participantResolvers.ts +++ b/src/resolvers/participantResolvers.ts @@ -25,7 +25,7 @@ export default { Query: { participantById: (_: any, { id }: { id: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.ReadParticipant)) { - return dataSources.participantAPI.getParticipantById(id); + return dataSources.participantAPI.participantById(id); } else { throw new PermissionError(); } @@ -119,6 +119,10 @@ export default { isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) }, + EngagementType: { + isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }), + isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req }) + }, Mutation: { createParticipant: (_: any, { participant }: { participant: any }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteParticipant)) { @@ -136,7 +140,7 @@ export default { }, unlockParticipant: (_: any, { id }: { id: number }, { dataSources, req }: { dataSources: any, req: any }) => { if (req.permissions.includes(Permission.WriteParticipant)) { - return dataSources.participantAPI.unlockeParticipant(id, req.userId); + return dataSources.participantAPI.unlockParticipant(id, req.userId); } else { throw new PermissionError(); } diff --git a/src/schema/type-defs.ts b/src/schema/type-defs.ts index 535fb61..a4e005c 100644 --- a/src/schema/type-defs.ts +++ b/src/schema/type-defs.ts @@ -35,6 +35,7 @@ export default gql` The kind of currency depends on the database. """ scalar Money + scalar Link "The CargoBike type is central to the graph. You could call it the root." type CargoBike { @@ -667,7 +668,7 @@ export default gql` """ Path to documents """ - documents: [String!]! + documents: [Link!]! remark: String isLocked: Boolean! isLockedByMe: Boolean! @@ -686,7 +687,7 @@ export default gql` """ Path to documents """ - documents: [String] + documents: [Link] remark: String } @@ -701,7 +702,7 @@ export default gql` """ Path to documents """ - documents: [String] + documents: [Link] remark: String keepLock: Boolean } @@ -1011,6 +1012,8 @@ export default gql` type Query { "Will (eventually) return all properties of cargo bike" cargoBikeById(id:ID!): CargoBike + "copies cargoBike, the id of the copy needs to be delted by the front end. This function will not create a new entry in the data base" + copyCargoBikeById(id: ID!): 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 @@ -1057,7 +1060,7 @@ export default gql` 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 + bikeEventTypeById(id: ID!): BikeEventType "If offset or limit is not provided, both values are ignored" bikeEvents(offset: Int, limit: Int): [BikeEvent!]! bikeEventById(id:ID!): BikeEvent @@ -1083,6 +1086,8 @@ export default gql` updateCargoBike(cargoBike: CargoBikeUpdateInput!): CargoBike! "true on success" deleteCargoBike(id: ID!): Boolean! + "edit or add key value pair to copy config for cargo bikes" + editCopyConfig(key: String!, value: Boolean!): Boolean! """ EQUIPMENT creates new peace of unique Equipment