Merge pull request #23 from fLotte-meets-HWR-DB/dev

Dev
pull/26/head
leonnicolas 4 years ago committed by GitHub
commit 69d44b792a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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.

@ -183,10 +183,7 @@ export class LendingStationAPI extends DataSource {
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')

@ -23,45 +23,48 @@ 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 prepares the cargoBike struct, to be used in an update or create.
* 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 from
* @param to
* @param range
*/
function genNumRange (from: number, to: number) {
if (from === null || from === undefined) {
from = to;
} else if (to === null || to === undefined) {
to = from;
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;
}
return from ? '[' + from + ',' + to + ']' : null;
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) {
cargoBike.dimensionsAndLoad.boxLengthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxLength, cargoBike.dimensionsAndLoad.maxBoxLength);
cargoBike.dimensionsAndLoad.boxWidthRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxWidth, cargoBike.dimensionsAndLoad.maxBoxWidth);
cargoBike.dimensionsAndLoad.boxHeightRange = genNumRange(cargoBike.dimensionsAndLoad.minBoxHeight, cargoBike.dimensionsAndLoad.maxBoxHeight);
// delete this so update cargo bike works
delete cargoBike.dimensionsAndLoad.minBoxLength;
delete cargoBike.dimensionsAndLoad.maxBoxLength;
delete cargoBike.dimensionsAndLoad.minBoxWidth;
delete cargoBike.dimensionsAndLoad.maxBoxWidth;
delete cargoBike.dimensionsAndLoad.minBoxHeight;
delete cargoBike.dimensionsAndLoad.maxBoxHeight;
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.
* Returns true if locked by other user.
@ -273,10 +276,10 @@ 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;

@ -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';
@ -90,13 +89,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,10 +111,14 @@ export class TechnicalEquipment {
}
export class DimensionsAndLoad {
@Column()
@Column({
nullable: true
})
hasCoverBox: boolean;
@Column()
@Column({
nullable: true
})
lockable:boolean;
@Column({
@ -156,7 +165,7 @@ export class DimensionsAndLoad {
@Column({
nullable: true,
type: 'numrange'
type: 'decimal'
})
bikeWidth: number;
@ -195,6 +204,11 @@ export class CargoBike implements Lockable {
})
name: string;
@Column({
nullable: true
})
state: string;
@OneToMany(type => Equipment, equipment => equipment.cargoBikeId, {
nullable: true,
eager: true

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

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

@ -161,45 +161,12 @@ export default {
isLockedByMe: (parent: any, __: any, { req }: { req: any }) => isLockedByMe(parent, { req }),
isLocked: (parent: any, __: any, { req }: { req: any }) => isLocked(parent, { req })
},
DimensionsAndLoad: {
minBoxLength: (parent: any) => {
if (!parent.boxLengthRange || parent.boxLengthRange === 'empty') {
return null;
}
return parent.boxLengthRange ? (parent.boxLengthRange as string).split(',')[0].replace('[', '') : null;
},
maxBoxLength: (parent: any) => {
if (!parent.boxLengthRange || parent.boxLengthRange === 'empty') {
return null;
}
const str = (parent.boxLengthRange as string).split(',')[1].replace(']', '');
return (str.length > 0) ? str : null;
},
minBoxWidth: (parent: any) => {
if (!parent.boxWidthRange || parent.boxWidthRange === 'empty') {
return null;
}
return parent.boxWidthRange ? (parent.boxWidthRange as string).split(',')[0].replace('[', '') : null;
NumRange: {
min: (parent: string) => {
return parent.replace(/^\[(.*),.*]$/, '$1');
},
maxBoxWidth: (parent: any) => {
if (!parent.boxWidthRange || parent.boxWidthRange === 'empty') {
return null;
}
const str = (parent.boxWidthRange as string).split(',')[1].replace(']', '');
return (str.length > 0) ? str : null;
},
minBoxHeight: (parent: any) => {
if (!parent.boxHeightRange || parent.boxHeightRange === 'empty') {
return null;
}
return parent.boxHeightRange ? (parent.boxHeightRange as string).split(',')[0].replace('[', '') : null;
},
maxBoxHeight: (parent: any) => {
if (!parent.boxHeightRange || parent.boxHeightRange === 'empty') {
return null;
}
const str = (parent.boxHeightRange as string).split(',')[1].replace(']', '');
return (str.length > 0) ? str : null;
max: (parent: string) => {
return parent.replace(/^\[.*,(.*)]$/, '$1');
}
},
Equipment: {

@ -104,14 +104,15 @@ export default {
return parent.loanTimes ? parent.loanTimes : [];
}
},
TimeFrame: {
from (parent: any) {
return (parent.dateRange as string).split(',')[0].replace('[', '');
DateRange: {
from (parent: string) {
return parent.replace(/^\[(.*),.*\)$/, '$1');
},
to (parent: any) {
const str = (parent.dateRange as string).split(',')[1].replace(')', '');
return (str.length > 0) ? str : null;
to (parent: string) {
return parent.replace(/^\[.*,(.*)\)$/, '$1');
}
},
TimeFrame: {
cargoBike (parent: any, __: any, { dataSources, req }: { dataSources: any, req: any }) {
if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.cargoBikeAPI.cargoBikeByTimeFrameId(parent.id);

@ -33,12 +33,14 @@ export default gql`
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
name: String!
state: BikeState
modelName: String
numberOfWheels: Int
forCargo: Boolean
@ -56,7 +58,7 @@ 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]
"If offset or limit is not provided, both values are ignored"
@ -69,7 +71,7 @@ export default gql`
provider: Provider
"all participants currently engaged with the cargoBike"
participants: [Participant]
insuranceData: InsuranceData!
insuranceData: InsuranceData
lendingStation: LendingStation
taxes: Taxes
currentEngagements: [Engagement]
@ -83,6 +85,15 @@ export default gql`
lockedUntil: Date
}
"""
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
"""
@ -90,6 +101,7 @@ export default gql`
"see column A in info tabelle"
group: Group!
name: String!
state: BikeState
modelName: String!
numberOfWheels: Int!
forCargo: Boolean!
@ -102,11 +114,11 @@ export default gql`
"""
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!
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.
@ -122,8 +134,8 @@ export default gql`
stickerBikeNameState: StickerBikeNameState
note: String
providerId: ID
insuranceData: InsuranceDataCreateInput!
taxes: TaxesCreateInput!
insuranceData: InsuranceDataCreateInput
taxes: TaxesCreateInput
}
"""
@ -134,6 +146,7 @@ export default gql`
"see column A in info tabelle"
group: Group
name: String
state: BikeState
modelName: String
numberOfWheels: Int
forCargo: Boolean
@ -180,15 +193,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:
@ -206,15 +219,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:
@ -254,17 +267,28 @@ export default gql`
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!
minBoxLength: Float
maxBoxLength: Float
minBoxWidth: Float
maxBoxWidth: Float
minBoxHeight: Float
maxBoxHeight: Float
lockable: Boolean
boxLengthRange: NumRange
boxWidthRange: NumRange
boxHeightRange: NumRange
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
@ -275,14 +299,11 @@ export default gql`
}
input DimensionsAndLoadCreateInput {
hasCoverBox: Boolean!
lockable: Boolean!
minBoxLength: Float
maxBoxLength: Float
minBoxWidth: Float
maxBoxWidth: Float
minBoxHeight: Float
maxBoxHeight: Float
hasCoverBox: Boolean
lockable: Boolean
boxLengthRange: NumRangeInput
boxWidthRange: NumRangeInput
boxHeightRange: NumRangeInput
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
@ -295,12 +316,9 @@ export default gql`
input DimensionsAndLoadUpdateInput {
hasCoverBox: Boolean
lockable: Boolean
minBoxLength: Float
maxBoxLength: Float
minBoxWidth: Float
maxBoxWidth: Float
minBoxHeight: Float
maxBoxHeight: Float
boxLengthRange: NumRangeInput
boxWidthRange: NumRangeInput
boxHeightRange: NumRangeInput
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
@ -316,16 +334,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
}
@ -563,12 +581,12 @@ export default gql`
}
type Taxes {
costCenter: String!
costCenter: String
organisationArea: OrganisationArea
}
input TaxesCreateInput {
costCenter: String!
costCenter: String
organisationArea: OrganisationArea
}
@ -643,7 +661,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!
@ -923,13 +941,26 @@ export default gql`
loanTimes: [String!]
}
"(dt. Zeitscheibe) When was a bike where"
type TimeFrame {
id: ID!
"format YYYY-MM-dd"
type DateRange{
from: Date!
"will be infinity of not omitted"
to: Date
}
input DateRangeInput {
"format YYYY-MM-dd"
from: Date!
"""
format YYYY-MM-dd
will be infinity of not omitted
"""
to: Date
}
"(dt. Zeitscheibe) When was a bike where"
type TimeFrame {
id: ID!
dateRange: DateRange!
note: String
lendingStation: LendingStation!
cargoBike: CargoBike!
@ -941,8 +972,7 @@ export default gql`
}
input TimeFrameCreateInput {
from: Date!
to: Date
dateRange: DateRangeInput!
note: String
lendingStationId: ID!
cargoBikeId: ID!
@ -950,8 +980,7 @@ export default gql`
input TimeFrameUpdateInput {
id: ID!
from: Date
to: Date
dateRange: DateRangeInput
note: String
lendingStationId: ID
cargoBikeId: ID

Loading…
Cancel
Save