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
Apollo server written in typescript that handles business logic.
## Assumptions
Userserver and postgres are running e.g. with Julius' Docker Compose.
## 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
```bash
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.
### Compile and run
@ -33,3 +30,14 @@ And start gulp in watch mode
gulp watch
```
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();
}
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}) {
return {
id,
name: 'token'
};
return await this.connection.manager
.createQueryBuilder()
.select('cargoBike')
.from(CargoBike, 'cargoBike')
.where('cargoBike.id = :id', { id })
.getOne() || new GraphQLError('ID not found');
}
async updateBike ({ id, name }:{id:any, name: string }) {
@ -39,47 +49,43 @@ export class CargoBikeAPI extends DataSource {
}
/**
* Creates or Updates CargoBike
* Will create bike, if no id given, else it will update bike with given id.
* @param param0 cargoBike to be updated or created
* Updates CargoBike and return updated cargoBike
* @param param0 cargoBike to be updated
*/
async updateCargoBike ({ cargoBike }:{ cargoBike: any }) {
if (cargoBike.id) {
// update bike with given id
const bike = await this.connection.manager.createQueryBuilder()
.select('cargoBike')
.from(CargoBike, 'cargoBike')
.where('cargoBike.id = :id', { id: cargoBike.id })
.getOne();
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
const bike = await this.connection.manager.createQueryBuilder()
.select('cargoBike')
.from(CargoBike, 'cargoBike')
.where('cargoBike.id = :id', { id: cargoBike.id })
.getOne();
if (bike) {
await this.connection
.createQueryBuilder()
.insert()
.into(CargoBike)
.values([cargoBike])
.returning('*')
.update(CargoBike)
.set({ ...cargoBike })
.where('id = :id', { id: bike.id })
.execute();
const newbike = inserts.generatedMaps[0];
newbike.id = inserts.identifiers[0].id;
return newbike;
return await this.findCargoBikeById({ id: bike.id });
} else {
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 { GraphQLError } from 'graphql';
import { Connection, getConnection } from 'typeorm';
import { LendingStation } from '../../model/LendingStation';
export class LendingStationAPI extends DataSource {
connection : Connection
@ -8,7 +10,64 @@ export class LendingStationAPI extends DataSource {
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 }) {
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 { Engagement } from './model/Engagement';
import { Workshop } from './model/Workshop';
import { LendingStationAPI } from './datasources/db/lendingstationAPI';
import lendingstationResolvers from './resolvers/lendingstationResolvers';
require('dotenv').config();
@ -77,10 +79,14 @@ createConnection({
const userAPI = new UserServerAPI(process.env.RPC_HOST);
const server = new ApolloServer({
resolvers: [bikeresolver],
resolvers: [
bikeresolver,
lendingstationResolvers
],
typeDefs,
dataSources: () => ({
cargoBikeAPI: new CargoBikeAPI(),
lendingStationAPI: new LendingStationAPI(),
userAPI
}),
context: (req: any) => {

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

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

@ -3,12 +3,19 @@ import { GraphQLError } from 'graphql';
export default {
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)) {
return dataSources.cargoBikeAPI.findCargoBikeById({ id });
} else {
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: {
@ -19,7 +26,14 @@ export default {
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)) {
return dataSources.cargoBikeAPI.updateCargoBike({ cargoBike });
} else {

@ -4,11 +4,32 @@ import { LendingStation } from '../model/LendingStation';
export default {
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: {
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)) {
return new GraphQLError('Not implemented');
return dataSources.lendingStationAPI.updateLendingStation({ lendingStation });
} else {
return new GraphQLError('Insufficient Permissions');
}

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