Merge pull request #8 from flotte-goes-smart/dev

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

@ -1,15 +1,12 @@
# API-server # API-server
Apollo server written in typescript that handles business logic. Apollo server written in typescript that handles business logic.
## Assumptions
Userserver and postgres are running e.g. with Julius' Docker Compose.
## Usage ## Usage
### Configure Postgres
Install postgresql and configure it, so the Database is accessible from remote hosts (only necessary for docker container [Here](https://wiki.archlinux.org/index.php/PostgreSQL))
See postgres client config in __ormconfig.json__
### Docker ### Docker
```bash ```bash
docker build -t <image name> . docker build -t <image name> .
docker run --rm --network="host" <image name> docker run --rm -p 4000:4000 <image name>
``` ```
The Dockerfile is pretty stupid and could produce a smaller image, e.g. with multistage build. The Dockerfile is pretty stupid and could produce a smaller image, e.g. with multistage build.
### Compile and run ### Compile and run
@ -33,3 +30,14 @@ And start gulp in watch mode
gulp watch gulp watch
``` ```
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_ and finally restart the server.
## Environment Variables
The following environment variables can be used to configure the server:
```bash
RPC_HOST=host:port
NODE_ENV=development/porduction
POSTGRES_CONNECTION_URL=postgres://username:password@host:port/database_name
```
- __RPC_HOST__ is used for the connection with the userserver.
- __NODE_ENV__ will not check authentication if set to development
- __POSTGRES_CONNECTION_URL__ for connection with the postgres database

@ -12,14 +12,24 @@ export class CargoBikeAPI extends DataSource {
this.connection = getConnection(); this.connection = getConnection();
} }
async getCargoBikes () {
return await this.connection.createQueryBuilder()
.select('cargoBikes')
.from(CargoBike, 'cargoBikes')
.getMany();
}
/** /**
* Finds cargo bike by id * Finds cargo bike by id, retuns error if id not found
* @param param0 id of bike
*/ */
async findCargoBikeById ({ id }:{id: any}) { async findCargoBikeById ({ id }:{id: any}) {
return { return await this.connection.manager
id, .createQueryBuilder()
name: 'token' .select('cargoBike')
}; .from(CargoBike, 'cargoBike')
.where('cargoBike.id = :id', { id })
.getOne() || new GraphQLError('ID not found');
} }
async updateBike ({ id, name }:{id:any, name: string }) { async updateBike ({ id, name }:{id:any, name: string }) {
@ -39,47 +49,43 @@ export class CargoBikeAPI extends DataSource {
} }
/** /**
* Creates or Updates CargoBike * Updates CargoBike and return updated cargoBike
* Will create bike, if no id given, else it will update bike with given id. * @param param0 cargoBike to be updated
* @param param0 cargoBike to be updated or created
*/ */
async updateCargoBike ({ cargoBike }:{ cargoBike: any }) { async updateCargoBike ({ cargoBike }:{ cargoBike: any }) {
if (cargoBike.id) { const bike = await this.connection.manager.createQueryBuilder()
// update bike with given id .select('cargoBike')
const bike = await this.connection.manager.createQueryBuilder() .from(CargoBike, 'cargoBike')
.select('cargoBike') .where('cargoBike.id = :id', { id: cargoBike.id })
.from(CargoBike, 'cargoBike') .getOne();
.where('cargoBike.id = :id', { id: cargoBike.id }) if (bike) {
.getOne(); await this.connection
if (bike) {
// bike exists
await this.connection
.createQueryBuilder()
.update(CargoBike)
.set({ ...cargoBike })
.where('id = :id', { id: bike.id })
.execute();
return await this.connection
.createQueryBuilder()
.select('cargoBike')
.from(CargoBike, 'cargoBike')
.where('cargoBike.id = :id', { id: bike.id })
.getOne();
} else {
return new GraphQLError('ID not in database');
}
} else {
// create new bike
const inserts = await this.connection.manager
.createQueryBuilder() .createQueryBuilder()
.insert() .update(CargoBike)
.into(CargoBike) .set({ ...cargoBike })
.values([cargoBike]) .where('id = :id', { id: bike.id })
.returning('*')
.execute(); .execute();
const newbike = inserts.generatedMaps[0]; return await this.findCargoBikeById({ id: bike.id });
newbike.id = inserts.identifiers[0].id; } else {
return newbike; return new GraphQLError('ID not in database');
} }
} }
/**
* createCargoBike
* created CargoBike and returns created bike with new ID
* @param param0 cargoBike to be created
*/
async createCargoBike ({ cargoBike }: { cargoBike: any }) {
const inserts = await this.connection.manager
.createQueryBuilder()
.insert()
.into(CargoBike)
.values([cargoBike])
.returning('*')
.execute();
const newbike = inserts.generatedMaps[0];
newbike.id = inserts.identifiers[0].id;
return newbike;
}
} }

@ -1,5 +1,7 @@
import { DataSource } from 'apollo-datasource'; import { DataSource } from 'apollo-datasource';
import { GraphQLError } from 'graphql';
import { Connection, getConnection } from 'typeorm'; import { Connection, getConnection } from 'typeorm';
import { LendingStation } from '../../model/LendingStation';
export class LendingStationAPI extends DataSource { export class LendingStationAPI extends DataSource {
connection : Connection connection : Connection
@ -8,7 +10,64 @@ export class LendingStationAPI extends DataSource {
this.connection = getConnection(); this.connection = getConnection();
} }
async getLendingStationById ({ id }: { id: any }) {
return await this.connection.manager
.createQueryBuilder()
.select('lendingStation')
.from(LendingStation, 'lendingStation')
.where('lendingStation.id = :id', { id: id })
.getOne();
}
/**
* get all lendingStations
*/
async getLendingStations () {
return await this.connection.manager
.createQueryBuilder()
.select('lendingStation')
.from(LendingStation, 'lendingStation')
.getMany() || new GraphQLError('Internal Server Error: could not query data from table lendingStation');
}
/**
* creates new lendingStation and returns new lendingStation with its new id
* @param param0 new lendingStation
*/
async createLendingStation ({ lendingStation }:{ lendingStation: any }) {
console.log(lendingStation);
const inserts = await this.connection.manager
.createQueryBuilder()
.insert()
.into(LendingStation)
.values([lendingStation])
.returning('*')
.execute();
const newLendingStaion = inserts.generatedMaps[0];
newLendingStaion.id = inserts.identifiers[0].id;
return newLendingStaion;
}
/**
* updates lendingStation and return updated lendingStation
* @param param0 lendingStation to be updated
*/
async updateLendingStation ({ lendingStation }:{ lendingStation: any }) { async updateLendingStation ({ lendingStation }:{ lendingStation: any }) {
return lendingStation; const oldLendingStation = await this.connection.manager.createQueryBuilder()
.select('lendingStation')
.from(LendingStation, 'lendingStation')
.where('lendingStation.id = :id', { id: lendingStation.id })
.getOne();
if (oldLendingStation) {
await this.connection
.createQueryBuilder()
.update(LendingStation)
.set({ ...lendingStation })
.where('id = :id', { id: lendingStation.id })
.execute();
return this.getLendingStationById({ id: lendingStation.id });
} else {
return new GraphQLError('ID not in database');
}
} }
} }

@ -20,6 +20,8 @@ import { Organization } from './model/Organization';
import { Provider } from './model/Provider'; import { Provider } from './model/Provider';
import { Engagement } from './model/Engagement'; import { Engagement } from './model/Engagement';
import { Workshop } from './model/Workshop'; import { Workshop } from './model/Workshop';
import { LendingStationAPI } from './datasources/db/lendingstationAPI';
import lendingstationResolvers from './resolvers/lendingstationResolvers';
require('dotenv').config(); require('dotenv').config();
@ -77,10 +79,14 @@ createConnection({
const userAPI = new UserServerAPI(process.env.RPC_HOST); const userAPI = new UserServerAPI(process.env.RPC_HOST);
const server = new ApolloServer({ const server = new ApolloServer({
resolvers: [bikeresolver], resolvers: [
bikeresolver,
lendingstationResolvers
],
typeDefs, typeDefs,
dataSources: () => ({ dataSources: () => ({
cargoBikeAPI: new CargoBikeAPI(), cargoBikeAPI: new CargoBikeAPI(),
lendingStationAPI: new LendingStationAPI(),
userAPI userAPI
}), }),
context: (req: any) => { context: (req: any) => {

@ -3,6 +3,7 @@ import { ContactInformation } from './ContactInformation';
import { LoanPeriod } from './LoanPeriod'; import { LoanPeriod } from './LoanPeriod';
import { CargoBike } from './CargoBike'; import { CargoBike } from './CargoBike';
import { Organization } from './Organization'; import { Organization } from './Organization';
import { Address } from './Provider';
@Entity() @Entity()
export class LendingStation { export class LendingStation {
@ -16,14 +17,8 @@ export class LendingStation {
@JoinTable() @JoinTable()
contactInformation: ContactInformation[]; contactInformation: ContactInformation[];
@Column() @Column(type => Address)
addressStreet: string; address: Address;
@Column()
addressStreetNo: string;
@Column()
addressZip: string;
@OneToMany(type => LoanPeriod, loanPeriod => loanPeriod.lendingStation) @OneToMany(type => LoanPeriod, loanPeriod => loanPeriod.lendingStation)
loanPeriods: LoanPeriod[]; loanPeriods: LoanPeriod[];

@ -5,6 +5,17 @@ import { ContactInformation } from './ContactInformation';
import { LendingStation } from './LendingStation'; import { LendingStation } from './LendingStation';
import { Organization } from './Organization'; import { Organization } from './Organization';
export class Address {
@Column()
street: string;
@Column()
number: string;
@Column()
zip: string;
}
@Entity() @Entity()
export class Provider { export class Provider {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@ -18,14 +29,8 @@ export class Provider {
}) })
formularName: String; formularName: String;
@Column() @Column(type => Address)
street: string; address: Address;
@Column()
number: string;
@Column()
zip: string;
@OneToMany(type => ContactInformation, contactInformation => contactInformation.provider) @OneToMany(type => ContactInformation, contactInformation => contactInformation.provider)
contactInformation: ContactInformation[]; contactInformation: ContactInformation[];

@ -3,12 +3,19 @@ import { GraphQLError } from 'graphql';
export default { export default {
Query: { Query: {
cargobikeById: (_: any, { id }:{id: any}, { dataSources, req }:{dataSources: any, req: any }) => { cargoBikeById: (_: any, { id }:{id: any}, { dataSources, req }:{dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBike)) { if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.cargoBikeAPI.findCargoBikeById({ id }); return dataSources.cargoBikeAPI.findCargoBikeById({ id });
} else { } else {
return new GraphQLError('Insufficient Permissions'); return new GraphQLError('Insufficient Permissions');
} }
},
cargoBikes: (_: any, __: any, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.cargoBikeAPI.getCargoBikes();
} else {
return new GraphQLError('Insufficiant Permissions');
}
} }
}, },
Mutation: { Mutation: {
@ -19,7 +26,14 @@ export default {
return new GraphQLError('Insufficient Permissions'); return new GraphQLError('Insufficient Permissions');
} }
}, },
cargoBike: (_: any, { cargoBike }: { cargoBike: any }, { dataSources, req }:{dataSources: any, req: any }) => { createCargoBike: (_: any, { cargoBike }: { cargoBike: any }, { dataSources, req }:{dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.WriteBike)) {
return dataSources.cargoBikeAPI.createCargoBike({ cargoBike });
} else {
return new GraphQLError('Insufficient Permissions');
}
},
updateCargoBike: (_: any, { cargoBike }: { cargoBike: any }, { dataSources, req }:{dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.WriteBike)) { if (req.permissions.includes(Permission.WriteBike)) {
return dataSources.cargoBikeAPI.updateCargoBike({ cargoBike }); return dataSources.cargoBikeAPI.updateCargoBike({ cargoBike });
} else { } else {

@ -4,11 +4,32 @@ import { LendingStation } from '../model/LendingStation';
export default { export default {
Query: { Query: {
lendingStationById: (_: any, { id }: { id: any }, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.lendingStationAPI.getLendingStationById({ id });
} else {
return new GraphQLError('Insufficient Permissions');
}
},
lendingStations: (_: any, __: any, { dataSources, req }: { dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.ReadBike)) {
return dataSources.lendingStationAPI.getLendingStations();
} else {
return new GraphQLError('Insufficient Permissions');
}
}
}, },
Mutation: { Mutation: {
lendingStation: (_: any, { lendingStation }:{ lendingStation: LendingStation }, { dataSources, req }:{dataSources: any, req: any }) => { createLendingStation: (_: any, { lendingStation }:{ lendingStation: LendingStation }, { dataSources, req }:{dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.WriteBike)) {
return dataSources.lendingStationAPI.createLendingStation({ lendingStation });
} else {
return new GraphQLError('Insufficient Permissions');
}
},
updateLendingStation: (_: any, { lendingStation }:{ lendingStation: LendingStation }, { dataSources, req }:{dataSources: any, req: any }) => {
if (req.permissions.includes(Permission.WriteBike)) { if (req.permissions.includes(Permission.WriteBike)) {
return new GraphQLError('Not implemented'); return dataSources.lendingStationAPI.updateLendingStation({ lendingStation });
} else { } else {
return new GraphQLError('Insufficient Permissions'); return new GraphQLError('Insufficient Permissions');
} }

@ -46,9 +46,7 @@ type CargoBike {
lockedUntil: Date lockedUntil: Date
} }
input CargoBikeInput { input CargoBikeCreateInput {
"if null, then new bike will be created, else old bike will be updated."
id: ID
"see column A in info tabelle" "see column A in info tabelle"
group: Group! group: Group!
name: String! name: String!
@ -61,15 +59,15 @@ input CargoBikeInput {
Safety is a custom type, that stores information about security features. Safety is a custom type, that stores information about security features.
TODO: Should this be calles Security? TODO: Should this be calles Security?
""" """
security: SecurityInput! security: SecurityCreateInput!
""" """
Does not refere to an extra table in the database. Does not refere to an extra table in the database.
""" """
technicalEquipment: TechnicalEquipmentInput! technicalEquipment: TechnicalEquipmentCreateInput!
""" """
Does not refere to an extra table in the database. Does not refere to an extra table in the database.
""" """
dimensionsAndLoad: DimensionsAndLoadInput! dimensionsAndLoad: DimensionsAndLoadCreateInput!
"Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2" "Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2"
otherEquipment: [String] otherEquipment: [String]
@ -77,8 +75,42 @@ input CargoBikeInput {
stickerBikeNameState: StickerBikeNameState stickerBikeNameState: StickerBikeNameState
note: String note: String
provider: String provider: String
insuranceData: InsuranceDataInput! insuranceData: InsuranceDataCreateInput!
taxes: TaxesInput taxes: TaxesCreateInput
}
input CargoBikeUpdateInput {
id: ID!
"see column A in info tabelle"
group: Group
name: String
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 calles Security?
"""
security: SecurityUpdateInput
"""
Does not refere to an extra table in the database.
"""
technicalEquipment: TechnicalEquipmentUpdateInput
"""
Does not refere to an extra table in the database.
"""
dimensionsAndLoad: DimensionsAndLoadUpdateInput
"Refers to equipment that is not unique. See kommentierte info tabelle -> Fragen -> Frage 2"
otherEquipment: [String]
"Sticker State"
stickerBikeNameState: StickerBikeNameState
note: String
provider: String
insuranceData: InsuranceDataUpdateInput
taxes: TaxesUpdateInput
} }
type InsuranceData { type InsuranceData {
@ -100,7 +132,7 @@ type InsuranceData {
notes: String notes: String
} }
input InsuranceDataInput { input InsuranceDataCreateInput {
""" """
Eventuelly, this field will become an enum or a seperate data table and user can choose from a pool of insurance companies. Eventuelly, this field will become an enum or a seperate data table and user can choose from a pool of insurance companies.
""" """
@ -119,6 +151,25 @@ input InsuranceDataInput {
notes: String notes: String
} }
input InsuranceDataUpdateInput {
"""
Eventuelly, this field will become an enum or a seperate data table and user can choose from a pool of insurance companies.
"""
name: String
benefactor: String
billing: String
noPnP: String
"eg. Anbieter, flotte, eigenleistung"
maintananceResponsible: String
maintananceBenefactor: String
maintananceAgreement: String
hasFixedRate: Boolean!
fixedRate: Float
"Projektzuschuss"
projectAllowance: Float
notes: String
}
enum Group{ enum Group{
KL KL
LI LI
@ -179,11 +230,16 @@ type Taxes {
organizationArea: OrganizationArea organizationArea: OrganizationArea
} }
input TaxesInput { input TaxesCreateInput {
costCenter: String! costCenter: String!
organizationArea: OrganizationArea organizationArea: OrganizationArea
} }
input TaxesUpdateInput {
costCenter: String
organizationArea: OrganizationArea
}
enum OrganizationArea { enum OrganizationArea {
IB IB
ZB ZB
@ -255,7 +311,7 @@ type DimensionsAndLoad {
bikeWeight: Float bikeWeight: Float
} }
input DimensionsAndLoadInput { input DimensionsAndLoadCreateInput {
hasCoverBox: Boolean! hasCoverBox: Boolean!
lockable: Boolean! lockable: Boolean!
boxLength: Float! boxLength: Float!
@ -270,6 +326,21 @@ input DimensionsAndLoadInput {
bikeWeight: Float bikeWeight: Float
} }
input DimensionsAndLoadUpdateInput {
hasCoverBox: Boolean
lockable: Boolean
boxLength: Float
boxWidth: Float
boxHeight: Float
maxWeightBox: Float
maxWeightLuggageRack: Float
maxWeightTotal: Float
bikeLength: Float
bikeWidth: Float
bikeHeight: Float
bikeWeight: Float
}
""" """
Some Technical Info about the bike. Some Technical Info about the bike.
This should be 1-1 Relation with the CargoBike. This should be 1-1 Relation with the CargoBike.
@ -282,13 +353,20 @@ type TechnicalEquipment {
specialFeatures: String specialFeatures: String
} }
input TechnicalEquipmentInput { input TechnicalEquipmentCreateInput {
bicycleShift: String! bicycleShift: String!
isEBike: Boolean! isEBike: Boolean!
hasLightSystem: Boolean! hasLightSystem: Boolean!
specialFeatures: String specialFeatures: String
} }
input TechnicalEquipmentUpdateInput {
bicycleShift: String
isEBike: Boolean
hasLightSystem: Boolean
specialFeatures: String
}
""" """
The Security Info about the bike. The Security Info about the bike.
his should be 1-1 Relation with the CargoBike. his should be 1-1 Relation with the CargoBike.
@ -302,7 +380,7 @@ type Security {
adfcCoding: String adfcCoding: String
} }
input SecurityInput { input SecurityCreateInput {
frameNumber: String! frameNumber: String!
keyNumberFrameLock: String keyNumberFrameLock: String
keyNumberAXAChain: String keyNumberAXAChain: String
@ -310,6 +388,14 @@ input SecurityInput {
adfcCoding: String adfcCoding: String
} }
input SecurityUpdateInput {
frameNumber: String
keyNumberFrameLock: String
keyNumberAXAChain: String
policeCoding: String
adfcCoding: String
}
enum StickerBikeNameState { enum StickerBikeNameState {
OK OK
IMPROVE IMPROVE
@ -346,8 +432,21 @@ type ContactInformation {
note: String note: String
} }
input ContactInformationInput { input ContactInformationCreateInput {
id: ID name: String
firstName: String
retiredAt: Date
phoneExtern: String
phone2Extern: String
phoneIntern: String
phone2Intern: String
emailExtern: String
emailIntern: String
note: String
}
input ContactInformationUpdateInput {
id: ID!
name: String name: String
firstName: String firstName: String
retiredAt: Date retiredAt: Date
@ -382,13 +481,21 @@ type LendingStation {
loanPeriods: [LoanPeriod]! loanPeriods: [LoanPeriod]!
} }
input LendingStationInput { input LendingStationCreateInput {
id: ID name: String!
contactInformation: [ContactInformationCreateInput]!
address: AddressCreateInput!
loanTimes: LoanTimesInput
loanPeriods: [LoanPeriodCreateInput]!
}
input LendingStationUpdateInput {
id: ID!
name: String name: String
contactInformation: [ContactInformationInput] contactInformation: [ContactInformationUpdateInput]
address: AddressInput address: AddressUpdateInput
loanTimes: LoanTimesInput loanTimes: LoanTimesInput
loanPeriods: [LoanPeriodInput] loanPeriods: [LoanPeriodUpdateInput]
} }
""" """
@ -426,17 +533,25 @@ type LoanPeriod {
from: Date! from: Date!
to: Date to: Date
note: String note: String
lendingstation: LendingStation! lendingStation: LendingStation!
cargobike: CargoBike! cargoBike: CargoBike!
}
input LoanPeriodCreateInput {
from: Date
to: Date
note: String
lendingStationID: LendingStationCreateInput
cargoBikeID: CargoBikeCreateInput
} }
input LoanPeriodInput { input LoanPeriodUpdateInput {
id: ID id: ID!
from: Date from: Date
to: Date to: Date
note: String note: String
lendingstationID: Int! lendingStation: LendingStationUpdateInput
cargobikeID: Int! cargoBike: CargoBikeUpdateInput
} }
type Address { type Address {
@ -445,17 +560,24 @@ type Address {
zip: String! zip: String!
} }
input AddressInput { input AddressCreateInput {
street: String! street: String!
number: String! number: String!
zip: String! zip: String!
} }
input AddressUpdateInput {
street: String
number: String
zip: String
}
type Query { type Query {
cargobikeById(id:ID!): CargoBike cargoBikeById(id:ID!): CargoBike
"!!!!" "returns all cargoBikes"
cargobikes: [CargoBike]! cargoBikes: [CargoBike]!
cargobikesByProvider(providerId:ID!): [CargoBike]! "not important, you can just use providerById {cargoBikes}"
cargoBikesByProvider(providerId:ID!): [CargoBike]!
providerById(id:ID!): Provider providerById(id:ID!): Provider
providers: [Provider]! providers: [Provider]!
participantById(id:ID!): Participant participantById(id:ID!): Participant
@ -468,10 +590,14 @@ type Query {
type Mutation { type Mutation {
"for testing" "for testing"
addBike(id: ID!, name: String): CargoBike! addBike(id: ID!, name: String): CargoBike!
"if id: null, then new bike will be created, else old bike will be updated" "creates new cargoBike and returns cargobike with new ID"
cargoBike(cargoBike: CargoBikeInput!): CargoBike! createCargoBike(cargoBike: CargoBikeCreateInput!): CargoBike!
"if id: null, then new lending station will be created, else existing one will be updated" "updates cargoBike of given ID with supplied fields and returns updated cargoBike"
lendingStation(lendingStation: LendingStationInput): LendingStation! updateCargoBike(cargoBike: CargoBikeUpdateInput!): CargoBike!
"creates new lendingStation and returns lendingStation with new ID"
createLendingStation(lendingStation: LendingStationCreateInput): LendingStation!
"updates lendingStation of given ID with supplied fields and returns updated lendingStation"
updateLendingStation(lendingstation: LendingStationUpdateInput!): LendingStation!
} }
`; `;

Loading…
Cancel
Save