├── .eslintrc ├── .gitignore ├── .graphqlconfig ├── .hound.yml ├── .travis.yml ├── Core └── BaseLoader.js ├── Facility ├── Facility.js ├── FacilityLoader.js └── FacilityService.js ├── Flinkster ├── FlinksterBike.js ├── FlinksterCar.js ├── FlinksterLoader.js └── FlinksterService.js ├── FlinksterParkingArea.js ├── LICENSE ├── Location.js ├── NearbyQuery.js ├── OperationLocation ├── OperationLocation.js ├── OperationLocationLoader.js └── OperationLocationService.js ├── Parkingspace ├── Occupancy.js ├── Parkingspace.js ├── ParkingspaceLoader.js ├── ParkingspaceRelationships.js └── ParkingspaceService.js ├── Platforms ├── DBSuS-Bahnsteigdaten-Stand2017-04.csv ├── Track.js └── TrackService.js ├── README.md ├── Routing ├── Route.js ├── RoutePart.js ├── RouteRelationships.js ├── RoutingService.js └── VehicleProduct.js ├── Station ├── NearbyStationsService.js ├── RegionalArea.js ├── Station.js ├── StationContact.js ├── StationIdMappingService.js ├── StationLoader.js ├── StationRelationships.js ├── StationService.js └── trainstations.csv ├── StationPicture ├── Photographer.js ├── Picture.js ├── StationPictureLoader.js └── StationPictureService.js ├── Timetable ├── TimetableLoader.js ├── TimetableService.js └── TrainOnStation.js ├── TravelCenter ├── TravelCenter.js ├── TravelCenterLoader.js └── TravelCenterService.js ├── app.json ├── express-graphql ├── LICENSE ├── PATENTS ├── README.md ├── dist │ ├── index.js │ ├── index.js.flow │ ├── parseBody.js │ ├── parseBody.js.flow │ ├── renderGraphiQL.js │ └── renderGraphiQL.js.flow └── package.json ├── index.js ├── logo.svg ├── mailAddress.js ├── openingTimes.js ├── package.json ├── schema.js └── test ├── Facility ├── FacilitiesMockResults.json ├── FacilityLoaderMock.js └── FacilityServiceTest.js ├── Flinkster ├── FlinksterLoaderMock.js ├── FlinksterServiceTest.js ├── NearbyBikesAndCarsMockResult.json ├── NearbyBikesMockResult.json └── NearbyCarsMockResult.json ├── Parkingspace ├── AllSpacesMockResult.json ├── OccupancyMockResult.json ├── ParkingspaceLoaderMock.js ├── ParkingspaceServiceTest.js ├── SingleParkingspaceMockResult.json └── WrongSpaceIdMockResult.json ├── Routing └── RouteRelationshipsTest.js ├── Station ├── SingleStationMockResult.json ├── StationLoaderMock.js ├── StationRelationshipsTest.js └── StationServiceTest.js └── StationPicture ├── StationPictureLoaderMock.js ├── StationPictureMockResult.json └── StationPicturesServiceTest.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "ecmaVersion": 6 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "schemaPath": "schema.js" 3 | } -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | jshint: 2 | enabled: false 3 | eslint: 4 | enabled: true 5 | esversion: 6 6 | config_file: .eslintrc 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "7" 5 | cache: 6 | directories: 7 | - "node_modules" -------------------------------------------------------------------------------- /Core/BaseLoader.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | class BaseLoader { 4 | constructor(APIToken, baseURL) { 5 | this.APIToken = APIToken; 6 | this.fetch = fetch; 7 | this.baseURL = baseURL; 8 | } 9 | 10 | get fetchConfiguration() { 11 | const headers = { 12 | Authorization: `Bearer ${this.APIToken}` 13 | }; 14 | const configuration = { 15 | method: 'GET', 16 | headers 17 | }; 18 | 19 | return configuration; 20 | } 21 | 22 | static parseJSON(res, APIName) { 23 | const resStatusCode = res.status; 24 | console.log(`STATUS CODE WAS: ${resStatusCode}`); 25 | return res.json().catch(error => { 26 | let errorMessage; 27 | switch (error.type) { 28 | case 'system': { 29 | errorMessage = `${APIName}: Failed to load data`; 30 | break; 31 | } 32 | case 'invalid-json': { 33 | errorMessage = `${APIName}: Failed to parse JSON`; 34 | break; 35 | } 36 | default: { 37 | errorMessage = `${APIName}: Unknown Error`; 38 | break; 39 | } 40 | } 41 | console.error(errorMessage); 42 | throw new Error(errorMessage, error); 43 | }); 44 | } 45 | 46 | static between(x, min, max) { 47 | return x >= min && x <= max; 48 | } 49 | } 50 | 51 | // Works for Flinkster API 52 | // function apiErrorParser(api, res) { 53 | // console.log(`Error for ${api}`); 54 | // return res 55 | // .json() 56 | // .then(body => { 57 | // switch (api) { 58 | // case 'Betriebsstellen': { 59 | // const error = body.error; 60 | // const errorMessage = `${res.status} at ${api}: ${error.message}`; 61 | // console.error(errorMessage, res.url, error); 62 | 63 | // throw new Error(error); 64 | // break; 65 | // } 66 | 67 | // case 'Flinkster': { 68 | // const allErrors = body.errors; 69 | // if (allErrors) { 70 | // error = allErrors[0]; 71 | // } 72 | 73 | // const errorMessage = `${res.status} at ${api}: ${error.name}: ${error 74 | // .attributes.constraintElementName} ${error.message}, was ${error 75 | // .attributes.constraintElementValue}`; 76 | // console.error(errorMessage, res.url, body, allErrors); 77 | 78 | // throw new Error(errorMessage); 79 | // break; 80 | // } 81 | 82 | // default: { 83 | // const errorMessage = `${res.status} at ${api}: ${body.errMsg}`; 84 | // console.error(errorMessage, res.url, body); 85 | 86 | // throw new Error(errorMessage); 87 | // break; 88 | // } 89 | // } 90 | // }) 91 | // .catch(error => { 92 | // throw new Error(error); 93 | // }); 94 | // } 95 | 96 | module.exports = BaseLoader; 97 | -------------------------------------------------------------------------------- /Facility/Facility.js: -------------------------------------------------------------------------------- 1 | class Facility { 2 | constructor(description, type, state, location, equipmentNumber) { 3 | this.description = description; 4 | this.type = type; 5 | this.state = state; 6 | this.location = location; 7 | this.equipmentNumber = equipmentNumber; 8 | } 9 | } 10 | 11 | module.exports = Facility 12 | -------------------------------------------------------------------------------- /Facility/FacilityLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/fasta/v2'; 4 | 5 | class FacilityLoader extends BaseLoader { 6 | 7 | facilitiesForStationNumber(stationNumber) { 8 | const url = `${this.baseURL}${serviceURL}/stations/${stationNumber}`; 9 | const configuration = this.fetchConfiguration; 10 | return this.fetch(url, configuration) 11 | .then(res => FacilityLoader.parseJSON(res, "FaSta")) 12 | .then(result => result.facilities || []); 13 | } 14 | } 15 | 16 | module.exports = FacilityLoader 17 | -------------------------------------------------------------------------------- /Facility/FacilityService.js: -------------------------------------------------------------------------------- 1 | const Location = require('./../Location.js'); 2 | const Facility = require('./Facility.js') 3 | 4 | 5 | class FacilityService { 6 | 7 | constructor(facilityLoader) { 8 | this.facilityLoader = facilityLoader 9 | } 10 | 11 | /** 12 | * Loads list of facilities from the faSta API 13 | * @param {int} stationNumber - The stationNumber for the station where the facilities are located. 14 | * @return {Promise} promise of a list of facilities 15 | */ 16 | facilitiesForStationNumber(stationNumber) { 17 | return this.facilityLoader.facilitiesForStationNumber(stationNumber) 18 | .then(facilities => { 19 | return facilities.map(facility => { 20 | let location; 21 | if (facility.geocoordY && facility.geocoordX) { 22 | location = new Location(facility.geocoordY, facility.geocoordX); 23 | } 24 | return new Facility(facility.description, facility.type, 25 | facility.state, location, facility.equipmentnumber); 26 | }); 27 | }); 28 | } 29 | } 30 | 31 | module.exports = FacilityService 32 | -------------------------------------------------------------------------------- /Flinkster/FlinksterBike.js: -------------------------------------------------------------------------------- 1 | const access = require('safe-access'); 2 | 3 | const Location = require('../Location'); 4 | const MailAddress = require('../mailAddress'); 5 | const FlinksterParkingArea = require('../FlinksterParkingArea'); 6 | 7 | class Attributes { 8 | constructor(attributes) { 9 | this.licensePlate = attributes.licenseplate; 10 | } 11 | } 12 | 13 | class PriceOption { 14 | constructor(price) { 15 | this.interval = price.interval; 16 | this.type = price.type; 17 | this.grossamount = price.grossamount; 18 | this.currency = price.currency; 19 | this.taxrate = price.taxrate; 20 | this.preferredprice = price.preferredprice; 21 | } 22 | } 23 | 24 | class FlinksterBike { 25 | constructor(bike) { 26 | this.id = bike.rentalObject.uid; 27 | this.url = bike.rentalObject.href; 28 | this.name = bike.rentalObject.name; 29 | this.description = bike.rentalObject.description; 30 | this.rentalModel = bike.rentalObject.rentalModel; 31 | this.providerRentalObjectId = bike.rentalObject.providerRentalObjectId; 32 | this.type = bike.rentalObject.type; 33 | this.attributes = new Attributes(bike.rentalObject.attributes); 34 | this.location = new Location(bike.position.coordinates[1], bike.position.coordinates[0]); 35 | this.address = new MailAddress(bike.area.address.city, bike.area.address.zip, bike.area.address.street); 36 | this.priceOptions = bike.price.items.map(price => new PriceOption(price)); 37 | this.parkingArea = new FlinksterParkingArea(bike.area); 38 | this.bookingUrl = access(bike, '_links[0].href'); 39 | } 40 | } 41 | 42 | module.exports = FlinksterBike; 43 | -------------------------------------------------------------------------------- /Flinkster/FlinksterCar.js: -------------------------------------------------------------------------------- 1 | const access = require('safe-access'); 2 | 3 | const Location = require('../Location'); 4 | const MailAddress = require('../mailAddress'); 5 | const FlinksterParkingArea = require('../FlinksterParkingArea'); 6 | 7 | class Attributes { 8 | constructor(attributes) { 9 | this.seats = attributes.seats; 10 | this.transmissionType = attributes.transmissionType; 11 | this.doors = attributes.doors; 12 | this.color = attributes.colour; 13 | this.fuel = attributes.fuel; 14 | this.licensePlate = attributes.licenseplate; 15 | this.fillLevel = attributes.fillLevel; 16 | } 17 | } 18 | 19 | class PriceOption { 20 | constructor(price) { 21 | this.interval = price.interval; 22 | this.type = price.type; 23 | this.grossamount = price.grossamount; 24 | this.currency = price.currency; 25 | this.taxrate = price.taxrate; 26 | this.preferredprice = price.preferredprice; 27 | } 28 | } 29 | 30 | class CarEquipment { 31 | constructor(equipment) { 32 | if (!equipment) { return; } 33 | 34 | this.cdPlayer = equipment.cdPlayer; 35 | this.emissionsStickers = equipment.emissionsStickers; 36 | this.childSeats = equipment.childSeats; 37 | this.airConditioning = equipment.airConditioning; 38 | this.navigationSystem = equipment.navigationSystem; 39 | this.roofRailing = equipment.roofRailing; 40 | this.particulateFilter = equipment.particulateFilte; 41 | this.audioInline = equipment.audioInlin; 42 | this.tyreType = equipment.tyreType; 43 | this.bluetoothHandsFreeCalling = equipment.bluetoothHandsFreeCalling; 44 | this.cruiseControl = equipment.cruiseControl; 45 | this.passengerAirbagTurnOff = equipment.passengerAirbagTurnOff; 46 | this.isofixSeatFittings = equipment.isofixSeatFittings; 47 | } 48 | } 49 | 50 | class FlinksterCar { 51 | constructor(car) { 52 | this.id = car.rentalObject.uid; 53 | this.name = car.rentalObject.name; 54 | this.description = car.rentalObject.description; 55 | this.rentalModel = car.rentalObject.rentalModel; 56 | this.type = car.rentalObject.type; 57 | this.attributes = new Attributes(car.rentalObject.attributes); 58 | this.equipment = new CarEquipment(car.rentalObject.equipment); 59 | this.location = new Location(car.position.coordinates[1], car.position.coordinates[0]); 60 | this.parkingArea = new FlinksterParkingArea(car.area); 61 | this.priceOptions = car.price.items.map(price => new PriceOption(price)); 62 | this.url = car.rentalObject.href; 63 | this.category = access(car, 'rentalObject.category.href'); 64 | } 65 | } 66 | 67 | module.exports = FlinksterCar; 68 | -------------------------------------------------------------------------------- /Flinkster/FlinksterLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/flinkster-api-ng/v1'; 4 | 5 | class FlinksterLoader extends BaseLoader { 6 | nearbyFlinksters(type, latitude, longitude, radius, count, offset) { 7 | const url = `${this.baseURL}${serviceURL}/bookingproposals?lat=${latitude}&lon=${longitude}&radius=${radius}&offset=${offset}&limit=${count}&providernetwork=${type}&expand=area%2Crentalobject%2Cprice`; 8 | const configuration = this.fetchConfiguration; 9 | 10 | return this.fetch(url, configuration).then(res => FlinksterLoader.parseJSON(res, "Flinkster")); 11 | } 12 | } 13 | 14 | module.exports = FlinksterLoader; 15 | -------------------------------------------------------------------------------- /Flinkster/FlinksterService.js: -------------------------------------------------------------------------------- 1 | // Models 2 | const FlinksterCar = require('./FlinksterCar'); 3 | const FlinksterBike = require('./FlinksterBike'); 4 | 5 | class FlinksterService { 6 | constructor(flinksterLoader) { 7 | this.flinksterLoader = flinksterLoader; 8 | this.relationships; 9 | } 10 | 11 | transformResultIntoFlinksterCar(jsonData) { 12 | if (jsonData) { 13 | const car = new FlinksterCar(jsonData); 14 | // this.relationships.resolve(flinkster); 15 | return car; 16 | } 17 | return null; 18 | } 19 | 20 | transformResultIntoFlinksterBike(jsonData) { 21 | if (jsonData) { 22 | const bike = new FlinksterBike(jsonData); 23 | // this.relationships.resolve(flinkster); 24 | return bike; 25 | } 26 | return null; 27 | } 28 | 29 | nearbyFlinksterCars(latitude, longitude, radius, count, offset) { 30 | const self = this; 31 | return this.flinksterLoader.nearbyFlinksters(1, latitude, longitude, radius, count, offset).then(flinksterCars => { 32 | if (flinksterCars.size > 0) { 33 | return flinksterCars.items.map(car => self.transformResultIntoFlinksterCar(car)); 34 | } 35 | return []; 36 | }); 37 | } 38 | 39 | nearbyFlinksterBikes(latitude, longitude, radius, count, offset) { 40 | const self = this; 41 | return this.flinksterLoader.nearbyFlinksters(2, latitude, longitude, radius, count, offset).then(flinksterBikes => { 42 | if (flinksterBikes.size > 0) { 43 | return flinksterBikes.items.map(bike => self.transformResultIntoFlinksterBike(bike)); 44 | } 45 | return []; 46 | }); 47 | } 48 | } 49 | 50 | module.exports = FlinksterService; 51 | -------------------------------------------------------------------------------- /FlinksterParkingArea.js: -------------------------------------------------------------------------------- 1 | const access = require('safe-access'); 2 | 3 | const Location = require('./Location'); 4 | const MailAddress = require('./mailAddress'); 5 | 6 | class FlinksterParkingArea { 7 | constructor(parkingArea) { 8 | this.id = parkingArea.uid; 9 | this.url = parkingArea.href; 10 | this.name = parkingArea.name; 11 | this.address = new MailAddress(parkingArea.address.city, parkingArea.address.zip, `${parkingArea.address.street} ${parkingArea.address.number}`); 12 | this.address.district = parkingArea.address.district; 13 | this.address.isoCountryCode = parkingArea.address.isoCountryCode; 14 | this.parkingDescription = access(parkingArea, 'attributes.parking'); 15 | this.accessDescription = access(parkingArea, '.attributes.access'); 16 | this.locationDescription = access(parkingArea, '.attributes.locationnote'); 17 | this.publicTransport = access(parkingArea, '.attributes.publictransportation'); 18 | this.provider = new FlinksterAreaProvider(parkingArea.provider, parkingArea.providerAreaId, parkingArea.providerNetworkIds); 19 | this.type = parkingArea.type; 20 | if (parkingArea.geometry.position.type == 'Point') { 21 | this.position = new Location(parkingArea.geometry.position.coordinates[0], parkingArea.geometry.position.coordinates[1]); 22 | } else if (parkingArea.geometry.position.type == 'MultiPolygon') { 23 | this.position = new Location(parkingArea.geometry.centroid.coordinates[0], parkingArea.geometry.centroid.coordinates[1]); 24 | this.GeoJSON = { 25 | type: 'FeatureCollection', 26 | features: [{ 27 | type: 'Feature', 28 | properties: { 29 | name: parkingArea.name, 30 | }, 31 | geometry: { 32 | type: 'MultiPolygon', 33 | coordinates: parkingArea.geometry.position.coordinates, 34 | }, 35 | }], 36 | }; 37 | } 38 | } 39 | } 40 | 41 | class FlinksterAreaProvider { 42 | constructor(provider, areaId, networkIds) { 43 | this.url = provider.href; 44 | this.areaId = areaId; 45 | this.networkIds = networkIds; 46 | } 47 | } 48 | 49 | module.exports = FlinksterParkingArea; 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Dennis Post, Lukas Schmidt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Location.js: -------------------------------------------------------------------------------- 1 | class Location { 2 | 3 | constructor(latitude, longitude) { 4 | this.latitude = latitude; 5 | this.longitude = longitude; 6 | } 7 | } 8 | 9 | module.exports = Location; 10 | -------------------------------------------------------------------------------- /NearbyQuery.js: -------------------------------------------------------------------------------- 1 | class NearbyQuery { 2 | constructor(latitude, longitude, radius, nearbyStationService, parkingspaceService, flinksterService, travelCenterService) { 3 | this.latitude = latitude; 4 | this.longitude = longitude; 5 | this.radius = radius; 6 | //Service Dependencies 7 | this.nearbyStationService = nearbyStationService; 8 | this.parkingspaceService = parkingspaceService; 9 | this.flinksterService = flinksterService; 10 | this.travelCenterService = travelCenterService; 11 | } 12 | 13 | parkingSpaces(args) { 14 | return this.parkingspaceService.nearbyParkingspaces(this.latitude, this.longitude, this.radius, args.count, args.offset); 15 | } 16 | 17 | travelCenters(args) { 18 | return this.travelCenterService.travelCentersAtLocation(this.latitude, this.longitude, this.radius, args.count, args.offset); 19 | } 20 | 21 | flinksterCars(args) { 22 | return this.flinksterService.nearbyFlinksterCars(this.latitude, this.longitude, this.radius, args.count, args.offset); 23 | } 24 | 25 | stations(args) { 26 | return this.nearbyStationService.stationNearby(this.latitude, this.longitude, this.radius, args.count, args.offset); 27 | } 28 | 29 | bikes(args) { 30 | return this.flinksterService.nearbyFlinksterBikes(this.latitude, this.longitude, this.radius, args.count, args.offset); 31 | } 32 | } 33 | 34 | module.exports = NearbyQuery; 35 | -------------------------------------------------------------------------------- /OperationLocation/OperationLocation.js: -------------------------------------------------------------------------------- 1 | class OperationLocation { 2 | constructor(operationLocation) { 3 | this.id = operationLocation.id; 4 | this.abbrev = operationLocation.abbrev; 5 | this.name = operationLocation.name; 6 | this.shortName = operationLocation.short; 7 | this.type = operationLocation.type; 8 | this.status = operationLocation.status; 9 | this.locationCode = operationLocation.locationCode; 10 | this.UIC = operationLocation.UIC; 11 | this.regionId = operationLocation.regionId; 12 | this.validFrom = operationLocation.validFrom; 13 | this.validTill = operationLocation.validTill; 14 | this.timeTableRelevant = operationLocation.timeTableRelevant; 15 | this.borderStation = operationLocation.borderStation; 16 | } 17 | } 18 | 19 | module.exports = OperationLocation; 20 | -------------------------------------------------------------------------------- /OperationLocation/OperationLocationLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/betriebsstellen/v1'; 4 | 5 | class OperationLocationLoader extends BaseLoader { 6 | /** 7 | * Search for bestriebsstellen with a given searchterm. 8 | * @param {string} name - A term you want to search for. 9 | * @return {Promise>} 10 | */ 11 | search(name) { 12 | const url = `${this.baseURL}${serviceURL}/betriebsstellen?name=${name}`; 13 | const configuration = this.fetchConfiguration; 14 | const promies = this.fetch(url, configuration).then(res => 15 | OperationLocationLoader.parseJSON(res, 'Betriebsstellen').catch(error => { 16 | console.error(error); 17 | throw error; 18 | }) 19 | ) 20 | .then(function(result) { 21 | if((!!result) && (result.constructor === Array)) { 22 | return result 23 | } else { 24 | return [] 25 | } 26 | }); 27 | 28 | return promies; 29 | } 30 | } 31 | 32 | module.exports = OperationLocationLoader; 33 | -------------------------------------------------------------------------------- /OperationLocation/OperationLocationService.js: -------------------------------------------------------------------------------- 1 | const OperationLocation = require('./OperationLocation.js'); 2 | 3 | class OperationLocationService { 4 | constructor(operationLocationLoader) { 5 | this.operationLocationLoader = operationLocationLoader; 6 | } 7 | 8 | searchOperationLocations(name) { 9 | return this.operationLocationLoader.search(name) 10 | .then((result => result.map(location => new OperationLocation(location)))) 11 | } 12 | } 13 | 14 | module.exports = OperationLocationService; 15 | -------------------------------------------------------------------------------- /Parkingspace/Occupancy.js: -------------------------------------------------------------------------------- 1 | class Occupancy { 2 | constructor(occupancyData) { 3 | this.validData = occupancyData.validData; 4 | this.timestamp = occupancyData.timestamp; 5 | this.timeSegment = occupancyData.timeSegment; 6 | this.category = occupancyData.category; 7 | this.text = occupancyData.text; 8 | } 9 | } 10 | 11 | module.exports = Occupancy; 12 | -------------------------------------------------------------------------------- /Parkingspace/Parkingspace.js: -------------------------------------------------------------------------------- 1 | const access = require('safe-access'); 2 | 3 | const Location = require('./../Location'); 4 | const MailAddress = require('./../mailAddress'); 5 | 6 | class Parkingspace { 7 | constructor(space) { 8 | this.id = space.id; 9 | this.name = space.title; 10 | this.label = space.label; 11 | this.spaceNumber = space.spaceNumber; 12 | this.responsibility = space.responsibility; 13 | this.source = space.source; 14 | this.nameDisplay = space.nameDisplay; 15 | this.spaceType = space.spaceType; 16 | this.spaceTypeEn = space.spaceTypeEn; 17 | this.spaceTypeName = space.spaceTypeName; 18 | this.location = new Location(access(space, 'geoLocation.latitude'), access(space, 'geoLocation.longitude')); 19 | this.url = space.url; 20 | this.operator = space.operator; 21 | this.operatorUrl = space.operatorUrl; 22 | this.address = new MailAddress(access(space, 'address.cityName'), access(space, 'address.postalCode'), access(space, 'address.street')); 23 | this.distance = space.distance; 24 | this.facilityType = space.facilityType; 25 | this.facilityTypeEn = space.facilityTypeEn; 26 | this.openingHours = space.openingHours; 27 | this.openingHoursEn = space.openingHoursEn; 28 | this.numberParkingPlaces = space.numberParkingPlaces; 29 | this.numberHandicapedPlaces = space.numberHandicapedPlaces; 30 | this.isSpecialProductDb = space.isSpecialProductDb; 31 | this.isOutOfService = space.isOutOfService; 32 | this.type = space.type; 33 | 34 | // space info 35 | this.clearanceWidth = access(space, 'spaceInfo.clearanceWidth'); 36 | this.clearanceHeight = access(space, 'spaceInfo.clearanceHeight'); 37 | this.allowedPropulsions = access(space, 'spaceInfo.allowedPropulsions'); 38 | this.hasChargingStation = access(space, 'spaceInfo.chargingStation'); 39 | 40 | // space flags 41 | this.spaceFlags = space.spaceFlags; 42 | 43 | // tariff info 44 | this.tariffDiscount = access(space, 'tariffInfo.tariffDiscount'); 45 | this.tariffFreeParkingTime = access(space, 'tariffInfo.tariffFreeParkingTime'); 46 | this.tariffPaymentOptions = access(space, 'tariffInfo.tariffPaymentOptions'); 47 | this.tariffPaymentCustomerCards = access(space, 'tariffInfo.tariffPaymentCustomerCards'); 48 | this.tariffDiscountEn = access(space, 'tariffInfo.tariffDiscountEn'); 49 | this.tariffFreeParkingTimeEn = access(space, 'tariffInfo.tariffFreeParkingTimeEn'); 50 | this.tariffPaymentOptionsEn = access(space, 'tariffInfo.tariffPaymentOptionsEn'); 51 | 52 | // tariff flags 53 | this.isDiscountDbBahnCard = access(space, 'tariffFlags.isDiscountDbBahnCard'); 54 | this.isDiscountDbBahnComfort = access(space, 'tariffFlags.isDiscountDbBahnComfort'); 55 | this.isDiscountDbParkAndRail = access(space, 'tariffFlags.isDiscountDbParkAndRail'); 56 | this.isDiscountDbBahnCard = access(space, 'tariffFlags.isMonthParkAndRide'); 57 | this.isMonthSeason = access(space, 'tariffFlags.isMonthSeason'); 58 | this.isMonthVendingMachine = access(space, 'tariffFlags.isMonthVendingMachine'); 59 | this.isMonthParkAndRide = access(space, 'tariffFlags.isMonthParkAndRide'); 60 | 61 | this.tariffPrices = space.tariffPrices; 62 | 63 | this.stationId = access(space, 'station.id'); 64 | this.outOfServiceText = space.outOfServiceText; 65 | this.outOfServiceTextEn = space.outOfServiceTextEn; 66 | this.reservation = space.reservation; 67 | this.outOfService = space.isOutOfService; 68 | this.slogan = space.slogan; 69 | this.sloganEn = space.sloganEn; 70 | } 71 | } 72 | 73 | module.exports = Parkingspace; 74 | -------------------------------------------------------------------------------- /Parkingspace/ParkingspaceLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/bahnpark/v1'; 4 | 5 | class ParkingspaceLoader extends BaseLoader { 6 | 7 | spaceById(spaceId) { 8 | const url = `${this.baseURL}${serviceURL}/spaces/${spaceId}`; 9 | const configuration = this.fetchConfiguration; 10 | 11 | return this.fetch(url, configuration).then(res => ParkingspaceLoader.parseJSON(res, "Bahnpark")) 12 | } 13 | 14 | occupancyForId(spaceId) { 15 | const url = `${this.baseURL}${serviceURL}/spaces/${spaceId}/occupancies`; 16 | const configuration = this.fetchConfiguration; 17 | 18 | return this.fetch(url, configuration).then(res => ParkingspaceLoader.parseJSON(res, "Bahnpark")) 19 | } 20 | 21 | spacesForStationNumber(stationNumber) { 22 | const url = `${this.baseURL}${serviceURL}/spaces?limit=1000`; 23 | const configuration = this.fetchConfiguration; 24 | 25 | return this.fetch(url, configuration).then(res => ParkingspaceLoader.parseJSON(res, "Bahnpark")) 26 | } 27 | 28 | nearbyParkingspaces(latitude, longitude, radius) { 29 | const url = `${this.baseURL}${serviceURL}/spaces?limit=1000`; 30 | const configuration = this.fetchConfiguration; 31 | 32 | return this.fetch(url, configuration).then(res => ParkingspaceLoader.parseJSON(res, "Bahnpark")); 33 | } 34 | } 35 | 36 | module.exports = ParkingspaceLoader; 37 | -------------------------------------------------------------------------------- /Parkingspace/ParkingspaceRelationships.js: -------------------------------------------------------------------------------- 1 | class ParkingspaceRelationships { 2 | constructor(parkingspaceService, stationService) { 3 | this.parkingspaceService = parkingspaceService; 4 | this.stationService = stationService; 5 | } 6 | 7 | resolve(parkingspace) { 8 | const parkingspaceService = this.parkingspaceService; 9 | const stationService = this.stationService; 10 | 11 | parkingspace.station = () => stationService.stationByBahnhofsnummer(parkingspace.stationId); 12 | parkingspace.occupancy = () => parkingspaceService.occupancyForSpaceId(parkingspace.id); 13 | } 14 | } 15 | 16 | module.exports = ParkingspaceRelationships; 17 | -------------------------------------------------------------------------------- /Parkingspace/ParkingspaceService.js: -------------------------------------------------------------------------------- 1 | // Models 2 | const Parkingspace = require('./Parkingspace'); 3 | const Occupancy = require('./Occupancy'); 4 | 5 | class ParkingspaceService { 6 | constructor(parkingspaceLoader) { 7 | this.parkingspaceLoader = parkingspaceLoader; 8 | this.relationships; 9 | } 10 | 11 | transformResultIntoParkingspace(jsonData) { 12 | if (jsonData) { 13 | const parkingspace = new Parkingspace(jsonData, this); 14 | this.relationships.resolve(parkingspace); 15 | return parkingspace; 16 | } 17 | return null; 18 | } 19 | 20 | transformResultIntoOccupancy(jsonData) { 21 | if (jsonData) { 22 | return new Occupancy(jsonData); 23 | } 24 | return null; 25 | } 26 | 27 | parkingspaceBySpaceId(spaceId) { 28 | const self = this; 29 | return this.parkingspaceLoader.spaceById(spaceId).then((parkingspace) => { 30 | if (parkingspace.code == 4100) { 31 | return null; 32 | } 33 | 34 | return self.transformResultIntoParkingspace(parkingspace); 35 | }); 36 | } 37 | 38 | occupancyForSpaceId(spaceId) { 39 | const self = this; 40 | return this.parkingspaceLoader.occupancyForId(spaceId).then((occupancyData) => { 41 | if (occupancyData.code === 5101) { 42 | return null; 43 | } 44 | 45 | return self.transformResultIntoOccupancy(occupancyData.allocation); 46 | }); 47 | } 48 | 49 | parkingspacesForStationNumber(stationNumber) { 50 | const self = this; 51 | return this.parkingspaceLoader.spacesForStationNumber(stationNumber) 52 | .then(parkingspaces => parkingspaces.items || []) 53 | .then(parkingspaces => parkingspaces.filter((parkingspace) => { 54 | if (parkingspace.station.id == stationNumber) { 55 | return self.transformResultIntoParkingspace(parkingspace); 56 | } 57 | })); 58 | } 59 | 60 | nearbyParkingspaces(latitude, longitude, radius, count, offset) { 61 | const self = this; 62 | return this.parkingspaceLoader.nearbyParkingspaces(latitude, longitude, radius).then((parkingspaces) => { 63 | // Filter based on geo and radius 64 | 65 | if (parkingspaces.count > 0) { 66 | // Sort by distance 67 | // geolib.orderByDistance(object latlng, mixed coords) 68 | const mapped = parkingspaces.items.filter((elem) => { 69 | if (elem.geoLocation) { 70 | elem.distance = parseFloat((calculateDistance(latitude, longitude, parseFloat(elem.geoLocation.latitude), parseFloat(elem.geoLocation.longitude)) * 100).toFixed(2), 2); 71 | 72 | if (elem.distance <= radius) { 73 | return elem; 74 | } 75 | } 76 | }); 77 | 78 | // sort by distance 79 | mapped.sort((elem1, elem2) => elem1.distance - elem2.distance); 80 | return mapped.slice(offset, offset + count).map(parkingspace => self.transformResultIntoParkingspace(parkingspace)); 81 | } 82 | return []; 83 | }); 84 | } 85 | } 86 | 87 | 88 | function calculateDistance(lat1, lon1, lat2, lon2) { 89 | const radlat1 = Math.PI * lat1 / 180; 90 | const radlat2 = Math.PI * lat2 / 180; 91 | const radlon1 = Math.PI * lon1 / 180; 92 | const radlon2 = Math.PI * lon2 / 180; 93 | const theta = lon1 - lon2; 94 | const radtheta = Math.PI * theta / 180; 95 | let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); 96 | dist = Math.acos(dist); 97 | dist = dist * 180 / Math.PI; 98 | dist = dist * 60 * 1.1515; 99 | 100 | // Miles to Kilometers 101 | dist *= 1.609344; 102 | 103 | return dist; 104 | } 105 | 106 | module.exports = ParkingspaceService; 107 | -------------------------------------------------------------------------------- /Platforms/Track.js: -------------------------------------------------------------------------------- 1 | class Track { 2 | constructor(payload) { 3 | this.platform = payload.platform; 4 | this.number = payload.trackNumber; 5 | this.name = payload.trackName; 6 | this.length = parseInt(payload.length); 7 | this.height = parseInt(payload.height); 8 | this.stationNumber = payload.stationNumber; 9 | } 10 | } 11 | 12 | module.exports = Track 13 | -------------------------------------------------------------------------------- /Platforms/TrackService.js: -------------------------------------------------------------------------------- 1 | const parse = require('csv-parse'); 2 | const fs = require('fs'); 3 | const Track = require('./Track.js'); 4 | require.extensions['.csv'] = function (module, filename) { 5 | module.exports = fs.readFileSync(filename, 'utf8'); 6 | }; 7 | const tracksFile = require("./DBSuS-Bahnsteigdaten-Stand2017-04.csv"); 8 | 9 | class TrackService { 10 | 11 | constructor(stationMappingService) { 12 | this.stationMappingService = stationMappingService; 13 | } 14 | 15 | get tracks() { 16 | if (!this.tracksPromise) { 17 | this.tracksPromise = new Promise(function(resolve) { 18 | parse(tracksFile, { comment: '#', delimiter: ";", columns: true}, function(err, result) { 19 | let map = {} 20 | result.forEach(function(track) { 21 | let stationNumber = track["stationNumber"] 22 | let tracks = map[stationNumber] || [] 23 | tracks.push(new Track(track)) 24 | map[stationNumber] = tracks 25 | }) 26 | resolve(map) 27 | }); 28 | }) 29 | } 30 | return this.tracksPromise 31 | } 32 | 33 | tracksForStationNumber(stationNumber) { 34 | return this.tracks.then(tracks => tracks[stationNumber] || []); 35 | } 36 | 37 | trackByEvaIdAndTrackNumber(evaId, trackNumber) { 38 | const self = this; 39 | return this.stationMappingService.stationNumberByEvaId(evaId) 40 | .then(stationNumber => self.tracksForStationNumber(stationNumber)) 41 | .then(tracks => tracks.filter(track => track.number == trackNumber)[0]); 42 | } 43 | 44 | } 45 | 46 | module.exports = TrackService 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1BahnQL 2 |

3 | 4 |

5 | 6 | | :bangbang: 1BahnQL is no longer maintained and will be archived. | 7 | |-----------------------------------------| 8 | 9 | Single unified API for all DBOpenData APIs implemented with GraphQL. We implemented the following APIs: StaDa, FaSta, TimeTables, Flinkster, CallABike, ParkplätzeAPI, ReiseCenter 10 | 11 | 12 | 13 | ## GraphiQL Playground 14 | [1BahnQL GraphiQL](https://bahnql.herokuapp.com/graphql) 15 | 16 | ## Usage 17 | ### Install 18 | The installation requires node.js as the execution environment as well as npm as the package manager. Then run `npm install` as a command on your commandline interface. 19 | 20 | ### Run 21 | You need an active authentication token to run your personal installation. You can get one on [developer.deutschebahn.com](https://developer.deutschebahn.com). After creating you account you also have to subscribe to desired services by your own. 22 | 23 | Use your "Zugangstoken" as the DBDeveloperAuthorization Token and run the server: 24 | 25 | `DBDeveloperAuthorization= node index.js` 26 | 27 | Optional parameters: 28 | - DBBaseURL 29 | 30 | ### Heroku Deploy 31 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/dennispost/1BahnQL]) 32 | 33 | 34 | ## Data Sources 35 | Following data sources are currently technically implemented. You need to subscribe to each service at [developer.deutschebahn.com](https://developer.deutschebahn.com) to use them with 1BahnQL. 36 | 37 | ### API based sources: 38 | 39 | - [x] Stationen (StaDa) 40 | - [x] Fahrstühle (FaSta) 41 | - [x] Fahrplan (Fahrplan-Free) 42 | - [x] Flinkster 43 | - [x] Betriebsstellen 44 | - [x] Reisezentren 45 | - [x] Parkplätze 46 | - [x] Bahnhofsfotos 47 | - [ ] https://github.com/derhuerst/db-zugradar-client 48 | - [ ] https://github.com/derhuerst/db-hafas 49 | - [ ] https://github.com/derhuerst/generate-db-shop-urls 50 | - [ ] https://github.com/derhuerst/find-db-station-by-name 51 | - [ ] https://github.com/derhuerst/european-transport-modules 52 | - [ ] https://github.com/derhuerst/vbb-lines 53 | - [ ] https://github.com/derhuerst/db-stations 54 | 55 | ### static sources: 56 | - [x] http://data.deutschebahn.com/dataset/data-bahnsteig 57 | - [ ] http://data.deutschebahn.com/dataset/data-bahnsteig-regio 58 | - [ ] http://data.deutschebahn.com/dataset/data-wagenreihungsplan-soll-daten 59 | - [ ] http://data.deutschebahn.com/dataset/luftschadstoffkataster 60 | 61 | ## Root Queries 62 | 63 | ### Connection Search 64 | 65 | tbi 66 | 67 | ### Stringbased Search 68 | - [x] Station 69 | - [ ] Zug 70 | 71 | ### Geo Search 72 | - [x] Station 73 | - [ ] Bahnsteig 74 | - [x] Flinkster 75 | - [x] Call a Bike 76 | - [x] Parkplätze 77 | - [ ] Zug 78 | - [ ] Fahrstühle / Rolltreppen 79 | 80 | ### ID Access 81 | - [x] EvaId (Station) 82 | - [ ] DS100 (BetrSt) 83 | - [ ] Zug 84 | - [ ] Flinkster 85 | - [ ] Call a Bike 86 | - [ ] Fahrstühle / Rolltreppen 87 | - [x] Parkplätze 88 | - [ ] Bahnsteig 89 | -------------------------------------------------------------------------------- /Routing/Route.js: -------------------------------------------------------------------------------- 1 | const RoutePart = require("./RoutePart.js"); 2 | 3 | class Route { 4 | constructor(data) { 5 | this.parts = data.parts.map(element => new RoutePart(element)) 6 | } 7 | 8 | } 9 | 10 | module.exports = Route 11 | -------------------------------------------------------------------------------- /Routing/RoutePart.js: -------------------------------------------------------------------------------- 1 | const VehicleProduct = require("./VehicleProduct.js"); 2 | 3 | class RoutePart { 4 | constructor(route) { 5 | this.delay = route.delay || 0; 6 | this.direction = route.direction; 7 | this.start = route.start; 8 | this.end = route.end; 9 | if(route.line) { 10 | this.product = new VehicleProduct(route.line); 11 | } 12 | this.fromEvaId = route.origin.id; 13 | this.toEvaId = route.destination.id; 14 | this.arrivingPlatformNumber = route.arrivalPlatform; 15 | this.departingPlatformNumber = route.departurePlatform; 16 | } 17 | } 18 | 19 | module.exports = RoutePart; 20 | -------------------------------------------------------------------------------- /Routing/RouteRelationships.js: -------------------------------------------------------------------------------- 1 | class RouteRelationships { 2 | constructor(stationService, trackService) { 3 | this.stationService = stationService; 4 | this.trackService = trackService; 5 | } 6 | 7 | resolve(route) { 8 | const stationService = this.stationService; 9 | const trackService = this.trackService; 10 | 11 | route.from = () => { 12 | return stationService.stationByEvaId(route.parts[0].fromEvaId); 13 | } 14 | route.to = () => { 15 | return stationService.stationByEvaId(route.parts[route.parts.length - 1].toEvaId); 16 | } 17 | 18 | route.parts = route.parts.map((part) => { 19 | part.from = () => { 20 | return stationService.stationByEvaId(part.fromEvaId); 21 | } 22 | part.to = () => { 23 | return stationService.stationByEvaId(part.toEvaId); 24 | } 25 | part.departingTrack = () => { 26 | return trackService.trackByEvaIdAndTrackNumber(part.fromEvaId, part.departingPlatformNumber); 27 | } 28 | part.arrivingTrack = () => { 29 | return trackService.trackByEvaIdAndTrackNumber(part.toEvaId, part.arrivingPlatformNumber); 30 | } 31 | 32 | return part 33 | }) 34 | 35 | return route 36 | } 37 | } 38 | 39 | module.exports = RouteRelationships 40 | -------------------------------------------------------------------------------- /Routing/RoutingService.js: -------------------------------------------------------------------------------- 1 | const hafas = require('db-hafas') 2 | const Route = require("./Route.js") 3 | 4 | class RoutingService { 5 | 6 | constructor() { 7 | this.relationships; 8 | } 9 | 10 | routes(from, to) { 11 | const self = this 12 | return hafas.journeys(from + "", to + "") 13 | .then(result => result.map(element => self.relationships.resolve(new Route(element)))) 14 | } 15 | } 16 | 17 | module.exports = RoutingService 18 | -------------------------------------------------------------------------------- /Routing/VehicleProduct.js: -------------------------------------------------------------------------------- 1 | class VehicleProduct { 2 | constructor(payload) { 3 | this.name = payload.name; 4 | this.class = payload.class; 5 | this.productCode = payload.productCode; 6 | this.productName = payload.product; 7 | } 8 | } 9 | 10 | module.exports = VehicleProduct; 11 | -------------------------------------------------------------------------------- /Station/NearbyStationsService.js: -------------------------------------------------------------------------------- 1 | const parse = require('csv-parse'); 2 | const fs = require('fs'); 3 | const StationIdMappingService = require('./StationIdMappingService.js'); 4 | 5 | require.extensions['.csv'] = function (module, filename) { 6 | module.exports = fs.readFileSync(filename, 'utf8'); 7 | }; 8 | const trainStations = require('./trainstations.csv'); 9 | 10 | // http://stackoverflow.com/questions/26836146/how-to-sort-array-items-by-longitude-latitude-distance-in-javascripts 11 | function calculateDistance(lat1, lon1, lat2, lon2) { 12 | const radlat1 = Math.PI * lat1 / 180; 13 | const radlat2 = Math.PI * lat2 / 180; 14 | const radlon1 = Math.PI * lon1 / 180; 15 | const radlon2 = Math.PI * lon2 / 180; 16 | const theta = lon1 - lon2; 17 | const radtheta = Math.PI * theta / 180; 18 | let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); 19 | dist = Math.acos(dist); 20 | dist = dist * 180 / Math.PI; 21 | dist = dist * 60 * 1.1515; 22 | 23 | // Miles to Kilometers 24 | dist *= 1.609344; 25 | 26 | return dist; 27 | } 28 | 29 | class NearbyStationService { 30 | constructor(stationService) { 31 | this.stationService = stationService; 32 | this.stationIdMappingService = new StationIdMappingService() 33 | } 34 | 35 | allStationsSortedByDistance(latitude, longitude, radius, count, offset) { 36 | const promise = new Promise(((resolve) => { 37 | parse(trainStations, { comment: '#', delimiter: ';', columns: true }, (err, stations) => { 38 | const result = stations.filter((station) => { 39 | return calculateDistance(latitude * 1, longitude * 1, station.latitude * 1, station.longitude * 1) <= radius / 1000; 40 | }).sort((a, b) => { 41 | const distanceToA = calculateDistance(latitude * 1, longitude * 1, a.latitude * 1, a.longitude * 1); 42 | const distanceToB = calculateDistance(latitude * 1, longitude * 1, b.latitude * 1, b.longitude * 1); 43 | return distanceToA - distanceToB; 44 | }).slice(offset, count); 45 | 46 | resolve(result); 47 | }); 48 | })); 49 | 50 | return promise; 51 | } 52 | 53 | /** 54 | * Return a promise which resolves to a list of stations nearby a given location. 55 | * @param {double} latitude 56 | * @param {double} lonitude 57 | * @param {integer} radius - radius within stations to return 58 | * @param {integer} count - count of the returned stations 59 | * @param {integer} offset - offset of the returned stations 60 | * @return {PromiseS} promise of a list of stations - A promise which resolves to a list of stations. 61 | */ 62 | stationNearby(latitude, longitude, radius, count, offset) { 63 | const self = this; 64 | const promise = this.allStationsSortedByDistance(latitude, longitude, radius, count, offset) 65 | .then((stations) => { 66 | const evaIDs = stations.map(station => station.id); 67 | return self.stationIdMappingService.stationNumbersByEvaIds(evaIDs); 68 | }).then((stationNrs) => { 69 | return stationNrs.map((nr) => self.stationService.stationByBahnhofsnummer(nr)); 70 | }); 71 | 72 | return promise; 73 | } 74 | } 75 | 76 | module.exports = NearbyStationService; 77 | -------------------------------------------------------------------------------- /Station/RegionalArea.js: -------------------------------------------------------------------------------- 1 | class RegionalArea { 2 | constructor(number, name, shortName) { 3 | this.number = number; 4 | this.name = name; 5 | this.shortName = shortName; 6 | } 7 | } 8 | 9 | module.exports = RegionalArea; 10 | -------------------------------------------------------------------------------- /Station/Station.js: -------------------------------------------------------------------------------- 1 | const access = require('safe-access'); 2 | 3 | // Models 4 | const Location = require('../Location.js'); 5 | const MailAddress = require('../mailAddress'); 6 | const OpeningTimes = require('../openingTimes.js'); 7 | const RegionalArea = require('./RegionalArea.js'); 8 | const StationContact = require('./StationContact.js'); 9 | 10 | class Station { 11 | constructor(station) { 12 | this.stationNumber = station.number; 13 | this.name = station.name; 14 | const coordinates = access(station, 'evaNumbers[0].geographicCoordinates.coordinates'); 15 | if (coordinates) { 16 | this.location = new Location(coordinates[1], coordinates[0]); 17 | } 18 | this.category = station.category; 19 | this.hasParking = station.hasParking; 20 | this.hasTaxiRank = station.hasTaxiRank; 21 | this.hasBicycleParking = station.hasBicycleParking; 22 | this.hasLocalPublicTransport = station.hasLocalPublicTransport; 23 | this.hasPublicFacilities = station.hasPublicFacilities; 24 | this.hasLockerSystem = station.hasLockerSystem; 25 | this.hasTravelNecessities = station.hasTravelNecessities; 26 | this.hasSteplessAccess = station.hasSteplessAccess; 27 | this.hasMobilityService = station.hasMobilityService; 28 | this.federalState = station.federalState; 29 | 30 | const area = station.regionalbereich; 31 | if (area) { 32 | this.regionalArea = new RegionalArea(area.number, area.name, area.shortName); 33 | } 34 | 35 | const adress = station.mailingAddress; 36 | this.mailingAddress = new MailAddress(adress.city, adress.zipcode, adress.street); 37 | 38 | const contact = station.aufgabentraeger; 39 | if (contact) { 40 | this.aufgabentraeger = new StationContact(contact.name, contact.shortName, 41 | contact.email, contact.number, contact.phoneNumber); 42 | } 43 | 44 | const timeTableContact = station.timeTableOffice; 45 | if (timeTableContact) { 46 | this.timeTableOffice = new StationContact(timeTableContact.name, timeTableContact.shortName, 47 | timeTableContact.email, timeTableContact.number, timeTableContact.phoneNumber); 48 | } 49 | 50 | const szentraleContact = station.szentrale; 51 | if (szentraleContact) { 52 | this.szentrale = new StationContact(szentraleContact.name, szentraleContact.shortName, 53 | szentraleContact.email, szentraleContact.number, szentraleContact.publicPhoneNumber); 54 | } 55 | 56 | const stationManagementContact = station.stationManagement; 57 | if (stationManagementContact) { 58 | this.stationManagement = new StationContact(stationManagementContact.name, stationManagementContact.shortName, 59 | stationManagementContact.email, stationManagementContact.number, stationManagementContact.phoneNumber); 60 | } 61 | 62 | if (station.DBinformation) { 63 | this.DBInformationOpeningTimes = new OpeningTimes(station.DBinformation.availability); 64 | } 65 | 66 | if (station.localServiceStaff) { 67 | this.localServiceStaffAvailability = new OpeningTimes(station.localServiceStaff.availability); 68 | } 69 | this.primaryEvaId = access(station.evaNumbers.filter(eva => eva.isMain), '[0].number'); 70 | let ril100Identifiers = (station.ril100Identifiers || []).filter(ril => ril.isMain)[0] 71 | this.primaryRil100 = access(ril100Identifiers, 'rilIdentifier'); 72 | this.hasSteamPermission = access(ril100Identifiers, 'hasSteamPermission') || false; 73 | this.priceCategory = station.priceCategory; 74 | this.hasWiFi = station.hasWiFi; 75 | this.hasTravelCenter = station.hasTravelCenter; 76 | this.hasRailwayMission = station.hasRailwayMission; 77 | this.hasDBLounge = station.hasDBLounge; 78 | this.hasLostAndFound = station.hasLostAndFound; 79 | this.hasCarRental = station.hasCarRental; 80 | } 81 | } 82 | 83 | module.exports = Station; 84 | -------------------------------------------------------------------------------- /Station/StationContact.js: -------------------------------------------------------------------------------- 1 | class StationContact { 2 | constructor(name, shortName, email, number, phoneNumber) { 3 | this.name = name; 4 | this.shortName = shortName; 5 | this.email = email; 6 | this.number = number; 7 | this.phoneNumber = phoneNumber; 8 | } 9 | } 10 | 11 | module.exports = StationContact; 12 | -------------------------------------------------------------------------------- /Station/StationIdMappingService.js: -------------------------------------------------------------------------------- 1 | const stations = require('db-stations'); 2 | 3 | class StationIdMappingService { 4 | 5 | get stationMap() { 6 | if (!this.stationMapPromise) { 7 | this.stationMapPromise = new Promise((resolve) => { 8 | let stationMap = {evaId: {}, ds100: {}, stationNumber: {}} 9 | stations.full().on('data', (station) => { 10 | 11 | station.additionalIds.forEach(id => { 12 | stationMap.evaId[id] = {stationNumber: station.nr, ds100: station.ds100} 13 | }) 14 | stationMap.evaId[station.id] = {stationNumber: station.nr, ds100: station.ds100} 15 | stationMap.ds100[station.ds100] = {stationNumber: station.nr, evaId: station.id} 16 | stationMap.stationNumber[station.nr] = {ds100: station.ds100, evaId: station.id} 17 | }).on('end', () => resolve(stationMap)) 18 | }) 19 | } 20 | 21 | return this.stationMapPromise 22 | } 23 | 24 | stationNumberByAttribute(attibute, matchingAttribute) { 25 | return this.stationMap.then(map => { 26 | let station = map[attibute]; 27 | if (!(map[attibute][matchingAttribute] || {}).stationNumber) { 28 | console.log("Missing", attibute, matchingAttribute); 29 | } 30 | return (map[attibute][matchingAttribute] || {}).stationNumber; 31 | }); 32 | } 33 | 34 | stationNumberByEvaId(evaID) { 35 | return this.stationNumberByAttribute('evaId', evaID); 36 | } 37 | 38 | stationNumberFromDS100(ds100) { 39 | return this.stationNumberByAttribute('ds100', ds100); 40 | } 41 | 42 | stationNumbersByEvaIds(evaIDs) { 43 | return new Promise((resolve) => { 44 | const result = []; 45 | stations().on('data', (station) => { 46 | if (evaIDs.find(id => id == station.id)) { 47 | result.push({ nr: station.nr, id: station.id }); 48 | } 49 | }).on('end', () => { 50 | resolve(result.sort((ids1, ids2) => evaIDs.indexOf(ids1.id) > evaIDs.indexOf(ids2.id)).map(ids => ids.nr)); 51 | }); 52 | }); 53 | } 54 | } 55 | 56 | 57 | 58 | module.exports = StationIdMappingService; 59 | -------------------------------------------------------------------------------- /Station/StationLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/stada/v2'; 4 | 5 | class StationLoader extends BaseLoader { 6 | /** 7 | * Loads a singe station JSON from StaDa API. 8 | * @param {int} stationNumber - The stationNumber of the requested station - aka Bahnhofsnummer in StaDa API. 9 | * @return {Promise} promise of a JSON station 10 | */ 11 | stationByBahnhofsnummer(stationNumber) { 12 | if (!stationNumber) { 13 | return Promise.resolve(null); 14 | } 15 | const url = `${this.baseURL}${serviceURL}/stations/${stationNumber}`; 16 | const configuration = this.fetchConfiguration; 17 | const promise = this.fetch(url, configuration) 18 | .then(res => StationLoader.parseJSON(res, 'StaDa')) 19 | .then(result => { 20 | if (result && result.total) { 21 | return result.result[0]; 22 | } 23 | return null; 24 | }); 25 | 26 | return promise; 27 | } 28 | 29 | /** 30 | * Loads a singe station JSON from StaDa API. 31 | * @param {int} evaId - The evaId of the requested station 32 | * @return {Promise} promise of a JSON station 33 | */ 34 | stationByEvaId(evaId) { 35 | const url = `${this.baseURL}${serviceURL}/stations?eva=${evaId}`; 36 | const configuration = this.fetchConfiguration; 37 | const promise = this.fetch(url, configuration) 38 | .then(res => StationLoader.parseJSON(res, 'StaDa')) 39 | .then(result => { 40 | if (result && result.total > 0 && result.result) { 41 | return result.result[0]; 42 | } 43 | return null; 44 | }); 45 | 46 | return promise; 47 | } 48 | 49 | /** 50 | * Loads a singe station JSON from StaDa API. 51 | * @param {String} evaId - The evaId of the requested station 52 | * @return {Promise} promise of a JSON station 53 | */ 54 | stationByRil100(ril100) { 55 | const url = `${this.baseURL}${serviceURL}/stations?ril=${ril100}`; 56 | const configuration = this.fetchConfiguration; 57 | const promise = this.fetch(url, configuration) 58 | .then(res => StationLoader.parseJSON(res, 'StaDa')) 59 | .then(result => { 60 | if (result && result.total > 0 && result.result) { 61 | return result.result[0]; 62 | } 63 | return null; 64 | }); 65 | 66 | return promise; 67 | } 68 | 69 | /** 70 | * Search for stations with a given searchterm. You can use * (arbitrary number of characters) and ? (one single character). 71 | * @param {string} searchterm - A term you want to search for. 72 | * @return {Promise>} 73 | */ 74 | searchStations(searchTerm) { 75 | const url = `${this 76 | .baseURL}${serviceURL}/stations?searchstring=*${searchTerm}*`; 77 | const configuration = this.fetchConfiguration; 78 | let response; 79 | const promies = this.fetch(url, configuration) 80 | .then(res => { 81 | response = res; 82 | return StationLoader.parseJSON(res, 'StaDa'); 83 | }) 84 | .then(result => { 85 | if (StationLoader.between(response.status, 200, 399)) { 86 | return result.result || []; 87 | }else if (response.status == 404) { 88 | return []; 89 | } else { 90 | const errorMessage = `${response.status} at StaDa: ${result.errMsg}`; 91 | console.error(errorMessage, response.url, result); 92 | 93 | throw new Error(errorMessage); 94 | } 95 | }); 96 | 97 | return promies; 98 | } 99 | } 100 | 101 | module.exports = StationLoader; 102 | -------------------------------------------------------------------------------- /Station/StationRelationships.js: -------------------------------------------------------------------------------- 1 | class StationRelationships { 2 | 3 | /** 4 | * A StationRelationships connects different datasources related to a station. 5 | * @constructor 6 | */ 7 | constructor(parkingSpaceService, facilityService, timetableService, trackService, stationPictureService) { 8 | this.parkingSpaceService = parkingSpaceService 9 | this.facilityService = facilityService 10 | this.timetableService = timetableService 11 | this.trackService = trackService; 12 | this.stationPictureService = stationPictureService 13 | } 14 | 15 | resolve(station) { 16 | const parkingSpaceService = this.parkingSpaceService; 17 | const facilityService = this.facilityService; 18 | const timetableService = this.timetableService; 19 | const trackService = this.trackService; 20 | const stationPictureService = this.stationPictureService; 21 | 22 | station.parkingSpaces = function () { 23 | return parkingSpaceService.parkingspacesForStationNumber(station.stationNumber); 24 | }; 25 | 26 | station.facilities = function () { 27 | return facilityService.facilitiesForStationNumber(station.stationNumber); 28 | }; 29 | 30 | station.timetable = function () { 31 | return timetableService.timetableForEvaId(station.primaryEvaId); 32 | }; 33 | 34 | station.tracks = function() { 35 | return trackService.tracksForStationNumber(station.stationNumber); 36 | } 37 | 38 | station.picture = () => { 39 | return stationPictureService.stationPictureForStationNumber(station.stationNumber); 40 | } 41 | 42 | } 43 | } 44 | 45 | module.exports = StationRelationships; 46 | -------------------------------------------------------------------------------- /Station/StationService.js: -------------------------------------------------------------------------------- 1 | // Models 2 | const Station = require('./Station.js'); 3 | 4 | class StationService { 5 | /** 6 | * A Stations service provides capability of loading stations via IDs and text search. 7 | * @constructor 8 | */ 9 | constructor(stationLoader) { 10 | this.stationLoader = stationLoader; 11 | this.relationships; 12 | } 13 | 14 | transformStationResultIntoStation(jsonStation) { 15 | if (jsonStation) { 16 | const station = new Station(jsonStation); 17 | this.relationships.resolve(station); 18 | return station; 19 | } 20 | return null; 21 | } 22 | 23 | /** 24 | * Request a Station with a given evaId. 25 | * @param {int} evaId - The evaId of the requested station. 26 | * @return {Promise} promise of a station - A promise which resolves to the fetched Station or null if the Id is not valid. 27 | */ 28 | stationByEvaId(evaId) { 29 | const self = this; 30 | return this.stationLoader 31 | .stationByEvaId(evaId) 32 | .then(station => self.transformStationResultIntoStation(station)); 33 | } 34 | 35 | /** 36 | * Request a Station with a given evaId. 37 | * @param {int} evaId - The evaId of the requested station. 38 | * @return {Promise} promise of a station - A promise which resolves to the fetched Station or null if the Id is not valid. 39 | */ 40 | stationByRil100(ril100) { 41 | const self = this; 42 | return this.stationLoader 43 | .stationByRil100(ril100) 44 | .then(station => self.transformStationResultIntoStation(station)); 45 | } 46 | 47 | /** 48 | * Request a Station with a given stationNumber(Station&Service calls them bahnhofsnummer in their APIs). 49 | * @param {int} stationNumber - The stationNumber of the requested station. 50 | * @return {Promise} promise of a station - A promise which resolves to the fetched Station or null if the Id is not valid. 51 | */ 52 | stationByBahnhofsnummer(stationNumber) { 53 | const self = this; 54 | return this.stationLoader 55 | .stationByBahnhofsnummer(stationNumber) 56 | .then(station => self.transformStationResultIntoStation(station)); 57 | } 58 | 59 | /** 60 | * Search for stations with a given searchterm. You can use * (arbitrary number of characters) and ? (one single character) 61 | * @param {string} searchterm - A term you want to search for. 62 | * @return {Promise>} promise of a list station - A promise which resolves to the a list of matching stations. 63 | */ 64 | searchStations(searchTerm) { 65 | const self = this; 66 | return this.stationLoader 67 | .searchStations(searchTerm) 68 | .then(stations => 69 | stations.map(station => self.transformStationResultIntoStation(station)) 70 | ); 71 | } 72 | } 73 | 74 | module.exports = StationService; 75 | -------------------------------------------------------------------------------- /StationPicture/Photographer.js: -------------------------------------------------------------------------------- 1 | class Photographer { 2 | constructor(stationPicture) { 3 | this.name = stationPicture.photographer; 4 | this.url = stationPicture.photographerUrl; 5 | } 6 | } 7 | 8 | module.exports = Photographer; 9 | -------------------------------------------------------------------------------- /StationPicture/Picture.js: -------------------------------------------------------------------------------- 1 | const Photographer = require('./Photographer'); 2 | 3 | class Picture { 4 | constructor(stationPicture) { 5 | this.id = stationPicture.id; 6 | this.url = stationPicture.photoUrl; 7 | this.license = stationPicture.license; 8 | this.photographer = new Photographer(stationPicture); 9 | } 10 | } 11 | 12 | module.exports = Picture; 13 | -------------------------------------------------------------------------------- /StationPicture/StationPictureLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/bahnhofsfotos/v1'; 4 | 5 | class StationPictureLoader extends BaseLoader { 6 | 7 | /** 8 | * 9 | * @param {int} stationNumber - The stationNumber of the requested station pictures - aka Bahnhofsnummer in StaDa API. 10 | * @return {Promise} promise of JSON station pictures. 11 | */ 12 | stationPictureForStationNumber(stationNumber) { 13 | if (!stationNumber) { 14 | return Promise.resolve(null); 15 | } 16 | const url = `${this.baseURL}${serviceURL}/de/stations/${stationNumber}`; 17 | const configuration = this.fetchConfiguration; 18 | const promise = this.fetch(url, configuration) 19 | .then(res => StationPictureLoader.parseJSON(res, "Bahnhofsfotos")); 20 | 21 | return promise; 22 | } 23 | 24 | } 25 | 26 | module.exports = StationPictureLoader 27 | -------------------------------------------------------------------------------- /StationPicture/StationPictureService.js: -------------------------------------------------------------------------------- 1 | const Picture = require('./Picture'); 2 | 3 | class StationPictureService { 4 | constructor(stationPictureLoader) { 5 | this.stationPictureLoader = stationPictureLoader; 6 | } 7 | 8 | transformResult(jsonStationPicture) { 9 | if (jsonStationPicture && jsonStationPicture.photoUrl) { 10 | const picture = new Picture(jsonStationPicture); 11 | return picture; 12 | } 13 | return null; 14 | } 15 | 16 | stationPictureForStationNumber(stationNumber) { 17 | const self = this; 18 | return this.stationPictureLoader.stationPictureForStationNumber(stationNumber) 19 | .then(pictureJSON => self.transformResult(pictureJSON)); 20 | } 21 | } 22 | 23 | module.exports = StationPictureService; 24 | -------------------------------------------------------------------------------- /Timetable/TimetableLoader.js: -------------------------------------------------------------------------------- 1 | const convert = require('xml-js'); 2 | const moment = require('moment-timezone'); 3 | const BaseLoader = require('./../Core/BaseLoader'); 4 | 5 | const serviceURL = '/timetables/v1'; 6 | 7 | class TimetableLoader extends BaseLoader { 8 | 9 | timetableForEvaId(evaId) { 10 | const now = moment(); 11 | const nowString = now.format('YYMMDD/HH'); 12 | const url = `${this.baseURL}${serviceURL}/plan/${evaId}/${nowString}`; 13 | const configuration = this.fetchConfiguration; 14 | return this.fetch(url, configuration) 15 | .then(res => res.text()) 16 | .then(result => { 17 | const options = { ignoreComment: true, alwaysChildren: true }; 18 | const json = JSON.parse(convert.xml2json(result, options)); 19 | let timtetable = json.elements[0].elements 20 | if (!timtetable) { 21 | throw new Error("Error loading timetables"); 22 | } 23 | return timtetable; 24 | }).catch(error => {throw new Error("Error loading timetables")}); 25 | } 26 | } 27 | 28 | module.exports = TimetableLoader 29 | -------------------------------------------------------------------------------- /Timetable/TimetableService.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone'); 2 | const TrainOnStation = require('./TrainOnStation.js'); 3 | 4 | const arrivalDepatingTypeKeyMap = { dp: 'nextDepatures', ar: 'nextArrivals' }; 5 | 6 | class TimetableService { 7 | 8 | constructor(timetableLoader) { 9 | this.timetableLoader = timetableLoader 10 | } 11 | 12 | timetableForEvaId(evaId) { 13 | return this.timetableLoader.timetableForEvaId(evaId) 14 | .then(timtetables => { 15 | var result = { nextDepatures: [], nextArrivals: [] }; 16 | timtetables.map((element) => { 17 | const trainType = element.elements[0].attributes.c; 18 | const trainNumber = element.elements[0].attributes.n; 19 | let platform = element.elements[1].attributes.pp; 20 | let time = element.elements[1].attributes.pt; 21 | let stops = element.elements[1].attributes.ppth.split('|'); 22 | let type = element.elements[1].name; 23 | let train = new TrainOnStation(trainType, trainNumber, time, platform, stops); 24 | result[arrivalDepatingTypeKeyMap[type]].push(train); 25 | if (element.elements.length > 2) { 26 | platform = element.elements[2].attributes.pp; 27 | time = element.elements[2].attributes.pt; 28 | type = element.elements[2].name; 29 | stops = element.elements[2].attributes.ppth.split('|'); 30 | train = new TrainOnStation(trainType, trainNumber, time, platform, stops); 31 | result[arrivalDepatingTypeKeyMap[type]].push(train); 32 | } 33 | }); 34 | result.nextDepatures = result.nextDepatures.sort((lhs, rhs) => lhs.time - rhs.time); 35 | result.nextArrivals = result.nextArrivals.sort((lhs, rhs) => lhs.time - rhs.time); 36 | 37 | return result 38 | }); 39 | } 40 | } 41 | 42 | module.exports = TimetableService 43 | -------------------------------------------------------------------------------- /Timetable/TrainOnStation.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone'); 2 | 3 | class TrainOnStation { 4 | constructor(trainType, trainNumber, time, platform, stops) { 5 | this.type = trainType; 6 | this.trainNumber = trainNumber; 7 | this.time = new moment.tz(time, 'YYMMDDHHmm', 'Europe/Berlin').utc().toDate(); 8 | this.platform = platform, 9 | this.stops = stops; 10 | } 11 | } 12 | 13 | module.exports = TrainOnStation; 14 | -------------------------------------------------------------------------------- /TravelCenter/TravelCenter.js: -------------------------------------------------------------------------------- 1 | const MailAddress = require('./../mailAddress'); 2 | const Location = require('./../Location'); 3 | 4 | class TravelCenter { 5 | constructor(travelCenterPayload) { 6 | this.id = travelCenterPayload.id; 7 | this.name = travelCenterPayload.name; 8 | this.address = new MailAddress(travelCenterPayload.city, travelCenterPayload.postCode, travelCenterPayload.address); 9 | this.type = travelCenterPayload.type; 10 | this.location = new Location(travelCenterPayload.lat, travelCenterPayload.lon); 11 | } 12 | } 13 | 14 | module.exports = TravelCenter; 15 | -------------------------------------------------------------------------------- /TravelCenter/TravelCenterLoader.js: -------------------------------------------------------------------------------- 1 | const BaseLoader = require('./../Core/BaseLoader'); 2 | 3 | const serviceURL = '/reisezentren/v1' 4 | 5 | class TravelCenterLoader extends BaseLoader { 6 | 7 | travelCenterAtLocation(latitude, longitude) { 8 | const url = `${this.baseURL}${serviceURL}/reisezentren/loc/${latitude}/${longitude}`; 9 | 10 | return this.fetch(url, this.fetchConfiguration) 11 | .then(res => TravelCenterLoader.parseJSON(res, "Reisezentren")) 12 | } 13 | } 14 | 15 | module.exports = TravelCenterLoader 16 | -------------------------------------------------------------------------------- /TravelCenter/TravelCenterService.js: -------------------------------------------------------------------------------- 1 | const TravelCenter = require('./TravelCenter'); 2 | class TravelCenterService { 3 | constructor(travelCenterLoader) { 4 | this.travelCenterLoader = travelCenterLoader 5 | } 6 | 7 | transformResultIntoTravleCenter(result) { 8 | return new TravelCenter(result); 9 | } 10 | 11 | travelCentersAtLocation(latitude, longitude, radius, count, offset) { 12 | const self = this; 13 | return this.travelCenterLoader.travelCenterAtLocation(latitude, longitude) 14 | .then(result => [self.transformResultIntoTravleCenter(result)]) 15 | .then(travelCenters => travelCenters.filter(center => calculateDistance(latitude, longitude, center.location.latitude, center.location.longitude) <= radius)) 16 | .then(travelCenters => travelCenters.slice(offset, count)) 17 | } 18 | 19 | } 20 | 21 | function calculateDistance(lat1, lon1, lat2, lon2) { 22 | const radlat1 = Math.PI * lat1 / 180; 23 | const radlat2 = Math.PI * lat2 / 180; 24 | const radlon1 = Math.PI * lon1 / 180; 25 | const radlon2 = Math.PI * lon2 / 180; 26 | const theta = lon1 - lon2; 27 | const radtheta = Math.PI * theta / 180; 28 | let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); 29 | dist = Math.acos(dist); 30 | dist = dist * 180 / Math.PI; 31 | dist = dist * 60 * 1.1515; 32 | 33 | // Miles to Kilometers 34 | dist *= 1.609344; 35 | 36 | return dist; 37 | } 38 | 39 | module.exports = TravelCenterService 40 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1BahnQL", 3 | "description": "Single unified API for all DBOpenData APIs implemented with GraphQL.", 4 | "repository": "https://github.com/dbsystel/1BahnQL", 5 | "keywords": [ 6 | "node", 7 | "graphQL", 8 | "dbopendata" 9 | ], 10 | "env": { 11 | "DBDeveloperAuthorization": { 12 | "description": "API Key from DB Developer Portal", 13 | "required": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /express-graphql/LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For GraphQL software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /express-graphql/PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the GraphQL software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. (“Facebook”) hereby grants to each recipient of the Software (“you”) a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (subject to the termination provision below) license under any Necessary Claims, to make, have made, use, sell, offer to sell, import, and otherwise transfer the Software. For avoidance of doubt, no license is granted under Facebook’s rights in any patent claims that are infringed by (i) modifications to the Software made by you or any third party or (ii) the Software in combination with any software or other technology. 6 | 7 | The license granted hereunder will terminate, automatically and without notice, if you (or any of your subsidiaries, corporate affiliates or agents) initiate directly or indirectly, or take a direct financial interest in, any Patent Assertion: (i) against Facebook or any of its subsidiaries or corporate affiliates, (ii) against any party if such Patent Assertion arises in whole or in part from any software, technology, product or service of Facebook or any of its subsidiaries or corporate affiliates, or (iii) against any party relating to the Software. Notwithstanding the foregoing, if Facebook or any of its subsidiaries or corporate affiliates files a lawsuit alleging patent infringement against you in the first instance, and you respond by filing a patent infringement counterclaim in that lawsuit against that party that is unrelated to the Software, the license granted hereunder will not terminate under section (i) of this paragraph due to such counterclaim. 8 | 9 | A “Necessary Claim” is a claim of a patent owned by Facebook that is necessarily infringed by the Software standing alone. 10 | 11 | A “Patent Assertion” is any lawsuit or other action alleging direct, indirect, or contributory infringement or inducement to infringe any patent, including a cross-claim or counterclaim. 12 | -------------------------------------------------------------------------------- /express-graphql/README.md: -------------------------------------------------------------------------------- 1 | GraphQL HTTP Server Middleware 2 | ============================== 3 | 4 | [![Build Status](https://travis-ci.org/graphql/express-graphql.svg?branch=master)](https://travis-ci.org/graphql/express-graphql) 5 | [![Coverage Status](https://coveralls.io/repos/graphql/express-graphql/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql/express-graphql?branch=master) 6 | 7 | Create a GraphQL HTTP server with any HTTP web framework that supports connect styled middleware, including [Connect](https://github.com/senchalabs/connect) itself, [Express](http://expressjs.com) and [Restify](http://restify.com/). 8 | 9 | ## Installation 10 | 11 | ```sh 12 | npm install --save express-graphql 13 | ``` 14 | 15 | 16 | ## Simple Setup 17 | 18 | Just mount `express-graphql` as a route handler: 19 | 20 | ```js 21 | const express = require('express'); 22 | const graphqlHTTP = require('express-graphql'); 23 | 24 | const app = express(); 25 | 26 | app.use('/graphql', graphqlHTTP({ 27 | schema: MyGraphQLSchema, 28 | graphiql: true 29 | })); 30 | 31 | app.listen(4000); 32 | ``` 33 | 34 | 35 | ## Setup with Restify 36 | 37 | Use `.get` or `.post` (or both) rather than `.use` to configure your route handler. If you want to show GraphiQL in the browser, set `graphiql: true` on your `.get` handler. 38 | 39 | ```js 40 | const restify = require('restify'); 41 | const graphqlHTTP = require('express-graphql'); 42 | 43 | const app = restify.createServer(); 44 | 45 | app.post('/graphql', graphqlHTTP({ 46 | schema: MyGraphQLSchema, 47 | graphiql: false 48 | })); 49 | 50 | app.get('/graphql', graphqlHTTP({ 51 | schema: MyGraphQLSchema, 52 | graphiql: true 53 | })); 54 | 55 | app.listen(4000); 56 | ``` 57 | 58 | 59 | ## Options 60 | 61 | The `graphqlHTTP` function accepts the following options: 62 | 63 | * **`schema`**: A `GraphQLSchema` instance from [`GraphQL.js`][]. 64 | A `schema` *must* be provided. 65 | 66 | * **`graphiql`**: If `true`, presents [GraphiQL][] when the GraphQL endpoint is 67 | loaded in a browser. We recommend that you set 68 | `graphiql` to `true` when your app is in development, because it's 69 | quite useful. You may or may not want it in production. 70 | 71 | * **`rootValue`**: A value to pass as the `rootValue` to the `graphql()` 72 | function from [`GraphQL.js`][]. 73 | 74 | * **`context`**: A value to pass as the `context` to the `graphql()` 75 | function from [`GraphQL.js`][]. If `context` is not provided, the 76 | `request` object is passed as the context. 77 | 78 | * **`pretty`**: If `true`, any JSON response will be pretty-printed. 79 | 80 | * **`formatError`**: An optional function which will be used to format any 81 | errors produced by fulfilling a GraphQL operation. If no function is 82 | provided, GraphQL's default spec-compliant [`formatError`][] function will be used. 83 | 84 | * **`extensions`**: An optional function for adding additional metadata to the 85 | GraphQL response as a key-value object. The result will be added to 86 | `"extensions"` field in the resulting JSON. This is often a useful place to 87 | add development time metadata such as the runtime of a query or the amount 88 | of resources consumed. This may be an async function. The function is 89 | give one object as an argument: `{ document, variables, operationName, result }`. 90 | 91 | * **`validationRules`**: Optional additional validation rules queries must 92 | satisfy in addition to those defined by the GraphQL spec. 93 | 94 | In addition to an object defining each option, options can also be provided as 95 | a function (or async function) which returns this options object. This function 96 | is provided the arguments `(request, response, graphQLParams)` and is called 97 | after the request has been parsed. 98 | 99 | The `graphQLParams` is provided as the object `{ query, variables, operationName, raw }`. 100 | 101 | ```js 102 | app.use('/graphql', graphqlHTTP(async (request, response, graphQLParams) => ({ 103 | schema: MyGraphQLSchema, 104 | rootValue: await someFunctionToGetRootValue(request) 105 | graphiql: true 106 | }))); 107 | ``` 108 | 109 | 110 | ## HTTP Usage 111 | 112 | Once installed at a path, `express-graphql` will accept requests with 113 | the parameters: 114 | 115 | * **`query`**: A string GraphQL document to be executed. 116 | 117 | * **`variables`**: The runtime values to use for any GraphQL query variables 118 | as a JSON object. 119 | 120 | * **`operationName`**: If the provided `query` contains multiple named 121 | operations, this specifies which operation should be executed. If not 122 | provided, a 400 error will be returned if the `query` contains multiple 123 | named operations. 124 | 125 | * **`raw`**: If the `graphiql` option is enabled and the `raw` parameter is 126 | provided raw JSON will always be returned instead of GraphiQL even when 127 | loaded from a browser. 128 | 129 | GraphQL will first look for each parameter in the URL's query-string: 130 | 131 | ``` 132 | /graphql?query=query+getUser($id:ID){user(id:$id){name}}&variables={"id":"4"} 133 | ``` 134 | 135 | If not found in the query-string, it will look in the POST request body. 136 | 137 | If a previous middleware has already parsed the POST body, the `request.body` 138 | value will be used. Use [`multer`][] or a similar middleware to add support 139 | for `multipart/form-data` content, which may be useful for GraphQL mutations 140 | involving uploading files. See an [example using multer](https://github.com/graphql/express-graphql/blob/304b24b993c8f16fffff8d23b0fa4088e690874b/src/__tests__/http-test.js#L674-L741). 141 | 142 | If the POST body has not yet been parsed, express-graphql will interpret it 143 | depending on the provided *Content-Type* header. 144 | 145 | * **`application/json`**: the POST body will be parsed as a JSON 146 | object of parameters. 147 | 148 | * **`application/x-www-form-urlencoded`**: this POST body will be 149 | parsed as a url-encoded string of key-value pairs. 150 | 151 | * **`application/graphql`**: The POST body will be parsed as GraphQL 152 | query string, which provides the `query` parameter. 153 | 154 | 155 | ## Combining with Other Express Middleware 156 | 157 | By default, the express request is passed as the GraphQL `context`. 158 | Since most express middleware operates by adding extra data to the 159 | request object, this means you can use most express middleware just by inserting it before `graphqlHTTP` is mounted. This covers scenarios such as authenticating the user, handling file uploads, or mounting GraphQL on a dynamic endpoint. 160 | 161 | This example uses [`express-session`][] to provide GraphQL with the currently logged-in session. 162 | 163 | ```js 164 | const session = require('express-session'); 165 | const graphqlHTTP = require('express-graphql'); 166 | 167 | const app = express(); 168 | 169 | app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})); 170 | 171 | app.use('/graphql', graphqlHTTP({ 172 | schema: MySessionAwareGraphQLSchema, 173 | graphiql: true 174 | })); 175 | ``` 176 | 177 | Then in your type definitions, you can access the request via the third "context" argument in your `resolve` function: 178 | 179 | ```js 180 | new GraphQLObjectType({ 181 | name: 'MyType', 182 | fields: { 183 | myField: { 184 | type: GraphQLString, 185 | resolve(parentValue, args, request) { 186 | // use `request.session` here 187 | } 188 | } 189 | } 190 | }); 191 | ``` 192 | 193 | 194 | ## Providing Extensions 195 | 196 | The GraphQL response allows for adding additional information in a response to 197 | a GraphQL query via a field in the response called `"extensions"`. This is added 198 | by providing an `extensions` function when using `graphqlHTTP`. The function 199 | must return a JSON-serializable Object. 200 | 201 | When called, this is provided an argument which you can use to get information 202 | about the GraphQL request: 203 | 204 | `{ document, variables, operationName, result }` 205 | 206 | This example illustrates adding the amount of time consumed by running the 207 | provided query, which could perhaps be used by your development tools. 208 | 209 | ```js 210 | const graphqlHTTP = require('express-graphql'); 211 | 212 | const app = express(); 213 | 214 | app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})); 215 | 216 | app.use('/graphql', graphqlHTTP(request => { 217 | const startTime = Date.now(); 218 | return { 219 | schema: MyGraphQLSchema, 220 | graphiql: true, 221 | extensions({ document, variables, operationName, result }) { 222 | return { runTime: Date.now() - startTime }; 223 | } 224 | }; 225 | })); 226 | ``` 227 | 228 | When querying this endpoint, it would include this information in the result, 229 | for example: 230 | 231 | ```js 232 | { 233 | "data": { ... } 234 | "extensions": { 235 | "runTime": 135 236 | } 237 | } 238 | ``` 239 | 240 | 241 | ## Other Exports 242 | 243 | **`getGraphQLParams(request: Request): Promise`** 244 | 245 | Given an HTTP Request, this returns a Promise for the parameters relevant to 246 | running a GraphQL request. This function is used internally to handle the 247 | incoming request, you may use it directly for building other similar services. 248 | 249 | ```js 250 | const graphqlHTTP = require('express-graphql'); 251 | 252 | graphqlHTTP.getGraphQLParams(request).then(params => { 253 | // do something... 254 | }) 255 | ``` 256 | 257 | 258 | ## Debugging Tips 259 | 260 | During development, it's useful to get more information from errors, such as 261 | stack traces. Providing a function to `formatError` enables this: 262 | 263 | ```js 264 | formatError: error => ({ 265 | message: error.message, 266 | locations: error.locations, 267 | stack: error.stack, 268 | path: error.path 269 | }) 270 | ``` 271 | 272 | 273 | [`GraphQL.js`]: https://github.com/graphql/graphql-js 274 | [`formatError`]: https://github.com/graphql/graphql-js/blob/master/src/error/formatError.js 275 | [GraphiQL]: https://github.com/graphql/graphiql 276 | [`multer`]: https://github.com/expressjs/multer 277 | [`express-session`]: https://github.com/expressjs/session 278 | -------------------------------------------------------------------------------- /express-graphql/dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 4 | /** 5 | * Copyright (c) 2015, Facebook, Inc. 6 | * All rights reserved. 7 | * 8 | * This source code is licensed under the BSD-style license found in the 9 | * LICENSE file in the root directory of this source tree. An additional grant 10 | * of patent rights can be found in the PATENTS file in the same directory. 11 | */ 12 | 13 | var _accepts = require('accepts'); 14 | 15 | var _accepts2 = _interopRequireDefault(_accepts); 16 | 17 | var _graphql = require('graphql'); 18 | 19 | var _httpErrors = require('http-errors'); 20 | 21 | var _httpErrors2 = _interopRequireDefault(_httpErrors); 22 | 23 | var _url = require('url'); 24 | 25 | var _url2 = _interopRequireDefault(_url); 26 | 27 | var _parseBody = require('./parseBody'); 28 | 29 | var _renderGraphiQL = require('./renderGraphiQL'); 30 | 31 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 32 | 33 | /** 34 | * Middleware for express; takes an options object or function as input to 35 | * configure behavior, and returns an express middleware. 36 | */ 37 | 38 | 39 | /** 40 | * Used to configure the graphqlHTTP middleware by providing a schema 41 | * and other configuration options. 42 | * 43 | * Options can be provided as an Object, a Promise for an Object, or a Function 44 | * that returns an Object or a Promise for an Object. 45 | */ 46 | 47 | 48 | /** 49 | * All information about a GraphQL request. 50 | */ 51 | module.exports = graphqlHTTP; 52 | function graphqlHTTP(options) { 53 | if (!options) { 54 | throw new Error('GraphQL middleware requires options.'); 55 | } 56 | 57 | return function (request, response) { 58 | // Higher scoped variables are referred to at various stages in the 59 | // asynchronous state machine below. 60 | var params = void 0; 61 | var pretty = void 0; 62 | var formatErrorFn = void 0; 63 | var extensionsFn = void 0; 64 | var showGraphiQL = void 0; 65 | var query = void 0; 66 | 67 | var documentAST = void 0; 68 | var variables = void 0; 69 | var operationName = void 0; 70 | 71 | // Promises are used as a mechanism for capturing any thrown errors during 72 | // the asynchronous process below. 73 | 74 | // Parse the Request to get GraphQL request parameters. 75 | return getGraphQLParams(request).then(function (graphQLParams) { 76 | params = graphQLParams; 77 | // Then, resolve the Options to get OptionsData. 78 | return new Promise(function (resolve) { 79 | return resolve(typeof options === 'function' ? options(request, response, params) : options); 80 | }); 81 | }).then(function (optionsData) { 82 | // Assert that optionsData is in fact an Object. 83 | if (!optionsData || (typeof optionsData === 'undefined' ? 'undefined' : _typeof(optionsData)) !== 'object') { 84 | throw new Error('GraphQL middleware option function must return an options object ' + 'or a promise which will be resolved to an options object.'); 85 | } 86 | 87 | // Assert that schema is required. 88 | if (!optionsData.schema) { 89 | throw new Error('GraphQL middleware options must contain a schema.'); 90 | } 91 | 92 | // Collect information from the options data object. 93 | var schema = optionsData.schema; 94 | var context = optionsData.context || request; 95 | var rootValue = optionsData.rootValue; 96 | var graphiql = optionsData.graphiql; 97 | pretty = optionsData.pretty; 98 | formatErrorFn = optionsData.formatError; 99 | extensionsFn = optionsData.extensions; 100 | 101 | var validationRules = _graphql.specifiedRules; 102 | if (optionsData.validationRules) { 103 | validationRules = validationRules.concat(optionsData.validationRules); 104 | } 105 | 106 | // GraphQL HTTP only supports GET and POST methods. 107 | if (request.method !== 'GET' && request.method !== 'POST') { 108 | response.setHeader('Allow', 'GET, POST'); 109 | throw (0, _httpErrors2.default)(405, 'GraphQL only supports GET and POST requests.'); 110 | } 111 | 112 | // Get GraphQL params from the request and POST body data. 113 | query = params.query; 114 | variables = params.variables; 115 | operationName = params.operationName; 116 | showGraphiQL = graphiql && canDisplayGraphiQL(request, params); 117 | 118 | // If there is no query, but GraphiQL will be displayed, do not produce 119 | // a result, otherwise return a 400: Bad Request. 120 | if (!query) { 121 | if (showGraphiQL) { 122 | return null; 123 | } 124 | throw (0, _httpErrors2.default)(400, 'Must provide query string.'); 125 | } 126 | 127 | // GraphQL source. 128 | var source = new _graphql.Source(query, 'GraphQL request'); 129 | 130 | // Parse source to AST, reporting any syntax error. 131 | try { 132 | documentAST = (0, _graphql.parse)(source); 133 | } catch (syntaxError) { 134 | // Return 400: Bad Request if any syntax errors errors exist. 135 | response.statusCode = 400; 136 | return { errors: [syntaxError] }; 137 | } 138 | 139 | // Validate AST, reporting any errors. 140 | var validationErrors = (0, _graphql.validate)(schema, documentAST, validationRules); 141 | if (validationErrors.length > 0) { 142 | // Return 400: Bad Request if any validation errors exist. 143 | response.statusCode = 400; 144 | return { errors: validationErrors }; 145 | } 146 | 147 | // Only query operations are allowed on GET requests. 148 | if (request.method === 'GET') { 149 | // Determine if this GET request will perform a non-query. 150 | var operationAST = (0, _graphql.getOperationAST)(documentAST, operationName); 151 | if (operationAST && operationAST.operation !== 'query') { 152 | // If GraphiQL can be shown, do not perform this query, but 153 | // provide it to GraphiQL so that the requester may perform it 154 | // themselves if desired. 155 | if (showGraphiQL) { 156 | return null; 157 | } 158 | 159 | // Otherwise, report a 405: Method Not Allowed error. 160 | response.setHeader('Allow', 'POST'); 161 | throw (0, _httpErrors2.default)(405, 'Can only perform a ' + operationAST.operation + ' operation ' + 'from a POST request.'); 162 | } 163 | } 164 | // Perform the execution, reporting any errors creating the context. 165 | try { 166 | return (0, _graphql.execute)(schema, documentAST, rootValue, context, variables, operationName); 167 | } catch (contextError) { 168 | // Return 400: Bad Request if any execution context errors exist. 169 | response.statusCode = 400; 170 | return { errors: [contextError] }; 171 | } 172 | }).then(function (result) { 173 | // Collect and apply any metadata extensions if a function was provided. 174 | // http://facebook.github.io/graphql/#sec-Response-Format 175 | if (result && extensionsFn) { 176 | return Promise.resolve(extensionsFn({ 177 | document: documentAST, 178 | variables: variables, 179 | operationName: operationName, 180 | result: result 181 | })).then(function (extensions) { 182 | if (extensions && (typeof extensions === 'undefined' ? 'undefined' : _typeof(extensions)) === 'object') { 183 | result.extensions = extensions; 184 | } 185 | return result; 186 | }); 187 | } 188 | return result; 189 | }).catch(function (error) { 190 | // If an error was caught, report the httpError status, or 500. 191 | response.statusCode = error.status || 500; 192 | return { errors: [error] }; 193 | }).then(function (result) { 194 | // If no data was included in the result, that indicates a runtime query 195 | // error, indicate as such with a generic status code. 196 | // Note: Information about the error itself will still be contained in 197 | // the resulting JSON payload. 198 | // http://facebook.github.io/graphql/#sec-Data 199 | if (result && result.data === null) { 200 | response.statusCode = 500; 201 | } 202 | // Format any encountered errors. 203 | if (result && result.errors) { 204 | result.errors = result.errors.map(formatErrorFn || _graphql.formatError); 205 | } 206 | 207 | // If allowed to show GraphiQL, present it instead of JSON. 208 | if (showGraphiQL) { 209 | var payload = (0, _renderGraphiQL.renderGraphiQL)({ 210 | query: query, 211 | variables: variables, 212 | operationName: operationName, 213 | result: result 214 | }); 215 | return sendResponse(response, 'text/html', payload); 216 | } 217 | 218 | // At this point, result is guaranteed to exist, as the only scenario 219 | // where it will not is when showGraphiQL is true. 220 | if (!result) { 221 | throw (0, _httpErrors2.default)(500, 'Internal Error'); 222 | } 223 | 224 | // If "pretty" JSON isn't requested, and the server provides a 225 | // response.json method (express), use that directly. 226 | // Otherwise use the simplified sendResponse method. 227 | if (!pretty && typeof response.json === 'function') { 228 | response.json(result); 229 | } else { 230 | var _payload = JSON.stringify(result, null, pretty ? 2 : 0); 231 | sendResponse(response, 'application/json', _payload); 232 | } 233 | }); 234 | }; 235 | } 236 | 237 | /** 238 | * Provided a "Request" provided by express or connect (typically a node style 239 | * HTTPClientRequest), Promise the GraphQL request parameters. 240 | */ 241 | module.exports.getGraphQLParams = getGraphQLParams; 242 | function getGraphQLParams(request) { 243 | return (0, _parseBody.parseBody)(request).then(function (bodyData) { 244 | var urlData = request.url && _url2.default.parse(request.url, true).query || {}; 245 | return parseGraphQLParams(urlData, bodyData); 246 | }); 247 | } 248 | 249 | /** 250 | * Helper function to get the GraphQL params from the request. 251 | */ 252 | function parseGraphQLParams(urlData, bodyData) { 253 | // GraphQL Query string. 254 | var query = urlData.query || bodyData.query; 255 | if (typeof query !== 'string') { 256 | query = null; 257 | } 258 | 259 | // Parse the variables if needed. 260 | var variables = urlData.variables || bodyData.variables; 261 | if (variables && typeof variables === 'string') { 262 | try { 263 | variables = JSON.parse(variables); 264 | } catch (error) { 265 | throw (0, _httpErrors2.default)(400, 'Variables are invalid JSON.'); 266 | } 267 | } else if ((typeof variables === 'undefined' ? 'undefined' : _typeof(variables)) !== 'object') { 268 | variables = null; 269 | } 270 | 271 | // Name of GraphQL operation to execute. 272 | var operationName = urlData.operationName || bodyData.operationName; 273 | if (typeof operationName !== 'string') { 274 | operationName = null; 275 | } 276 | 277 | var raw = urlData.raw !== undefined || bodyData.raw !== undefined; 278 | 279 | return { query: query, variables: variables, operationName: operationName, raw: raw }; 280 | } 281 | 282 | /** 283 | * Helper function to determine if GraphiQL can be displayed. 284 | */ 285 | function canDisplayGraphiQL(request, params) { 286 | // If `raw` exists, GraphiQL mode is not enabled. 287 | // Allowed to show GraphiQL if not requested as raw and this request 288 | // prefers HTML over JSON. 289 | return !params.raw && (0, _accepts2.default)(request).types(['json', 'html']) === 'html'; 290 | } 291 | 292 | /** 293 | * Helper function for sending a response using only the core Node server APIs. 294 | */ 295 | function sendResponse(response, type, data) { 296 | var chunk = new Buffer(data, 'utf8'); 297 | response.setHeader('Content-Type', type + '; charset=utf-8'); 298 | response.setHeader('Content-Length', String(chunk.length)); 299 | response.end(chunk); 300 | } -------------------------------------------------------------------------------- /express-graphql/dist/index.js.flow: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /** 3 | * Copyright (c) 2015, Facebook, Inc. 4 | * All rights reserved. 5 | * 6 | * This source code is licensed under the BSD-style license found in the 7 | * LICENSE file in the root directory of this source tree. An additional grant 8 | * of patent rights can be found in the PATENTS file in the same directory. 9 | */ 10 | 11 | import accepts from 'accepts'; 12 | import { 13 | Source, 14 | parse, 15 | validate, 16 | execute, 17 | formatError, 18 | getOperationAST, 19 | specifiedRules, 20 | } from 'graphql'; 21 | import httpError from 'http-errors'; 22 | import url from 'url'; 23 | 24 | import { parseBody } from './parseBody'; 25 | import { renderGraphiQL } from './renderGraphiQL'; 26 | 27 | import type { DocumentNode, GraphQLError, GraphQLSchema } from 'graphql'; 28 | import type { $Request, $Response } from 'express'; 29 | 30 | /** 31 | * Used to configure the graphqlHTTP middleware by providing a schema 32 | * and other configuration options. 33 | * 34 | * Options can be provided as an Object, a Promise for an Object, or a Function 35 | * that returns an Object or a Promise for an Object. 36 | */ 37 | export type Options = 38 | | (( 39 | request: $Request, 40 | response: $Response, 41 | params?: GraphQLParams, 42 | ) => OptionsResult) 43 | | OptionsResult; 44 | export type OptionsResult = OptionsData | Promise; 45 | export type OptionsData = { 46 | /** 47 | * A GraphQL schema from graphql-js. 48 | */ 49 | schema: GraphQLSchema, 50 | 51 | /** 52 | * A value to pass as the context to the graphql() function. 53 | */ 54 | context?: ?mixed, 55 | 56 | /** 57 | * An object to pass as the rootValue to the graphql() function. 58 | */ 59 | rootValue?: ?mixed, 60 | 61 | /** 62 | * A boolean to configure whether the output should be pretty-printed. 63 | */ 64 | pretty?: ?boolean, 65 | 66 | /** 67 | * An optional function which will be used to format any errors produced by 68 | * fulfilling a GraphQL operation. If no function is provided, GraphQL's 69 | * default spec-compliant `formatError` function will be used. 70 | */ 71 | formatError?: ?(error: GraphQLError) => mixed, 72 | 73 | /** 74 | * An optional array of validation rules that will be applied on the document 75 | * in additional to those defined by the GraphQL spec. 76 | */ 77 | validationRules?: ?Array, 78 | 79 | /** 80 | * An optional function for adding additional metadata to the GraphQL response 81 | * as a key-value object. The result will be added to "extensions" field in 82 | * the resulting JSON. This is often a useful place to add development time 83 | * info such as the runtime of a query or the amount of resources consumed. 84 | * 85 | * Information about the request is provided to be used. 86 | * 87 | * This function may be async. 88 | */ 89 | extensions?: ?(info: RequestInfo) => { [key: string]: mixed }, 90 | 91 | /** 92 | * A boolean to optionally enable GraphiQL mode. 93 | */ 94 | graphiql?: ?boolean, 95 | }; 96 | 97 | /** 98 | * All information about a GraphQL request. 99 | */ 100 | export type RequestInfo = { 101 | /** 102 | * The parsed GraphQL document. 103 | */ 104 | document: DocumentNode, 105 | 106 | /** 107 | * The variable values used at runtime. 108 | */ 109 | variables: ?{ [name: string]: mixed }, 110 | 111 | /** 112 | * The (optional) operation name requested. 113 | */ 114 | operationName: ?string, 115 | 116 | /** 117 | * The result of executing the operation. 118 | */ 119 | result: ?mixed, 120 | }; 121 | 122 | type Middleware = (request: $Request, response: $Response) => Promise; 123 | 124 | /** 125 | * Middleware for express; takes an options object or function as input to 126 | * configure behavior, and returns an express middleware. 127 | */ 128 | module.exports = graphqlHTTP; 129 | function graphqlHTTP(options: Options): Middleware { 130 | if (!options) { 131 | throw new Error('GraphQL middleware requires options.'); 132 | } 133 | 134 | return (request: $Request, response: $Response) => { 135 | // Higher scoped variables are referred to at various stages in the 136 | // asynchronous state machine below. 137 | let params; 138 | let pretty; 139 | let formatErrorFn; 140 | let extensionsFn; 141 | let showGraphiQL; 142 | let query; 143 | 144 | let documentAST; 145 | let variables; 146 | let operationName; 147 | 148 | // Promises are used as a mechanism for capturing any thrown errors during 149 | // the asynchronous process below. 150 | 151 | // Parse the Request to get GraphQL request parameters. 152 | return getGraphQLParams(request) 153 | .then(graphQLParams => { 154 | params = graphQLParams; 155 | // Then, resolve the Options to get OptionsData. 156 | return new Promise(resolve => 157 | resolve( 158 | typeof options === 'function' 159 | ? options(request, response, params) 160 | : options, 161 | ), 162 | ); 163 | }) 164 | .then(optionsData => { 165 | // Assert that optionsData is in fact an Object. 166 | if (!optionsData || typeof optionsData !== 'object') { 167 | throw new Error( 168 | 'GraphQL middleware option function must return an options object ' + 169 | 'or a promise which will be resolved to an options object.', 170 | ); 171 | } 172 | 173 | // Assert that schema is required. 174 | if (!optionsData.schema) { 175 | throw new Error('GraphQL middleware options must contain a schema.'); 176 | } 177 | 178 | // Collect information from the options data object. 179 | const schema = optionsData.schema; 180 | const context = optionsData.context || request; 181 | const rootValue = optionsData.rootValue; 182 | const graphiql = optionsData.graphiql; 183 | pretty = optionsData.pretty; 184 | formatErrorFn = optionsData.formatError; 185 | extensionsFn = optionsData.extensions; 186 | 187 | let validationRules = specifiedRules; 188 | if (optionsData.validationRules) { 189 | validationRules = validationRules.concat(optionsData.validationRules); 190 | } 191 | 192 | // GraphQL HTTP only supports GET and POST methods. 193 | if (request.method !== 'GET' && request.method !== 'POST') { 194 | response.setHeader('Allow', 'GET, POST'); 195 | throw httpError(405, 'GraphQL only supports GET and POST requests.'); 196 | } 197 | 198 | // Get GraphQL params from the request and POST body data. 199 | query = params.query; 200 | variables = params.variables; 201 | operationName = params.operationName; 202 | showGraphiQL = graphiql && canDisplayGraphiQL(request, params); 203 | 204 | // If there is no query, but GraphiQL will be displayed, do not produce 205 | // a result, otherwise return a 400: Bad Request. 206 | if (!query) { 207 | if (showGraphiQL) { 208 | return null; 209 | } 210 | throw httpError(400, 'Must provide query string.'); 211 | } 212 | 213 | // GraphQL source. 214 | const source = new Source(query, 'GraphQL request'); 215 | 216 | // Parse source to AST, reporting any syntax error. 217 | try { 218 | documentAST = parse(source); 219 | } catch (syntaxError) { 220 | // Return 400: Bad Request if any syntax errors errors exist. 221 | response.statusCode = 400; 222 | return { errors: [syntaxError] }; 223 | } 224 | 225 | // Validate AST, reporting any errors. 226 | const validationErrors = validate(schema, documentAST, validationRules); 227 | if (validationErrors.length > 0) { 228 | // Return 400: Bad Request if any validation errors exist. 229 | response.statusCode = 400; 230 | return { errors: validationErrors }; 231 | } 232 | 233 | // Only query operations are allowed on GET requests. 234 | if (request.method === 'GET') { 235 | // Determine if this GET request will perform a non-query. 236 | const operationAST = getOperationAST(documentAST, operationName); 237 | if (operationAST && operationAST.operation !== 'query') { 238 | // If GraphiQL can be shown, do not perform this query, but 239 | // provide it to GraphiQL so that the requester may perform it 240 | // themselves if desired. 241 | if (showGraphiQL) { 242 | return null; 243 | } 244 | 245 | // Otherwise, report a 405: Method Not Allowed error. 246 | response.setHeader('Allow', 'POST'); 247 | throw httpError( 248 | 405, 249 | `Can only perform a ${operationAST.operation} operation ` + 250 | 'from a POST request.', 251 | ); 252 | } 253 | } 254 | // Perform the execution, reporting any errors creating the context. 255 | try { 256 | return execute( 257 | schema, 258 | documentAST, 259 | rootValue, 260 | context, 261 | variables, 262 | operationName, 263 | ); 264 | } catch (contextError) { 265 | // Return 400: Bad Request if any execution context errors exist. 266 | response.statusCode = 400; 267 | return { errors: [contextError] }; 268 | } 269 | }) 270 | .then(result => { 271 | // Collect and apply any metadata extensions if a function was provided. 272 | // http://facebook.github.io/graphql/#sec-Response-Format 273 | if (result && extensionsFn) { 274 | return Promise.resolve( 275 | extensionsFn({ 276 | document: documentAST, 277 | variables, 278 | operationName, 279 | result, 280 | }), 281 | ).then(extensions => { 282 | if (extensions && typeof extensions === 'object') { 283 | (result: any).extensions = extensions; 284 | } 285 | return result; 286 | }); 287 | } 288 | return result; 289 | }) 290 | .catch(error => { 291 | // If an error was caught, report the httpError status, or 500. 292 | response.statusCode = error.status || 500; 293 | return { errors: [error] }; 294 | }) 295 | .then(result => { 296 | // If no data was included in the result, that indicates a runtime query 297 | // error, indicate as such with a generic status code. 298 | // Note: Information about the error itself will still be contained in 299 | // the resulting JSON payload. 300 | // http://facebook.github.io/graphql/#sec-Data 301 | if (result && result.data === null) { 302 | response.statusCode = 500; 303 | } 304 | // Format any encountered errors. 305 | if (result && result.errors) { 306 | (result: any).errors = result.errors.map( 307 | formatErrorFn || formatError, 308 | ); 309 | } 310 | 311 | // If allowed to show GraphiQL, present it instead of JSON. 312 | if (showGraphiQL) { 313 | const payload = renderGraphiQL({ 314 | query, 315 | variables, 316 | operationName, 317 | result, 318 | }); 319 | return sendResponse(response, 'text/html', payload); 320 | } 321 | 322 | // At this point, result is guaranteed to exist, as the only scenario 323 | // where it will not is when showGraphiQL is true. 324 | if (!result) { 325 | throw httpError(500, 'Internal Error'); 326 | } 327 | 328 | // If "pretty" JSON isn't requested, and the server provides a 329 | // response.json method (express), use that directly. 330 | // Otherwise use the simplified sendResponse method. 331 | if (!pretty && typeof response.json === 'function') { 332 | response.json(result); 333 | } else { 334 | const payload = JSON.stringify(result, null, pretty ? 2 : 0); 335 | sendResponse(response, 'application/json', payload); 336 | } 337 | }); 338 | }; 339 | } 340 | 341 | export type GraphQLParams = { 342 | query: ?string, 343 | variables: ?{ [name: string]: mixed }, 344 | operationName: ?string, 345 | raw: ?boolean, 346 | }; 347 | 348 | /** 349 | * Provided a "Request" provided by express or connect (typically a node style 350 | * HTTPClientRequest), Promise the GraphQL request parameters. 351 | */ 352 | module.exports.getGraphQLParams = getGraphQLParams; 353 | function getGraphQLParams(request: $Request): Promise { 354 | return parseBody(request).then(bodyData => { 355 | const urlData = (request.url && url.parse(request.url, true).query) || {}; 356 | return parseGraphQLParams(urlData, bodyData); 357 | }); 358 | } 359 | 360 | /** 361 | * Helper function to get the GraphQL params from the request. 362 | */ 363 | function parseGraphQLParams( 364 | urlData: { [param: string]: mixed }, 365 | bodyData: { [param: string]: mixed }, 366 | ): GraphQLParams { 367 | // GraphQL Query string. 368 | let query = urlData.query || bodyData.query; 369 | if (typeof query !== 'string') { 370 | query = null; 371 | } 372 | 373 | // Parse the variables if needed. 374 | let variables = urlData.variables || bodyData.variables; 375 | if (variables && typeof variables === 'string') { 376 | try { 377 | variables = JSON.parse(variables); 378 | } catch (error) { 379 | throw httpError(400, 'Variables are invalid JSON.'); 380 | } 381 | } else if (typeof variables !== 'object') { 382 | variables = null; 383 | } 384 | 385 | // Name of GraphQL operation to execute. 386 | let operationName = urlData.operationName || bodyData.operationName; 387 | if (typeof operationName !== 'string') { 388 | operationName = null; 389 | } 390 | 391 | const raw = urlData.raw !== undefined || bodyData.raw !== undefined; 392 | 393 | return { query, variables, operationName, raw }; 394 | } 395 | 396 | /** 397 | * Helper function to determine if GraphiQL can be displayed. 398 | */ 399 | function canDisplayGraphiQL(request: $Request, params: GraphQLParams): boolean { 400 | // If `raw` exists, GraphiQL mode is not enabled. 401 | // Allowed to show GraphiQL if not requested as raw and this request 402 | // prefers HTML over JSON. 403 | return !params.raw && accepts(request).types(['json', 'html']) === 'html'; 404 | } 405 | 406 | /** 407 | * Helper function for sending a response using only the core Node server APIs. 408 | */ 409 | function sendResponse(response: $Response, type: string, data: string): void { 410 | const chunk = new Buffer(data, 'utf8'); 411 | response.setHeader('Content-Type', type + '; charset=utf-8'); 412 | response.setHeader('Content-Length', String(chunk.length)); 413 | response.end(chunk); 414 | } 415 | -------------------------------------------------------------------------------- /express-graphql/dist/parseBody.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | /** 9 | * Copyright (c) 2015, Facebook, Inc. 10 | * All rights reserved. 11 | * 12 | * This source code is licensed under the BSD-style license found in the 13 | * LICENSE file in the root directory of this source tree. An additional grant 14 | * of patent rights can be found in the PATENTS file in the same directory. 15 | */ 16 | 17 | exports.parseBody = parseBody; 18 | 19 | var _contentType = require('content-type'); 20 | 21 | var _contentType2 = _interopRequireDefault(_contentType); 22 | 23 | var _rawBody = require('raw-body'); 24 | 25 | var _rawBody2 = _interopRequireDefault(_rawBody); 26 | 27 | var _httpErrors = require('http-errors'); 28 | 29 | var _httpErrors2 = _interopRequireDefault(_httpErrors); 30 | 31 | var _querystring = require('querystring'); 32 | 33 | var _querystring2 = _interopRequireDefault(_querystring); 34 | 35 | var _zlib = require('zlib'); 36 | 37 | var _zlib2 = _interopRequireDefault(_zlib); 38 | 39 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 40 | 41 | /** 42 | * Provided a "Request" provided by express or connect (typically a node style 43 | * HTTPClientRequest), Promise the body data contained. 44 | */ 45 | function parseBody(req) { 46 | return new Promise(function (resolve, reject) { 47 | var body = req.body; 48 | 49 | // If express has already parsed a body as a keyed object, use it. 50 | if ((typeof body === 'undefined' ? 'undefined' : _typeof(body)) === 'object' && !(body instanceof Buffer)) { 51 | return resolve(body); 52 | } 53 | 54 | // Skip requests without content types. 55 | if (req.headers['content-type'] === undefined) { 56 | return resolve({}); 57 | } 58 | 59 | var typeInfo = _contentType2.default.parse(req); 60 | 61 | // If express has already parsed a body as a string, and the content-type 62 | // was application/graphql, parse the string body. 63 | if (typeof body === 'string' && typeInfo.type === 'application/graphql') { 64 | return resolve(graphqlParser(body)); 65 | } 66 | 67 | // Already parsed body we didn't recognise? Parse nothing. 68 | if (body) { 69 | return resolve({}); 70 | } 71 | 72 | // Use the correct body parser based on Content-Type header. 73 | switch (typeInfo.type) { 74 | case 'application/graphql': 75 | return read(req, typeInfo, graphqlParser, resolve, reject); 76 | case 'application/json': 77 | return read(req, typeInfo, jsonEncodedParser, resolve, reject); 78 | case 'application/x-www-form-urlencoded': 79 | return read(req, typeInfo, urlEncodedParser, resolve, reject); 80 | } 81 | 82 | // If no Content-Type header matches, parse nothing. 83 | return resolve({}); 84 | }); 85 | } 86 | 87 | function jsonEncodedParser(body) { 88 | if (jsonObjRegex.test(body)) { 89 | /* eslint-disable no-empty */ 90 | try { 91 | return JSON.parse(body); 92 | } catch (error) {} 93 | // Do nothing 94 | 95 | /* eslint-enable no-empty */ 96 | } 97 | throw (0, _httpErrors2.default)(400, 'POST body sent invalid JSON.'); 98 | } 99 | 100 | function urlEncodedParser(body) { 101 | return _querystring2.default.parse(body); 102 | } 103 | 104 | function graphqlParser(body) { 105 | return { query: body }; 106 | } 107 | 108 | /** 109 | * RegExp to match an Object-opening brace "{" as the first non-space 110 | * in a string. Allowed whitespace is defined in RFC 7159: 111 | * 112 | * x20 Space 113 | * x09 Horizontal tab 114 | * x0A Line feed or New line 115 | * x0D Carriage return 116 | */ 117 | var jsonObjRegex = /^[\x20\x09\x0a\x0d]*\{/; 118 | 119 | // Read and parse a request body. 120 | function read(req, typeInfo, parseFn, resolve, reject) { 121 | var charset = (typeInfo.parameters.charset || 'utf-8').toLowerCase(); 122 | 123 | // Assert charset encoding per JSON RFC 7159 sec 8.1 124 | if (charset.slice(0, 4) !== 'utf-') { 125 | throw (0, _httpErrors2.default)(415, 'Unsupported charset "' + charset.toUpperCase() + '".'); 126 | } 127 | 128 | // Get content-encoding (e.g. gzip) 129 | var contentEncoding = req.headers['content-encoding']; 130 | var encoding = typeof contentEncoding === 'string' ? contentEncoding.toLowerCase() : 'identity'; 131 | var length = encoding === 'identity' ? req.headers['content-length'] : null; 132 | var limit = 100 * 1024; // 100kb 133 | var stream = decompressed(req, encoding); 134 | 135 | // Read body from stream. 136 | (0, _rawBody2.default)(stream, { encoding: charset, length: length, limit: limit }, function (err, body) { 137 | if (err) { 138 | return reject(err.type === 'encoding.unsupported' ? (0, _httpErrors2.default)(415, 'Unsupported charset "' + charset.toUpperCase() + '".') : (0, _httpErrors2.default)(400, 'Invalid body: ' + err.message + '.')); 139 | } 140 | 141 | try { 142 | // Decode and parse body. 143 | return resolve(parseFn(body)); 144 | } catch (error) { 145 | return reject(error); 146 | } 147 | }); 148 | } 149 | 150 | // Return a decompressed stream, given an encoding. 151 | function decompressed(req, encoding) { 152 | switch (encoding) { 153 | case 'identity': 154 | return req; 155 | case 'deflate': 156 | return req.pipe(_zlib2.default.createInflate()); 157 | case 'gzip': 158 | return req.pipe(_zlib2.default.createGunzip()); 159 | } 160 | throw (0, _httpErrors2.default)(415, 'Unsupported content-encoding "' + encoding + '".'); 161 | } -------------------------------------------------------------------------------- /express-graphql/dist/parseBody.js.flow: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /** 3 | * Copyright (c) 2015, Facebook, Inc. 4 | * All rights reserved. 5 | * 6 | * This source code is licensed under the BSD-style license found in the 7 | * LICENSE file in the root directory of this source tree. An additional grant 8 | * of patent rights can be found in the PATENTS file in the same directory. 9 | */ 10 | 11 | import contentType from 'content-type'; 12 | import getBody from 'raw-body'; 13 | import httpError from 'http-errors'; 14 | import querystring from 'querystring'; 15 | import zlib from 'zlib'; 16 | 17 | import type { $Request } from 'express'; 18 | 19 | /** 20 | * Provided a "Request" provided by express or connect (typically a node style 21 | * HTTPClientRequest), Promise the body data contained. 22 | */ 23 | export function parseBody(req: $Request): Promise<{ [param: string]: mixed }> { 24 | return new Promise((resolve, reject) => { 25 | const body = req.body; 26 | 27 | // If express has already parsed a body as a keyed object, use it. 28 | if (typeof body === 'object' && !(body instanceof Buffer)) { 29 | return resolve((body: any)); 30 | } 31 | 32 | // Skip requests without content types. 33 | if (req.headers['content-type'] === undefined) { 34 | return resolve({}); 35 | } 36 | 37 | const typeInfo = contentType.parse(req); 38 | 39 | // If express has already parsed a body as a string, and the content-type 40 | // was application/graphql, parse the string body. 41 | if (typeof body === 'string' && typeInfo.type === 'application/graphql') { 42 | return resolve(graphqlParser(body)); 43 | } 44 | 45 | // Already parsed body we didn't recognise? Parse nothing. 46 | if (body) { 47 | return resolve({}); 48 | } 49 | 50 | // Use the correct body parser based on Content-Type header. 51 | switch (typeInfo.type) { 52 | case 'application/graphql': 53 | return read(req, typeInfo, graphqlParser, resolve, reject); 54 | case 'application/json': 55 | return read(req, typeInfo, jsonEncodedParser, resolve, reject); 56 | case 'application/x-www-form-urlencoded': 57 | return read(req, typeInfo, urlEncodedParser, resolve, reject); 58 | } 59 | 60 | // If no Content-Type header matches, parse nothing. 61 | return resolve({}); 62 | }); 63 | } 64 | 65 | function jsonEncodedParser(body) { 66 | if (jsonObjRegex.test(body)) { 67 | /* eslint-disable no-empty */ 68 | try { 69 | return JSON.parse(body); 70 | } catch (error) { 71 | // Do nothing 72 | } 73 | /* eslint-enable no-empty */ 74 | } 75 | throw httpError(400, 'POST body sent invalid JSON.'); 76 | } 77 | 78 | function urlEncodedParser(body) { 79 | return querystring.parse(body); 80 | } 81 | 82 | function graphqlParser(body) { 83 | return { query: body }; 84 | } 85 | 86 | /** 87 | * RegExp to match an Object-opening brace "{" as the first non-space 88 | * in a string. Allowed whitespace is defined in RFC 7159: 89 | * 90 | * x20 Space 91 | * x09 Horizontal tab 92 | * x0A Line feed or New line 93 | * x0D Carriage return 94 | */ 95 | const jsonObjRegex = /^[\x20\x09\x0a\x0d]*\{/; 96 | 97 | // Read and parse a request body. 98 | function read(req, typeInfo, parseFn, resolve, reject) { 99 | const charset = (typeInfo.parameters.charset || 'utf-8').toLowerCase(); 100 | 101 | // Assert charset encoding per JSON RFC 7159 sec 8.1 102 | if (charset.slice(0, 4) !== 'utf-') { 103 | throw httpError(415, `Unsupported charset "${charset.toUpperCase()}".`); 104 | } 105 | 106 | // Get content-encoding (e.g. gzip) 107 | const contentEncoding = req.headers['content-encoding']; 108 | const encoding = typeof contentEncoding === 'string' 109 | ? contentEncoding.toLowerCase() 110 | : 'identity'; 111 | const length = encoding === 'identity' ? req.headers['content-length'] : null; 112 | const limit = 100 * 1024; // 100kb 113 | const stream = decompressed(req, encoding); 114 | 115 | // Read body from stream. 116 | getBody(stream, { encoding: charset, length, limit }, (err, body) => { 117 | if (err) { 118 | return reject( 119 | err.type === 'encoding.unsupported' 120 | ? httpError(415, `Unsupported charset "${charset.toUpperCase()}".`) 121 | : httpError(400, `Invalid body: ${err.message}.`), 122 | ); 123 | } 124 | 125 | try { 126 | // Decode and parse body. 127 | return resolve(parseFn(body)); 128 | } catch (error) { 129 | return reject(error); 130 | } 131 | }); 132 | } 133 | 134 | // Return a decompressed stream, given an encoding. 135 | function decompressed(req, encoding) { 136 | switch (encoding) { 137 | case 'identity': 138 | return req; 139 | case 'deflate': 140 | return req.pipe(zlib.createInflate()); 141 | case 'gzip': 142 | return req.pipe(zlib.createGunzip()); 143 | } 144 | throw httpError(415, `Unsupported content-encoding "${encoding}".`); 145 | } 146 | -------------------------------------------------------------------------------- /express-graphql/dist/renderGraphiQL.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.renderGraphiQL = renderGraphiQL; 7 | 8 | 9 | // Current latest version of GraphiQL. 10 | var GRAPHIQL_VERSION = '0.10.1'; 11 | 12 | // Ensures string values are safe to be used within a \n \n \n \n\n\n \n\n'; 124 | } 125 | -------------------------------------------------------------------------------- /express-graphql/dist/renderGraphiQL.js.flow: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /** 3 | * Copyright (c) 2015, Facebook, Inc. 4 | * All rights reserved. 5 | * 6 | * This source code is licensed under the BSD-style license found in the 7 | * LICENSE file in the root directory of this source tree. An additional grant 8 | * of patent rights can be found in the PATENTS file in the same directory. 9 | */ 10 | 11 | type GraphiQLData = { 12 | query: ?string, 13 | variables: ?{ [name: string]: mixed }, 14 | operationName: ?string, 15 | result?: mixed, 16 | }; 17 | 18 | // Current latest version of GraphiQL. 19 | const GRAPHIQL_VERSION = '0.10.1'; 20 | 21 | // Ensures string values are safe to be used within a 68 | 69 | 70 | 71 | 72 | 73 | 164 | 165 | `; 166 | } 167 | -------------------------------------------------------------------------------- /express-graphql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | { 5 | "raw": "express-graphql@^0.6.6", 6 | "scope": null, 7 | "escapedName": "express-graphql", 8 | "name": "express-graphql", 9 | "rawSpec": "^0.6.6", 10 | "spec": ">=0.6.6 <0.7.0", 11 | "type": "range" 12 | }, 13 | "/Users/lukaslaschmidt/Developer/hackberlin" 14 | ] 15 | ], 16 | "_from": "express-graphql@>=0.6.6 <0.7.0", 17 | "_id": "express-graphql@0.6.6", 18 | "_inCache": true, 19 | "_location": "/express-graphql", 20 | "_nodeVersion": "7.10.0", 21 | "_npmOperationalInternal": { 22 | "host": "s3://npm-registry-packages", 23 | "tmp": "tmp/express-graphql-0.6.6.tgz_1495826697389_0.7526548514142632" 24 | }, 25 | "_npmUser": { 26 | "name": "leebyron", 27 | "email": "lee@leebyron.com" 28 | }, 29 | "_npmVersion": "4.2.0", 30 | "_phantomChildren": {}, 31 | "_requested": { 32 | "raw": "express-graphql@^0.6.6", 33 | "scope": null, 34 | "escapedName": "express-graphql", 35 | "name": "express-graphql", 36 | "rawSpec": "^0.6.6", 37 | "spec": ">=0.6.6 <0.7.0", 38 | "type": "range" 39 | }, 40 | "_requiredBy": [ 41 | "/" 42 | ], 43 | "_resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.6.6.tgz", 44 | "_shasum": "fdf5c7b5af600b8ef920e4095e03dfc784f9094d", 45 | "_shrinkwrap": null, 46 | "_spec": "express-graphql@^0.6.6", 47 | "_where": "/Users/lukaslaschmidt/Developer/hackberlin", 48 | "babel": { 49 | "presets": [ 50 | "es2015" 51 | ], 52 | "plugins": [ 53 | "transform-class-properties", 54 | "transform-flow-strip-types" 55 | ] 56 | }, 57 | "bugs": { 58 | "url": "https://github.com/graphql/express-graphql/issues" 59 | }, 60 | "contributors": [ 61 | { 62 | "name": "Lee Byron", 63 | "email": "lee@leebyron.com", 64 | "url": "http://leebyron.com/" 65 | }, 66 | { 67 | "name": "Daniel Schafer", 68 | "email": "dschafer@fb.com" 69 | }, 70 | { 71 | "name": "Caleb Meredith", 72 | "email": "calebmeredith8@gmail.com" 73 | } 74 | ], 75 | "dependencies": { 76 | "accepts": "^1.3.0", 77 | "content-type": "^1.0.2", 78 | "http-errors": "^1.3.0", 79 | "prettier": "^1.3.1", 80 | "raw-body": "^2.1.0" 81 | }, 82 | "description": "Production ready GraphQL HTTP middleware.", 83 | "devDependencies": { 84 | "babel-cli": "6.24.1", 85 | "babel-eslint": "7.2.3", 86 | "babel-plugin-transform-async-to-generator": "6.24.1", 87 | "babel-plugin-transform-class-properties": "6.24.1", 88 | "babel-plugin-transform-flow-strip-types": "6.22.0", 89 | "babel-plugin-transform-runtime": "6.22.0", 90 | "babel-preset-es2015": "6.24.1", 91 | "babel-register": "6.24.1", 92 | "babel-runtime": "6.23.0", 93 | "body-parser": "1.17.2", 94 | "chai": "3.5.0", 95 | "connect": "3.6.2", 96 | "coveralls": "2.13.1", 97 | "eslint": "3.19.0", 98 | "eslint-plugin-flowtype": "2.33.0", 99 | "express": "4.14.1", 100 | "express3": "*", 101 | "flow-bin": "0.47.0", 102 | "graphql": "0.10.0", 103 | "isparta": "4.0.0", 104 | "mocha": "3.4.2", 105 | "multer": "1.3.0", 106 | "restify": "4.3.0", 107 | "sane": "1.7.0", 108 | "sinon": "2.3.1", 109 | "supertest": "3.0.0" 110 | }, 111 | "directories": { 112 | "lib": "./dist" 113 | }, 114 | "dist": { 115 | "shasum": "fdf5c7b5af600b8ef920e4095e03dfc784f9094d", 116 | "tarball": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.6.6.tgz" 117 | }, 118 | "files": [ 119 | "dist", 120 | "README.md", 121 | "LICENSE", 122 | "PATENTS" 123 | ], 124 | "gitHead": "364db6457bd3de637773a7eb12bb7b850cf54a9e", 125 | "homepage": "https://github.com/graphql/express-graphql#readme", 126 | "keywords": [ 127 | "express", 128 | "restify", 129 | "connect", 130 | "http", 131 | "graphql", 132 | "middleware", 133 | "api" 134 | ], 135 | "license": "BSD-3-Clause", 136 | "main": "dist/index.js", 137 | "maintainers": [ 138 | { 139 | "name": "fb", 140 | "email": "opensource+npm@fb.com" 141 | }, 142 | { 143 | "name": "leebyron", 144 | "email": "lee@leebyron.com" 145 | }, 146 | { 147 | "name": "wincent", 148 | "email": "greg@hurrell.net" 149 | } 150 | ], 151 | "name": "express-graphql", 152 | "optionalDependencies": {}, 153 | "options": { 154 | "mocha": "--require resources/mocha-bootload src/**/__tests__/**/*.js" 155 | }, 156 | "peerDependencies": { 157 | "graphql": "^0.10.0" 158 | }, 159 | "readme": "GraphQL HTTP Server Middleware\n==============================\n\n[![Build Status](https://travis-ci.org/graphql/express-graphql.svg?branch=master)](https://travis-ci.org/graphql/express-graphql)\n[![Coverage Status](https://coveralls.io/repos/graphql/express-graphql/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql/express-graphql?branch=master)\n\nCreate a GraphQL HTTP server with any HTTP web framework that supports connect styled middleware, including [Connect](https://github.com/senchalabs/connect) itself, [Express](http://expressjs.com) and [Restify](http://restify.com/).\n\n## Installation\n\n```sh\nnpm install --save express-graphql\n```\n\n\n## Simple Setup\n\nJust mount `express-graphql` as a route handler:\n\n```js\nconst express = require('express');\nconst graphqlHTTP = require('express-graphql');\n\nconst app = express();\n\napp.use('/graphql', graphqlHTTP({\n schema: MyGraphQLSchema,\n graphiql: true\n}));\n\napp.listen(4000);\n```\n\n\n## Setup with Restify\n\nUse `.get` or `.post` (or both) rather than `.use` to configure your route handler. If you want to show GraphiQL in the browser, set `graphiql: true` on your `.get` handler.\n\n```js\nconst restify = require('restify');\nconst graphqlHTTP = require('express-graphql');\n\nconst app = restify.createServer();\n\napp.post('/graphql', graphqlHTTP({\n schema: MyGraphQLSchema,\n graphiql: false\n}));\n\napp.get('/graphql', graphqlHTTP({\n schema: MyGraphQLSchema,\n graphiql: true\n}));\n\napp.listen(4000);\n```\n\n\n## Options\n\nThe `graphqlHTTP` function accepts the following options:\n\n * **`schema`**: A `GraphQLSchema` instance from [`GraphQL.js`][].\n A `schema` *must* be provided.\n\n * **`graphiql`**: If `true`, presents [GraphiQL][] when the GraphQL endpoint is\n loaded in a browser. We recommend that you set\n `graphiql` to `true` when your app is in development, because it's\n quite useful. You may or may not want it in production.\n\n * **`rootValue`**: A value to pass as the `rootValue` to the `graphql()`\n function from [`GraphQL.js`][].\n\n * **`context`**: A value to pass as the `context` to the `graphql()`\n function from [`GraphQL.js`][]. If `context` is not provided, the\n `request` object is passed as the context.\n\n * **`pretty`**: If `true`, any JSON response will be pretty-printed.\n\n * **`formatError`**: An optional function which will be used to format any\n errors produced by fulfilling a GraphQL operation. If no function is\n provided, GraphQL's default spec-compliant [`formatError`][] function will be used.\n\n * **`extensions`**: An optional function for adding additional metadata to the\n GraphQL response as a key-value object. The result will be added to\n `\"extensions\"` field in the resulting JSON. This is often a useful place to\n add development time metadata such as the runtime of a query or the amount\n of resources consumed. This may be an async function. The function is\n give one object as an argument: `{ document, variables, operationName, result }`.\n\n * **`validationRules`**: Optional additional validation rules queries must\n satisfy in addition to those defined by the GraphQL spec.\n\nIn addition to an object defining each option, options can also be provided as\na function (or async function) which returns this options object. This function\nis provided the arguments `(request, response, graphQLParams)` and is called\nafter the request has been parsed.\n\nThe `graphQLParams` is provided as the object `{ query, variables, operationName, raw }`.\n\n```js\napp.use('/graphql', graphqlHTTP(async (request, response, graphQLParams) => ({\n schema: MyGraphQLSchema,\n rootValue: await someFunctionToGetRootValue(request)\n graphiql: true\n})));\n```\n\n\n## HTTP Usage\n\nOnce installed at a path, `express-graphql` will accept requests with\nthe parameters:\n\n * **`query`**: A string GraphQL document to be executed.\n\n * **`variables`**: The runtime values to use for any GraphQL query variables\n as a JSON object.\n\n * **`operationName`**: If the provided `query` contains multiple named\n operations, this specifies which operation should be executed. If not\n provided, a 400 error will be returned if the `query` contains multiple\n named operations.\n\n * **`raw`**: If the `graphiql` option is enabled and the `raw` parameter is\n provided raw JSON will always be returned instead of GraphiQL even when\n loaded from a browser.\n\nGraphQL will first look for each parameter in the URL's query-string:\n\n```\n/graphql?query=query+getUser($id:ID){user(id:$id){name}}&variables={\"id\":\"4\"}\n```\n\nIf not found in the query-string, it will look in the POST request body.\n\nIf a previous middleware has already parsed the POST body, the `request.body`\nvalue will be used. Use [`multer`][] or a similar middleware to add support\nfor `multipart/form-data` content, which may be useful for GraphQL mutations\ninvolving uploading files. See an [example using multer](https://github.com/graphql/express-graphql/blob/304b24b993c8f16fffff8d23b0fa4088e690874b/src/__tests__/http-test.js#L674-L741).\n\nIf the POST body has not yet been parsed, express-graphql will interpret it\ndepending on the provided *Content-Type* header.\n\n * **`application/json`**: the POST body will be parsed as a JSON\n object of parameters.\n\n * **`application/x-www-form-urlencoded`**: this POST body will be\n parsed as a url-encoded string of key-value pairs.\n\n * **`application/graphql`**: The POST body will be parsed as GraphQL\n query string, which provides the `query` parameter.\n\n\n## Combining with Other Express Middleware\n\nBy default, the express request is passed as the GraphQL `context`.\nSince most express middleware operates by adding extra data to the\nrequest object, this means you can use most express middleware just by inserting it before `graphqlHTTP` is mounted. This covers scenarios such as authenticating the user, handling file uploads, or mounting GraphQL on a dynamic endpoint.\n\nThis example uses [`express-session`][] to provide GraphQL with the currently logged-in session.\n\n```js\nconst session = require('express-session');\nconst graphqlHTTP = require('express-graphql');\n\nconst app = express();\n\napp.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}));\n\napp.use('/graphql', graphqlHTTP({\n schema: MySessionAwareGraphQLSchema,\n graphiql: true\n}));\n```\n\nThen in your type definitions, you can access the request via the third \"context\" argument in your `resolve` function:\n\n```js\nnew GraphQLObjectType({\n name: 'MyType',\n fields: {\n myField: {\n type: GraphQLString,\n resolve(parentValue, args, request) {\n // use `request.session` here\n }\n }\n }\n});\n```\n\n\n## Providing Extensions\n\nThe GraphQL response allows for adding additional information in a response to\na GraphQL query via a field in the response called `\"extensions\"`. This is added\nby providing an `extensions` function when using `graphqlHTTP`. The function\nmust return a JSON-serializable Object.\n\nWhen called, this is provided an argument which you can use to get information\nabout the GraphQL request:\n\n`{ document, variables, operationName, result }`\n\nThis example illustrates adding the amount of time consumed by running the\nprovided query, which could perhaps be used by your development tools.\n\n```js\nconst graphqlHTTP = require('express-graphql');\n\nconst app = express();\n\napp.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}));\n\napp.use('/graphql', graphqlHTTP(request => {\n const startTime = Date.now();\n return {\n schema: MyGraphQLSchema,\n graphiql: true,\n extensions({ document, variables, operationName, result }) {\n return { runTime: Date.now() - startTime };\n }\n };\n}));\n```\n\nWhen querying this endpoint, it would include this information in the result,\nfor example:\n\n```js\n{\n \"data\": { ... }\n \"extensions\": {\n \"runTime\": 135\n }\n}\n```\n\n\n## Other Exports\n\n**`getGraphQLParams(request: Request): Promise`**\n\nGiven an HTTP Request, this returns a Promise for the parameters relevant to\nrunning a GraphQL request. This function is used internally to handle the\nincoming request, you may use it directly for building other similar services.\n\n```js\nconst graphqlHTTP = require('express-graphql');\n\ngraphqlHTTP.getGraphQLParams(request).then(params => {\n // do something...\n})\n```\n\n\n## Debugging Tips\n\nDuring development, it's useful to get more information from errors, such as\nstack traces. Providing a function to `formatError` enables this:\n\n```js\nformatError: error => ({\n message: error.message,\n locations: error.locations,\n stack: error.stack,\n path: error.path\n})\n```\n\n\n[`GraphQL.js`]: https://github.com/graphql/graphql-js\n[`formatError`]: https://github.com/graphql/graphql-js/blob/master/src/error/formatError.js\n[GraphiQL]: https://github.com/graphql/graphiql\n[`multer`]: https://github.com/expressjs/multer\n[`express-session`]: https://github.com/expressjs/session\n", 160 | "readmeFilename": "README.md", 161 | "repository": { 162 | "type": "git", 163 | "url": "git+ssh://git@github.com/graphql/express-graphql.git" 164 | }, 165 | "scripts": { 166 | "build": "rm -rf dist/* && babel src --ignore __tests__ --out-dir dist && npm run build:flow", 167 | "build:flow": "find ./src -name '*.js' -not -path '*/__tests__*' | while read filepath; do cp $filepath `echo $filepath | sed 's/\\/src\\//\\/dist\\//g'`.flow; done", 168 | "check": "flow check", 169 | "cover": "babel-node node_modules/.bin/isparta cover --root src --report html node_modules/.bin/_mocha -- $npm_package_options_mocha", 170 | "cover:lcov": "babel-node node_modules/.bin/isparta cover --root src --report lcovonly node_modules/.bin/_mocha -- $npm_package_options_mocha", 171 | "lint": "eslint src", 172 | "prepublish": ". ./resources/prepublish.sh", 173 | "pretty": "node resources/pretty.js", 174 | "pretty-check": "node resources/pretty.js --check", 175 | "preversion": "npm test", 176 | "test": "npm run lint && npm run pretty-check && npm run check && npm run testonly", 177 | "testonly": "mocha $npm_package_options_mocha", 178 | "watch": "node resources/watch.js" 179 | }, 180 | "version": "0.6.6" 181 | } 182 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { graphql } = require('graphql'); 2 | const schema = require('./schema.js'); 3 | const express = require('express'); 4 | const graphqlHTTP = require('./express-graphql/dist/index'); 5 | const expressPlayground = require('graphql-playground-middleware-express').default 6 | 7 | const ParkingspaceLoader = require('./Parkingspace/ParkingspaceLoader'); 8 | const FlinksterLoader = require('./Flinkster/FlinksterLoader'); 9 | const StationLoader = require('./Station/StationLoader'); 10 | const OperationLocationLoader = require('./OperationLocation/OperationLocationLoader'); 11 | const TravelCenterLoader = require('./TravelCenter/TravelCenterLoader'); 12 | const TimetableLoader = require('./Timetable/TimetableLoader.js'); 13 | const FacilityLoader = require('./Facility/FacilityLoader.js'); 14 | const StationPictureLoader = require('./StationPicture/StationPictureLoader'); 15 | 16 | const ParkingspaceService = require('./Parkingspace/ParkingspaceService'); 17 | const FlinksterService = require('./Flinkster/FlinksterService'); 18 | const OperationLocationService = require('./OperationLocation/OperationLocationService'); 19 | const StationService = require('./Station/StationService'); 20 | const NearbyStationService = require('./Station/NearbyStationsService'); 21 | const TravelCenterService = require('./TravelCenter/TravelCenterService'); 22 | const FacilityService = require('./Facility/FacilityService.js'); 23 | const RoutingService = require('./Routing/RoutingService.js'); 24 | const TimetableService = require('./Timetable/TimetableService.js'); 25 | const TrackService = require('./Platforms/TrackService.js'); 26 | const StationIdMappingService = require('./Station/StationIdMappingService'); 27 | const StationPictureService = require('./StationPicture/StationPictureService'); 28 | 29 | const StationRelationships = require('./Station/StationRelationships'); 30 | const ParkingspaceRelationships = require('./Parkingspace/ParkingspaceRelationships'); 31 | const RouteRelationships = require('./Routing/RouteRelationships'); 32 | 33 | const NearbyQuery = require('./NearbyQuery'); 34 | 35 | // --------- // 36 | 37 | const APIToken = process.env.DBDeveloperAuthorization; 38 | const baseURL = process.env.DBBaseURL || 'https://api.deutschebahn.com'; 39 | 40 | // Loader 41 | const parkingspaceLoader = new ParkingspaceLoader(APIToken, baseURL); 42 | const stationLoader = new StationLoader(APIToken, baseURL); 43 | const timetableLoader = new TimetableLoader(APIToken, baseURL); 44 | const operationLocationLoader = new OperationLocationLoader(APIToken, baseURL); 45 | const travelCenterLoader = new TravelCenterLoader(APIToken, baseURL); 46 | const facilityLoader = new FacilityLoader(APIToken, baseURL); 47 | const flinksterLoader = new FlinksterLoader(APIToken, baseURL); 48 | const stationPictureLoader = new StationPictureLoader(APIToken, baseURL); 49 | 50 | // Services 51 | const parkingspaceService = new ParkingspaceService(parkingspaceLoader); 52 | const operationLocationService = new OperationLocationService(operationLocationLoader); 53 | const stationIdMappingService = new StationIdMappingService(); 54 | const stationService = new StationService(stationLoader, stationIdMappingService); 55 | const nearbyStationService = new NearbyStationService(stationService); 56 | const travelCenterService = new TravelCenterService(travelCenterLoader); 57 | const facilityService = new FacilityService(facilityLoader) 58 | const routingService = new RoutingService(); 59 | const flinksterService = new FlinksterService(flinksterLoader); 60 | const timetableServcie = new TimetableService(timetableLoader); 61 | const trackService = new TrackService(stationIdMappingService); 62 | const stationPictureService = new StationPictureService(stationPictureLoader) 63 | 64 | // Relationships 65 | stationService.relationships = new StationRelationships(parkingspaceService, facilityService, timetableServcie, trackService, stationPictureService); 66 | parkingspaceService.relationships = new ParkingspaceRelationships(parkingspaceService, stationService); 67 | routingService.relationships = new RouteRelationships(stationService, trackService); 68 | 69 | // Queries 70 | const root = { 71 | parkingSpace: args => parkingspaceService.parkingspaceBySpaceId(args.id), 72 | stationWithEvaId: args => stationService.stationByEvaId(args.evaId), 73 | stationWithStationNumber: args => stationService.stationByBahnhofsnummer(args.stationNumber), 74 | stationWithRill100: args => stationService.stationByRil100(args.rill100), 75 | search: args => ({ stations: stationService.searchStations(args.searchTerm), operationLocations: operationLocationService.searchOperationLocations(args.searchTerm) }), 76 | nearby: args => new NearbyQuery(args.latitude, args.longitude, args.radius, nearbyStationService, parkingspaceService, flinksterService, travelCenterService), 77 | }; 78 | 79 | const experimental = process.env.experimental 80 | if(experimental) { 81 | root.routing = (args) => { 82 | const routeSearch = routingService.routes(args.from, args.to); 83 | return routeSearch.then(options => [options[0]]); 84 | } 85 | } 86 | 87 | const introductionDemoQuery = ` 88 | # Welcome to 1BahnQL 89 | # 90 | # GraphiQL is an in-browser IDE for writing, validating, and 91 | # testing GraphQL queries. 92 | # 93 | # Type queries into this side of the screen, and you will 94 | # see intelligent typeaheads aware of the current GraphQL type schema and 95 | # live syntax and validation errors highlighted within the text. 96 | # 97 | # To bring up the auto-complete at any point, just press Ctrl-Space. 98 | # 99 | # Press the run button above, or Cmd-Enter to execute the query, and the result 100 | # will appear in the pane to the right. 101 | # 102 | # Learning resources: 103 | # GraphQL: http://graphql.org 104 | # GraphiQL: https://github.com/graphql/graphiql 105 | # 1BahnQL: https://github.com/dbsystel/1BahnQL 106 | # 107 | # 108 | # Example queries: 109 | # Just comment out the query you would like to test and press the run button above, 110 | # or Cmd-Enter to execute the query 111 | # Requires api subscription: Stationen (StaDa) 112 | 113 | { 114 | stationWithEvaId(evaId: 8000105) { 115 | name 116 | location { 117 | latitude 118 | longitude 119 | } 120 | } 121 | } 122 | ` 123 | 124 | const app = express(); 125 | app.use('/graphql', graphqlHTTP({ 126 | schema, 127 | rootValue: root, 128 | graphiql: true 129 | })); 130 | app.get('/playground', expressPlayground({ endpoint: 'graphql' })) 131 | // set the port of our application 132 | // process.env.PORT lets the port be set by Heroku 133 | const port = process.env.PORT || 8080; 134 | 135 | app.listen(port, () => console.log(`now browse to localhost:${port}/graphql`)); 136 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | Icon 1BahnQL_Icon -------------------------------------------------------------------------------- /mailAddress.js: -------------------------------------------------------------------------------- 1 | class MailAddress { 2 | constructor(city, zipcode, street) { 3 | this.city = city; 4 | this.zipcode = zipcode; 5 | this.street = street; 6 | } 7 | } 8 | 9 | module.exports = MailAddress; 10 | -------------------------------------------------------------------------------- /openingTimes.js: -------------------------------------------------------------------------------- 1 | class OpeningTimes { 2 | constructor(staDatOpeningTimes) { 3 | for (const key in staDatOpeningTimes) { 4 | if (staDatOpeningTimes.hasOwnProperty(key)) { 5 | const value = staDatOpeningTimes[key]; 6 | this[key] = new OpeningTime(value.fromTime, value.toTime); 7 | } 8 | } 9 | } 10 | } 11 | 12 | class OpeningTime { 13 | constructor(from, to) { 14 | this.from = from; 15 | this.to = to; 16 | } 17 | } 18 | 19 | module.exports = OpeningTimes; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1BahnQL", 3 | "version": "0.0.1", 4 | "description": "- [ ] Stationen (StaDa) - [ ] Fahrstühle (FaSta) - [ ] Fahrplan (Fahrplan-Free) - [ ] Flinkster - [ ] Betriebsstellen - [ ] Reisezentren - [ ] Parkplätze - [ ] https://github.com/derhuerst/db-zugradar-client - [ ] https://github.com/derhuerst/db-hafas - [ ] https://github.com/derhuerst/generate-db-shop-urls - [ ] https://github.com/derhuerst/find-db-station-by-name - [ ] https://github.com/derhuerst/european-transport-modules - [ ] https://github.com/derhuerst/vbb-lines - [ ] https://github.com/derhuerst/db-stations", 5 | "main": "index.js", 6 | "dependencies": { 7 | "csv-parse": "^1.2.0", 8 | "db-hafas": "^1.0.1", 9 | "db-stations": "^1.8.0", 10 | "express": "^4.16.2", 11 | "express-graphql": "^0.6.11", 12 | "graphiql": "^0.11.10", 13 | "graphql": "^0.11.7", 14 | "graphql-playground-middleware-express": "^1.3.5", 15 | "install": "^0.10.1", 16 | "moment": "^2.18.1", 17 | "moment-timezone": "^0.5.13", 18 | "node-fetch": "^1.6.3", 19 | "npm": "^4.5.0", 20 | "prop-types": "^15.5.8", 21 | "react": "^16.2", 22 | "react-dom": "^16.2", 23 | "safe-access": "^0.1.0", 24 | "xml-js": "^1.2.2" 25 | }, 26 | "devDependencies": { 27 | "chai": "^4.0.2", 28 | "eslint": "^4.2.0", 29 | "eslint-config-airbnb": "^15.0.2", 30 | "eslint-plugin-import": "^2.7.0", 31 | "eslint-plugin-jsx-a11y": "^6.0.2", 32 | "eslint-plugin-react": "^7.1.0", 33 | "chai-as-promised": "^7.1.1", 34 | "mocha": "^3.4.2" 35 | }, 36 | "scripts": { 37 | "start": "node index.js", 38 | "test": "mocha test --recursive" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/dbsystel/1BahnQL" 43 | }, 44 | "keywords": [ 45 | "grapql", 46 | "db", 47 | "bahn" 48 | ], 49 | "author": "Dennis Post, Lukas Schmidt", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/dbsystel/1BahnQL/issues" 53 | }, 54 | "homepage": "https://github.com/dbsystel/1BahnQL#readme" 55 | } 56 | -------------------------------------------------------------------------------- /schema.js: -------------------------------------------------------------------------------- 1 | const { buildSchema } = require('graphql'); 2 | const experimental = process.env.experimental 3 | 4 | const experimentalTypes = experimental ? ` 5 | type Route { 6 | parts: [RoutePart!]! 7 | from: Station 8 | to: Station 9 | } 10 | 11 | type RoutePart { 12 | # Station where the part begins 13 | from: Station! 14 | to: Station! 15 | delay: Int 16 | product: Product 17 | direction: String! 18 | start: String! 19 | end: String! 20 | departingTrack: Track 21 | arrivingTrack: Track 22 | } 23 | 24 | type Product { 25 | name: String 26 | class: Int 27 | productCode: Int 28 | productName: String 29 | } 30 | ` : '' 31 | 32 | const experimentalQuerys = experimental ? ` 33 | routing(from: Int!, to: Int!): [Route!]! 34 | ` : '' 35 | 36 | const schema = buildSchema(` 37 | type Query { 38 | ${experimentalQuerys} 39 | 40 | stationWithEvaId(evaId: Int!): Station 41 | stationWithStationNumber(stationNumber: Int!): Station 42 | stationWithRill100(rill100: String!): Station 43 | 44 | search(searchTerm: String): Searchable! 45 | nearby(latitude: Float!, longitude: Float!, radius: Int = 10000): Nearby! 46 | parkingSpace(id: Int): ParkingSpace 47 | } 48 | 49 | ${experimentalTypes} 50 | 51 | type Searchable { 52 | stations: [Station!]! 53 | operationLocations: [OperationLocation!]! 54 | } 55 | 56 | type OperationLocation { 57 | id: String 58 | abbrev: String! 59 | name: String! 60 | shortName: String! 61 | type: String! 62 | status: String 63 | locationCode: String 64 | UIC: String! 65 | regionId: String 66 | validFrom: String! 67 | validTill: String 68 | timeTableRelevant: Boolean 69 | borderStation: Boolean 70 | } 71 | type Station { 72 | primaryEvaId: Int 73 | stationNumber: Int 74 | primaryRil100: String 75 | name: String! 76 | location: Location 77 | category: Int! 78 | priceCategory: Int! 79 | hasParking: Boolean! 80 | hasBicycleParking: Boolean! 81 | hasLocalPublicTransport: Boolean! 82 | hasPublicFacilities: Boolean! 83 | hasLockerSystem: Boolean! 84 | hasTaxiRank: Boolean! 85 | hasTravelNecessities: Boolean! 86 | hasSteplessAccess: String! 87 | hasMobilityService: String! 88 | federalState: String! 89 | regionalArea: RegionalArea! 90 | facilities: [Facility!]! 91 | mailingAddress: MailingAddress! 92 | DBInformationOpeningTimes: OpeningTimes 93 | localServiceStaffAvailability: OpeningTimes 94 | aufgabentraeger: StationContact! 95 | timeTableOffice: StationContact 96 | szentrale: StationContact! 97 | stationManagement: StationContact! 98 | timetable: Timetable! 99 | parkingSpaces: [ParkingSpace!]! 100 | hasSteamPermission: Boolean! 101 | hasWiFi: Boolean! 102 | hasTravelCenter: Boolean! 103 | hasRailwayMission: Boolean! 104 | hasDBLounge: Boolean! 105 | hasLostAndFound: Boolean! 106 | hasCarRental: Boolean! 107 | tracks: [Track!]! 108 | picture: Picture 109 | } 110 | 111 | type Track { 112 | platform: String! 113 | number: String! 114 | name: String! 115 | # Length of the platform in cm 116 | length: Int 117 | # Height of the platform in cm 118 | height: Int! 119 | } 120 | 121 | type Location { 122 | latitude: Float! 123 | longitude: Float! 124 | } 125 | 126 | type Facility { 127 | description: String 128 | type: FacilityType! 129 | state: FacilityState! 130 | equipmentNumber: Int 131 | location: Location 132 | } 133 | 134 | type Picture { 135 | id: Int! 136 | url: String! 137 | license: String! 138 | photographer: Photographer! 139 | } 140 | 141 | type Photographer { 142 | name: String! 143 | url: String! 144 | } 145 | 146 | enum FacilityState { 147 | ACTIVE 148 | INACTIVE 149 | UNKNOWN 150 | } 151 | 152 | enum FacilityType { 153 | ESCALATOR 154 | ELEVATOR 155 | } 156 | 157 | type MailingAddress { 158 | city: String! 159 | zipcode: String! 160 | street: String! 161 | } 162 | 163 | type RegionalArea { 164 | number: Int! 165 | name: String! 166 | shortName: String! 167 | } 168 | 169 | type OpeningTimes { 170 | monday: OpeningTime 171 | tuesday: OpeningTime 172 | wednesday: OpeningTime 173 | thursday: OpeningTime 174 | friday: OpeningTime 175 | saturday: OpeningTime 176 | sunday: OpeningTime 177 | holiday: OpeningTime 178 | } 179 | 180 | type OpeningTime { 181 | from: String! 182 | to: String! 183 | } 184 | 185 | type StationContact { 186 | name: String! 187 | shortName: String 188 | email: String 189 | number: String 190 | phoneNumber: String 191 | } 192 | 193 | type Nearby { 194 | stations (count: Int = 10, offset: Int = 0): [Station!]! 195 | parkingSpaces (count: Int = 10, offset: Int = 0): [ParkingSpace!]! 196 | travelCenters (count: Int = 10, offset: Int = 0): [TravelCenter!]! 197 | flinksterCars (count: Int = 10, offset: Int = 0): [FlinksterCar!]! 198 | bikes (count: Int = 10, offset: Int = 0): [FlinksterBike!]! 199 | } 200 | 201 | type ParkingSpace { 202 | type: String 203 | id: Int! 204 | name: String 205 | label: String 206 | spaceNumber: String 207 | responsibility: String 208 | source: String 209 | nameDisplay: String 210 | spaceType: String 211 | spaceTypeEn: String 212 | spaceTypeName: String 213 | location: Location 214 | url: String 215 | operator: String 216 | operatorUrl: String 217 | address: MailingAddress 218 | distance: String 219 | facilityType: String 220 | facilityTypeEn: String 221 | openingHours: String 222 | openingHoursEn: String 223 | numberParkingPlaces: String 224 | numberHandicapedPlaces: String 225 | isSpecialProductDb: Boolean! 226 | isOutOfService: Boolean! 227 | station: Station 228 | occupancy: Occupancy 229 | outOfServiceText: String 230 | outOfServiceTextEn: String 231 | reservation: String 232 | clearanceWidth: String 233 | clearanceHeight: String 234 | allowedPropulsions: String 235 | hasChargingStation: String 236 | tariffPrices: [ParkingPriceOption!]! 237 | outOfService: Boolean! 238 | isDiscountDbBahnCard: Boolean! 239 | isMonthVendingMachine: Boolean! 240 | isDiscountDbBahnComfort: Boolean! 241 | isDiscountDbParkAndRail: Boolean! 242 | isMonthParkAndRide: Boolean! 243 | isMonthSeason: Boolean! 244 | tariffDiscount: String 245 | tariffFreeParkingTime: String 246 | tariffDiscountEn: String 247 | tariffPaymentOptions: String 248 | tariffPaymentCustomerCards: String 249 | tariffFreeParkingTimeEn: String 250 | tariffPaymentOptionsEn: String 251 | slogan: String 252 | sloganEn: String 253 | occupancy: Occupancy 254 | } 255 | 256 | type ParkingPriceOption { 257 | id: Int! 258 | duration: String! 259 | price: Float 260 | } 261 | 262 | type Occupancy { 263 | validData: Boolean! 264 | timestamp: String! 265 | timeSegment: String! 266 | category: Int! 267 | text: String! 268 | } 269 | 270 | type Timetable { 271 | nextArrivals: [TrainInStation!]! 272 | nextDepatures: [TrainInStation!]! 273 | } 274 | 275 | type TrainInStation { 276 | type: String! 277 | trainNumber: String! 278 | platform: String! 279 | time: String! 280 | stops: [String!]! 281 | } 282 | 283 | type TravelCenter { 284 | id: Int 285 | name: String 286 | address: MailingAddress 287 | type: String 288 | location: Location 289 | } 290 | 291 | type FlinksterCar { 292 | id: String! 293 | name: String! 294 | description: String! 295 | attributes: CarAttributes! 296 | location: Location! 297 | priceOptions: [PriceOption]! 298 | equipment: CarEquipment! 299 | rentalModel: String! 300 | parkingArea: FlinksterParkingArea! 301 | category: String! 302 | url: String! 303 | } 304 | 305 | type FlinksterBike { 306 | id: String! 307 | url: String! 308 | name: String! 309 | description: String! 310 | location: Location! 311 | priceOptions: [PriceOption]! 312 | attributes: BikeAttributes! 313 | address: MailingAddress! 314 | rentalModel: String! 315 | type: String! 316 | providerRentalObjectId: Int! 317 | parkingArea: FlinksterParkingArea! 318 | bookingUrl: String! 319 | } 320 | 321 | type CarAttributes { 322 | seats: Int! 323 | color: String! 324 | doors: Int! 325 | transmissionType: String! 326 | licensePlate: String 327 | fillLevel: Int 328 | fuel: String 329 | } 330 | 331 | type CarEquipment { 332 | cdPlayer: Boolean 333 | airConditioning: Boolean 334 | navigationSystem: Boolean 335 | roofRailing: Boolean 336 | particulateFilter: Boolean 337 | audioInline: Boolean 338 | tyreType: String 339 | bluetoothHandsFreeCalling: Boolean 340 | cruiseControl: Boolean 341 | passengerAirbagTurnOff: Boolean 342 | isofixSeatFittings: Boolean 343 | } 344 | 345 | type FlinksterParkingArea { 346 | id: String! 347 | url: String! 348 | name: String! 349 | address: MailingAddress! 350 | parkingDescription: String 351 | accessDescription: String 352 | locationDescription: String 353 | publicTransport: String 354 | provider: FlinksterProvider! 355 | type: String! 356 | position: Location! 357 | GeoJSON: GeoJSON 358 | } 359 | 360 | type GeoJSON { 361 | type: String! 362 | features: [GeoFeature!]! 363 | } 364 | 365 | type GeoFeature { 366 | type: String! 367 | properties: GeoProperties! 368 | geometry: GeoPolygon! 369 | } 370 | 371 | type GeoPolygon { 372 | type: String! 373 | coordinates: [[[[Float]]]]! 374 | } 375 | 376 | type GeoProperties { 377 | name: String! 378 | } 379 | 380 | type FlinksterProvider { 381 | url: String! 382 | areaId: Int! 383 | networkIds: [Int!]! 384 | } 385 | 386 | type BikeAttributes { 387 | licensePlate: String! 388 | } 389 | 390 | type PriceOption { 391 | interval: Int 392 | type: String! 393 | grossamount: Float! 394 | currency: String! 395 | taxrate: Float! 396 | preferredprice: Boolean! 397 | } 398 | `); 399 | 400 | module.exports = schema; 401 | -------------------------------------------------------------------------------- /test/Facility/FacilitiesMockResults.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "equipmentnumber": 10355942, 4 | "type": "ELEVATOR", 5 | "description": "zu Gleis 6/7", 6 | "geocoordX": 6.09134, 7 | "geocoordY": 50.767629, 8 | "state": "ACTIVE", 9 | "stationnumber": 1 10 | }, 11 | { 12 | "equipmentnumber": 10355941, 13 | "type": "ELEVATOR", 14 | "description": "zu Gleis 8/9", 15 | "geocoordX": 6.0914006, 16 | "geocoordY": 50.7675157, 17 | "state": "ACTIVE", 18 | "stationnumber": 1 19 | }, 20 | { 21 | "equipmentnumber": 10318681, 22 | "type": "ELEVATOR", 23 | "description": "zu Gleis 1", 24 | "geocoordX": 6.0911305, 25 | "geocoordY": 50.7680045, 26 | "state": "ACTIVE", 27 | "stationnumber": 1 28 | }, 29 | { 30 | "equipmentnumber": 10355943, 31 | "type": "ELEVATOR", 32 | "description": "zu Gleis 2/3", 33 | "geocoordX": 6.0912217, 34 | "geocoordY": 50.767839, 35 | "state": "ACTIVE", 36 | "stationnumber": 1 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /test/Facility/FacilityLoaderMock.js: -------------------------------------------------------------------------------- 1 | class FacilityLoaderMock { 2 | constructor(result) { 3 | this.result = result 4 | } 5 | 6 | facilitiesForStationNumber(stationNumber) { 7 | this.stationNumber = stationNumber 8 | return Promise.resolve(this.result); 9 | } 10 | } 11 | 12 | module.exports = FacilityLoaderMock 13 | -------------------------------------------------------------------------------- /test/Facility/FacilityServiceTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require("chai-as-promised"); 6 | chai.use(chaiAsPromised); 7 | const expect = chai.expect; 8 | 9 | const FacilityLoaderMock = require('./FacilityLoaderMock.js'); 10 | const FacilityService = require('./../../Facility/FacilityService.js'); 11 | 12 | 13 | describe('FacilityService', () => { 14 | var facilityLoaderMock = new FacilityLoaderMock(); 15 | let facilityServcie = new FacilityService(facilityLoaderMock) 16 | 17 | beforeEach(function() { 18 | facilityLoaderMock = new FacilityLoaderMock(); 19 | facilityServcie = new FacilityService(facilityLoaderMock); 20 | }); 21 | 22 | it('stationByEvaId should return valid station', () => { 23 | facilityLoaderMock.result = require('./FacilitiesMockResults'); 24 | let promise = facilityServcie.facilitiesForStationNumber(1).then(facilities => facilities[0]); 25 | 26 | return Promise.all([ 27 | expect(promise).to.eventually.have.property("equipmentNumber", 10355942), 28 | expect(promise).to.eventually.have.property("type", "ELEVATOR"), 29 | expect(promise).to.eventually.have.property("description", "zu Gleis 6/7"), 30 | expect(promise).to.eventually.have.property("state", "ACTIVE"), 31 | expect(promise).to.eventually.have.deep.property("location", {latitude: 50.767629, longitude: 6.09134}) 32 | 33 | ]); 34 | }); 35 | 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /test/Flinkster/FlinksterLoaderMock.js: -------------------------------------------------------------------------------- 1 | class FlinksterLoaderMock { 2 | constructor() { 3 | this.flinksterMockResult; 4 | } 5 | 6 | nearbyFlinksters(type, latitude, longitude, radius, count, offset) { 7 | this.type = type; 8 | this.latitude = latitude; 9 | this.longitude = longitude; 10 | this.radius = radius; 11 | this.count = count; 12 | this.offset = offset; 13 | return Promise.resolve(this.flinksterMockResult); 14 | } 15 | } 16 | 17 | module.exports = FlinksterLoaderMock; -------------------------------------------------------------------------------- /test/Flinkster/FlinksterServiceTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require('chai-as-promised'); 6 | 7 | const FlinksterService = require('../../Flinkster/FlinksterService'); 8 | const FlinksterLoaderMock = require('./FlinksterLoaderMock'); 9 | 10 | chai.use(chaiAsPromised); 11 | const expect = chai.expect; 12 | 13 | describe('FlinksterService', () => { 14 | let flinksterLoaderMock = new FlinksterLoaderMock(); 15 | let flinksterService = new FlinksterService(flinksterLoaderMock); 16 | 17 | beforeEach(() => { 18 | flinksterLoaderMock = new FlinksterLoaderMock(); 19 | flinksterService = new FlinksterService(flinksterLoaderMock); 20 | }); 21 | 22 | describe('Cars', () => { 23 | it('passes over correct parameters to loader', () => { 24 | const promise = flinksterService.nearbyFlinksterCars(50.11, 8.66, 1000, 10, 0); 25 | expect(flinksterLoaderMock.type).to.be.equal(1); 26 | expect(flinksterLoaderMock.latitude).to.be.equal(50.11); 27 | expect(flinksterLoaderMock.longitude).to.be.equal(8.66); 28 | expect(flinksterLoaderMock.radius).to.be.equal(1000); 29 | expect(flinksterLoaderMock.count).to.be.equal(10); 30 | expect(flinksterLoaderMock.offset).to.be.equal(0); 31 | }) 32 | 33 | it('should return nearby cars', () => { 34 | flinksterLoaderMock.flinksterMockResult = require('./NearbyCarsMockResult'); 35 | const promise = flinksterService.nearbyFlinksterCars(50.11, 8.66, 1000, 10, 0); 36 | 37 | return Promise.all([ 38 | expect(promise).to.eventually.have.nested.property('[0].id', '4A8EF25D7CDBC02BB9D79823A847A3A6D717C6E7'), 39 | expect(promise).to.eventually.have.length.of(10), 40 | ]); 41 | }); 42 | }); 43 | 44 | describe('Bikes', () => { 45 | it('passes over correct parameters to loader', () => { 46 | const promise = flinksterService.nearbyFlinksterBikes(50.11, 8.66, 1000, 10, 0); 47 | expect(flinksterLoaderMock.type).to.be.equal(2); 48 | expect(flinksterLoaderMock.latitude).to.be.equal(50.11); 49 | expect(flinksterLoaderMock.longitude).to.be.equal(8.66); 50 | expect(flinksterLoaderMock.radius).to.be.equal(1000); 51 | expect(flinksterLoaderMock.count).to.be.equal(10); 52 | expect(flinksterLoaderMock.offset).to.be.equal(0); 53 | }) 54 | it('should return nearby bikes', () => { 55 | flinksterLoaderMock.flinksterMockResult = require('./NearbyBikesMockResult'); 56 | const promise = flinksterService.nearbyFlinksterBikes(50.11, 8.66, 1000, 10, 0); 57 | 58 | return Promise.all([ 59 | expect(promise).to.eventually.have.nested.property('[0].id', 'CD6C50E89A09C65209E211C4143E8B3231ED9F04'), 60 | expect(promise).to.eventually.have.length.of(10), 61 | ]); 62 | }); 63 | }); 64 | 65 | describe.skip('Bikes And Cars', () => { 66 | it('should return nearby bikes and cars (mixed)', () => { 67 | flinksterLoaderMock.flinksterMockResult = require('./NearbyBikesAndCarsMockResult'); 68 | const promise = flinksterService.nearbyFlinksterBikesAndCars(50.11, 8.66, 1000, 10, 0); 69 | 70 | return Promise.all([ 71 | expect(promise).to.eventually.have.nested.property('[0].id', ''), 72 | expect(promise).to.eventually.have.length.of(10), 73 | ]); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/Parkingspace/OccupancyMockResult.json: -------------------------------------------------------------------------------- 1 | { 2 | "space": { 3 | "id": 100082, 4 | "title": "Frankfurt (Main) Hbf P2 Frankfurt (Main) Hbf P2 Vorfahrt I", 5 | "station": { 6 | "id": 1866, 7 | "name": "Frankfurt (Main) Hbf" 8 | }, 9 | "label": "P2", 10 | "name": "Hauptbahnhof Vorfahrt I", 11 | "nameDisplay": "Frankfurt (Main) Hbf P2 Vorfahrt I" 12 | }, 13 | "allocation": { 14 | "validData": true, 15 | "timestamp": "2017-07-27T17:10:00", 16 | "timeSegment": "2017-07-27T17:10:00", 17 | "capacity": 47, 18 | "category": 2, 19 | "text": "> 10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/Parkingspace/ParkingspaceLoaderMock.js: -------------------------------------------------------------------------------- 1 | class ParkingspaceLoaderMock { 2 | constructor() { 3 | this.parkingspaceMockResult; 4 | this.occupancyMockResult; 5 | } 6 | 7 | spaceById(spaceId) { 8 | this.spaceId = spaceId; 9 | return Promise.resolve(this.parkingspaceMockResult); 10 | } 11 | 12 | occupancyForId(spaceId) { 13 | this.spaceId = spaceId; 14 | return Promise.resolve(this.occupancyMockResult); 15 | } 16 | 17 | spacesForStationNumber(stationNumber) { 18 | this.stationNumber = stationNumber; 19 | return Promise.resolve(this.parkingspaceMockResult); 20 | } 21 | 22 | nearbyParkingspaces(latitude, longitude, radius) { 23 | this.latitude = latitude; 24 | this.longitude = longitude; 25 | this.radius = radius; 26 | return Promise.resolve(this.parkingspaceMockResult); 27 | } 28 | } 29 | 30 | module.exports = ParkingspaceLoaderMock; 31 | -------------------------------------------------------------------------------- /test/Parkingspace/ParkingspaceServiceTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require('chai-as-promised'); 6 | 7 | const ParkingspaceService = require('../../Parkingspace/ParkingspaceService'); 8 | const ParkingspaceLoaderMock = require('./ParkingspaceLoaderMock'); 9 | const ParkingspaceRelationships = require('../../Parkingspace/ParkingspaceRelationships'); 10 | 11 | chai.use(chaiAsPromised); 12 | const expect = chai.expect; 13 | 14 | describe('ParkingspaceService', () => { 15 | let parkingspaceLoaderMock = new ParkingspaceLoaderMock(); 16 | let parkingspaceService = new ParkingspaceService(parkingspaceLoaderMock); 17 | 18 | beforeEach(() => { 19 | parkingspaceLoaderMock = new ParkingspaceLoaderMock(); 20 | parkingspaceService = new ParkingspaceService(parkingspaceLoaderMock); 21 | parkingspaceService.relationships = new ParkingspaceRelationships(parkingspaceService, null); 22 | }); 23 | 24 | describe('parkingspaceBySpaceId', () => { 25 | it('should return valid parkingspace', () => { 26 | parkingspaceLoaderMock.parkingspaceMockResult = require('./SingleParkingspaceMockResult'); 27 | const promise = parkingspaceService.parkingspaceBySpaceId(100082); 28 | 29 | return Promise.all([ 30 | expect(promise).to.eventually.have.property('id', 100082), 31 | expect(promise).to.eventually.have.deep.property('address', { city: 'Frankfurt am Main', zipcode: '60329', street: 'Poststraße' }), 32 | ]); 33 | }); 34 | 35 | // Test will be added with correct error handling 36 | it('should return null for wrong spaceId', () => { 37 | parkingspaceLoaderMock.parkingspaceMockResult = require('./WrongSpaceIdMockResult'); 38 | const promise = parkingspaceService.parkingspaceBySpaceId(1234567); 39 | 40 | return Promise.all([ 41 | expect(promise).to.eventually.become(null), 42 | ]); 43 | }); 44 | }); 45 | 46 | 47 | describe('parkingspacesForStationNumber', () => { 48 | it('should return only parkingspaces of requested station', () => { 49 | parkingspaceLoaderMock.parkingspaceMockResult = require('./AllSpacesMockResult'); 50 | const promise = parkingspaceService.parkingspacesForStationNumber(1866); 51 | 52 | return Promise.all([ 53 | expect(promise).to.eventually.have.nested.property('[0].station.id', 1866), 54 | ]); 55 | }); 56 | }); 57 | 58 | describe('occupancyForSpaceId', () => { 59 | it('should return valid occupancy data', () => { 60 | parkingspaceLoaderMock.parkingspaceMockResult = require('./SingleParkingspaceMockResult'); 61 | parkingspaceLoaderMock.occupancyMockResult = require('./OccupancyMockResult'); 62 | const promise = parkingspaceService.parkingspaceBySpaceId(100082); 63 | 64 | return promise.then(space => Promise.all([ 65 | expect(space.id).to.equal(100082), 66 | expect(space.occupancy().then()).to.eventually.have.property('text', '> 10'), 67 | ])); 68 | }); 69 | }); 70 | 71 | describe('nearbyParkingspaces', () => { 72 | it('should return only parkingspaces within radius', () => { 73 | parkingspaceLoaderMock.parkingspaceMockResult = require('./AllSpacesMockResult'); 74 | const promise = parkingspaceService.nearbyParkingspaces(50.11, 8.66, 1000, 10, 0); 75 | 76 | return Promise.all([ 77 | expect(promise).to.eventually.have.length.of(8), 78 | expect(promise).to.eventually.have.nested.property('[0].distance', 25.38), 79 | expect(promise).to.eventually.have.nested.property('[7].distance').to.be.below(1000), 80 | ]); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /test/Parkingspace/SingleParkingspaceMockResult.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "space", 3 | "id": 100082, 4 | "title": "Frankfurt (Main) Hbf P2 Frankfurt (Main) Hbf P2 Vorfahrt I", 5 | "station": { 6 | "id": 1866, 7 | "name": "Frankfurt (Main) Hbf" 8 | }, 9 | "label": "P2", 10 | "spaceNumber": "57", 11 | "responsibility": "dbbahnpark", 12 | "source": "dbbahnpark", 13 | "name": "Hauptbahnhof Vorfahrt I", 14 | "nameDisplay": "Frankfurt (Main) Hbf P2 Vorfahrt I", 15 | "spaceType": "Parkplatz", 16 | "spaceTypeEn": "Surface car park", 17 | "spaceTypeName": "Parkplatz", 18 | "geoLocation": { 19 | "longitude": 8.663243, 20 | "latitude": 50.107951 21 | }, 22 | "url": "http://www.dbbahnpark.info/content/fahrplanauskunft/bahnpark/pdf/8000105.pdf", 23 | "operator": "Contipark Parkgaragengesellschaft mbH", 24 | "operatorUrl": "www.contipark.de", 25 | "address": { 26 | "cityName": "Frankfurt am Main", 27 | "postalCode": "60329", 28 | "street": "Poststraße" 29 | }, 30 | "distance": "<50", 31 | "facilityType": "Schrankenanlage", 32 | "facilityTypeEn": "Barrier system", 33 | "openingHours": "24 Stunden, 7 Tage", 34 | "openingHoursEn": "24 hours a day, 7 days a week", 35 | "numberParkingPlaces": "43", 36 | "numberHandicapedPlaces": "0", 37 | "isSpecialProductDb": true, 38 | "isOutOfService": false, 39 | "spaceInfo": { 40 | "clearanceWidth": "330", 41 | "clearanceHeight": "500", 42 | "allowedPropulsions": "Alle", 43 | "chargingStation": "nein" 44 | }, 45 | "spaceFlags": {}, 46 | "tariffFlags": { 47 | "isDiscountDbBahnCard": false, 48 | "isMonthVendingMachine": false, 49 | "isDiscountDbBahnComfort": true, 50 | "isDiscountDbParkAndRail": false, 51 | "isMonthParkAndRide": false, 52 | "isMonthSeason": false 53 | }, 54 | "tariffInfo": { 55 | "tariffDiscount": "Rabattierung mit bahn.comfort-Karte direkt am Kassenautomaten.", 56 | "tariffFreeParkingTime": "nein", 57 | "tariffDiscountEn": "Bahn.comfort card discount available directly at the pay station.", 58 | "tariffPaymentOptions": "Münzen, Scheine, Kreditkarten (AMEX, MasterCard, VISA), P Card", 59 | "tariffPointOfSale": "Nur mit gültiger bahn.comfort-Karte.", 60 | "tariffPointOfSaleEn": "Only with a valid bahn.comfort card. ", 61 | "tariffPaymentCustomerCards": "P Card", 62 | "tariffFreeParkingTimeEn": "nein", 63 | "tariffPaymentOptionsEn": "Coins, bills, credit cards (AMEX, MasterCard, VISA), P Card" 64 | }, 65 | "tariffPrices": [ 66 | { 67 | "id": 219182, 68 | "duration": "20min" 69 | }, 70 | { 71 | "id": 219183, 72 | "duration": "30min", 73 | "price": 2 74 | }, 75 | { 76 | "id": 219184, 77 | "duration": "1hour", 78 | "price": 4 79 | }, 80 | { 81 | "id": 219185, 82 | "duration": "1day", 83 | "price": 29 84 | }, 85 | { 86 | "id": 219186, 87 | "duration": "1dayDiscount", 88 | "price": 24.5 89 | }, 90 | { 91 | "id": 219187, 92 | "duration": "1week" 93 | }, 94 | { 95 | "id": 219188, 96 | "duration": "1weekDiscount" 97 | }, 98 | { 99 | "id": 219189, 100 | "duration": "1monthVendingMachine" 101 | }, 102 | { 103 | "id": 219190, 104 | "duration": "1monthLongTerm" 105 | }, 106 | { 107 | "id": 219191, 108 | "duration": "1monthReservation" 109 | } 110 | ] 111 | } -------------------------------------------------------------------------------- /test/Parkingspace/WrongSpaceIdMockResult.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 4100, 3 | "error": "STORAGE_ENTITY_NOT_FOUND", 4 | "message": "No space with id '12345' found.", 5 | "userMessage": "No space with id '12345' found." 6 | } -------------------------------------------------------------------------------- /test/Routing/RouteRelationshipsTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require("chai-as-promised"); 6 | chai.use(chaiAsPromised); 7 | const expect = chai.expect; 8 | 9 | const RouteRelationships = require('./../../Routing/RouteRelationships'); 10 | 11 | describe('RouteRelationships', () => { 12 | 13 | it('resolve should connect stationService & from', () => { 14 | //Given 15 | var capturedEvaId; 16 | let relationships = new RouteRelationships(); 17 | relationships.stationService = { stationByEvaId: (evaId) => { capturedEvaId = evaId; return "Sucess" } } 18 | let firstRoutePart = { fromEvaId: 1}; 19 | let route = { parts: [firstRoutePart] }; 20 | 21 | //When 22 | relationships.resolve(route); 23 | 24 | //Then 25 | expect(route.from()).to.be.equal("Sucess"); 26 | expect(capturedEvaId).to.be.equal(1); 27 | }); 28 | 29 | it('resolve should connect stationService & to', () => { 30 | //Given 31 | var capturedEvaId; 32 | let relationships = new RouteRelationships(); 33 | relationships.stationService = { stationByEvaId: (evaId) => { capturedEvaId = evaId; return "Sucess" } } 34 | let firstRoutePart = { toEvaId: 1 }; 35 | let route = { parts: [firstRoutePart] }; 36 | 37 | //When 38 | relationships.resolve(route); 39 | 40 | //Then 41 | expect(route.to()).to.be.equal("Sucess"); 42 | expect(capturedEvaId).to.be.equal(1); 43 | }); 44 | 45 | it('resolve should connect stationService & route.parts.from', () => { 46 | //Given 47 | var capturedEvaId; 48 | let relationships = new RouteRelationships(); 49 | relationships.stationService = { stationByEvaId: (evaId) => { capturedEvaId = evaId; return "Sucess" } } 50 | let firstRoutePart = { toEvaId: 1 }; 51 | let route = { parts: [firstRoutePart] }; 52 | 53 | //When 54 | relationships.resolve(route); 55 | 56 | //Then 57 | expect(route.parts[0].to()).to.be.equal("Sucess"); 58 | expect(capturedEvaId).to.be.equal(1); 59 | }); 60 | 61 | it('resolve should connect stationService & route.parts.to', () => { 62 | //Given 63 | var capturedEvaId; 64 | let relationships = new RouteRelationships(); 65 | relationships.stationService = { stationByEvaId: (evaId) => { capturedEvaId = evaId; return "Sucess" } } 66 | let firstRoutePart = { fromEvaId: 1 }; 67 | let route = { parts: [firstRoutePart] }; 68 | 69 | //When 70 | relationships.resolve(route); 71 | 72 | //Then 73 | expect(route.parts[0].from()).to.be.equal("Sucess"); 74 | expect(capturedEvaId).to.be.equal(1); 75 | }); 76 | 77 | it('resolve should connect trackService & route.parts.departingTrack', () => { 78 | //Given 79 | var capturedEvaId; 80 | var capturedPlatform; 81 | let relationships = new RouteRelationships(); 82 | relationships.trackService = { trackByEvaIdAndTrackNumber: (evaId, trackNumber) => { capturedEvaId = evaId; capturedPlatform = trackNumber; return "Sucess" } } 83 | let firstRoutePart = { fromEvaId: 1, departingPlatformNumber: "1" }; 84 | let route = { parts: [firstRoutePart] }; 85 | 86 | //When 87 | relationships.resolve(route); 88 | 89 | //Then 90 | expect(route.parts[0].departingTrack()).to.be.equal("Sucess"); 91 | expect(capturedEvaId).to.be.equal(1); 92 | expect(capturedPlatform).to.be.equal("1"); 93 | }); 94 | 95 | it('resolve should connect trackService & route.parts.arrivingTrack', () => { 96 | //Given 97 | var capturedEvaId; 98 | var capturedPlatform; 99 | let relationships = new RouteRelationships(); 100 | relationships.trackService = { trackByEvaIdAndTrackNumber: (evaId, trackNumber) => { capturedEvaId = evaId; capturedPlatform = trackNumber; return "Sucess" } } 101 | let firstRoutePart = { toEvaId: 1, arrivingPlatformNumber: "1" }; 102 | let route = { parts: [firstRoutePart] }; 103 | 104 | //When 105 | relationships.resolve(route); 106 | 107 | //Then 108 | expect(route.parts[0].arrivingTrack()).to.be.equal("Sucess"); 109 | expect(capturedEvaId).to.be.equal(1); 110 | expect(capturedPlatform).to.be.equal("1"); 111 | }); 112 | 113 | 114 | }); 115 | -------------------------------------------------------------------------------- /test/Station/SingleStationMockResult.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": 1, 3 | "name": "Aachen Hbf", 4 | "mailingAddress": { 5 | "city": "Aachen", 6 | "zipcode": "52064", 7 | "street": "Bahnhofplatz 2a" 8 | }, 9 | "category": 2, 10 | "priceCategory": 2, 11 | "hasParking": true, 12 | "hasBicycleParking": true, 13 | "hasLocalPublicTransport": true, 14 | "hasPublicFacilities": true, 15 | "hasLockerSystem": true, 16 | "hasTaxiRank": true, 17 | "hasTravelNecessities": true, 18 | "hasSteplessAccess": "yes", 19 | "hasMobilityService": "Ja, um Voranmeldung unter 01806 512 512 wird gebeten", 20 | "hasWiFi": true, 21 | "hasTravelCenter": true, 22 | "hasRailwayMission": true, 23 | "hasDBLounge": false, 24 | "hasLostAndFound": true, 25 | "hasCarRental": false, 26 | "federalState": "Nordrhein-Westfalen", 27 | "regionalbereich": { 28 | "number": 4, 29 | "name": "RB West", 30 | "shortName": "RB W" 31 | }, 32 | "aufgabentraeger": { 33 | "shortName": "NVR", 34 | "name": "Zweckverband Nahverkehr Rheinland GmbH" 35 | }, 36 | "localServiceStaff": { 37 | "availability": { 38 | "monday": { 39 | "fromTime": "06:00", 40 | "toTime": "22:30" 41 | }, 42 | "tuesday": { 43 | "fromTime": "06:00", 44 | "toTime": "22:30" 45 | }, 46 | "wednesday": { 47 | "fromTime": "06:00", 48 | "toTime": "22:30" 49 | }, 50 | "thursday": { 51 | "fromTime": "06:00", 52 | "toTime": "22:30" 53 | }, 54 | "friday": { 55 | "fromTime": "06:00", 56 | "toTime": "22:30" 57 | }, 58 | "saturday": { 59 | "fromTime": "06:00", 60 | "toTime": "22:30" 61 | }, 62 | "sunday": { 63 | "fromTime": "06:00", 64 | "toTime": "22:30" 65 | }, 66 | "holiday": { 67 | "fromTime": "06:00", 68 | "toTime": "22:30" 69 | } 70 | } 71 | }, 72 | "timeTableOffice": { 73 | "email": "DBS.Fahrplan.NordrheinWestfalen@deutschebahn.com", 74 | "name": "Bahnhofsmanagement Köln" 75 | }, 76 | "szentrale": { 77 | "number": 15, 78 | "publicPhoneNumber": "0203/30171055", 79 | "name": "Duisburg Hbf" 80 | }, 81 | "stationManagement": { 82 | "number": 45, 83 | "name": "Düsseldorf" 84 | }, 85 | "evaNumbers": [ 86 | { 87 | "number": 8000001, 88 | "geographicCoordinates": { 89 | "type": "Point", 90 | "coordinates": [ 91 | 6.091499, 92 | 50.7678 93 | ] 94 | }, 95 | "isMain": true 96 | } 97 | ], 98 | "ril100Identifiers": [ 99 | { 100 | "rilIdentifier": "KA", 101 | "isMain": true, 102 | "hasSteamPermission": true, 103 | "geographicCoordinates": { 104 | "type": "Point", 105 | "coordinates": [ 106 | 6.091201396, 107 | 50.767558188 108 | ] 109 | } 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /test/Station/StationLoaderMock.js: -------------------------------------------------------------------------------- 1 | class StationLoaderMock { 2 | constructor(result) { 3 | this.result = result 4 | } 5 | 6 | stationByBahnhofsnummer(stationNumber) { 7 | this.stationNumber = stationNumber 8 | return Promise.resolve(this.result); 9 | } 10 | 11 | stationByEvaId(evaId) { 12 | this.evaId = evaId 13 | return Promise.resolve(this.result); 14 | } 15 | 16 | stationByRil100(rill100) { 17 | this.rill100 = rill100 18 | return Promise.resolve(this.result); 19 | } 20 | 21 | searchStations(searchTerm) { 22 | this.searchTerm = searchTerm 23 | return Promise.resolve(this.result); 24 | } 25 | } 26 | 27 | module.exports = StationLoaderMock 28 | -------------------------------------------------------------------------------- /test/Station/StationRelationshipsTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require("chai-as-promised"); 6 | chai.use(chaiAsPromised); 7 | const expect = chai.expect; 8 | 9 | const StationRelationships = require('./../../Station/StationRelationships'); 10 | 11 | describe('StationRelationships', () => { 12 | 13 | it('resolve should connect parkingSpaceService & parkingSpaces', () => { 14 | //Given 15 | var capturedStationNumber; 16 | let relationships = new StationRelationships(); 17 | relationships.parkingSpaceService = { parkingspacesForStationNumber: (stationNumber) => { capturedStationNumber = stationNumber; return "Sucess" } } 18 | let station = { stationNumber: 1 }; 19 | 20 | //When 21 | relationships.resolve(station); 22 | 23 | //Then 24 | expect(station.parkingSpaces()).to.be.equal("Sucess"); 25 | expect(capturedStationNumber).to.be.equal(1); 26 | }); 27 | 28 | it('resolve should connect facilityService & facilities', () => { 29 | //Given 30 | var capturedStationNumber; 31 | let relationships = new StationRelationships(); 32 | relationships.facilityService = { facilitiesForStationNumber: (stationNumber) => { capturedStationNumber = stationNumber; return "Sucess" } } 33 | let station = { stationNumber: 1 }; 34 | 35 | //When 36 | relationships.resolve(station); 37 | 38 | //Then 39 | expect(station.facilities()).to.be.equal("Sucess"); 40 | expect(capturedStationNumber).to.be.equal(1); 41 | }); 42 | 43 | it('resolve should connect timetableService & timetable', () => { 44 | //Given 45 | var capturedPrimaryEvaId; 46 | let relationships = new StationRelationships(); 47 | relationships.timetableService = { timetableForEvaId: (primaryEvaId) => { capturedPrimaryEvaId = primaryEvaId; return "Sucess" } } 48 | let station = { primaryEvaId: 1 }; 49 | 50 | //When 51 | relationships.resolve(station); 52 | 53 | //Then 54 | expect(station.timetable()).to.be.equal("Sucess"); 55 | expect(capturedPrimaryEvaId).to.be.equal(1); 56 | }); 57 | 58 | it('resolve should connect trackService & tracks', () => { 59 | //Given 60 | var capturedStationNumber; 61 | let relationships = new StationRelationships(); 62 | relationships.trackService = { tracksForStationNumber: (stationNumber) => { capturedStationNumber = stationNumber; return "Sucess" } } 63 | let station = { stationNumber: 1 }; 64 | 65 | //When 66 | relationships.resolve(station); 67 | 68 | //Then 69 | expect(station.tracks()).to.be.equal("Sucess"); 70 | expect(capturedStationNumber).to.be.equal(1); 71 | }); 72 | 73 | it('resolve should connect stationPictureService & picture', () => { 74 | //Given 75 | var capturedStationNumber; 76 | let relationships = new StationRelationships(); 77 | relationships.stationPictureService = { stationPictureForStationNumber: (stationNumber) => { capturedStationNumber = stationNumber; return "Sucess" } } 78 | let station = { stationNumber: 1 }; 79 | 80 | //When 81 | relationships.resolve(station); 82 | 83 | //Then 84 | expect(station.picture()).to.be.equal("Sucess"); 85 | expect(capturedStationNumber).to.be.equal(1); 86 | }); 87 | 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /test/Station/StationServiceTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const StationService = require('../../Station/StationService.js') 6 | const StationLoaderMock = require('./StationLoaderMock.js') 7 | const StationIdMappingService = require('../../Station/StationIdMappingService.js') 8 | const chaiAsPromised = require("chai-as-promised"); 9 | const StationRelationships = require("../../Station/StationRelationships"); 10 | 11 | chai.use(chaiAsPromised); 12 | const expect = chai.expect; 13 | 14 | describe('StationService', () => { 15 | let stationLoaderMock = new StationLoaderMock() 16 | let stationService = new StationService(stationLoaderMock) 17 | 18 | beforeEach(function() { 19 | stationLoaderMock = new StationLoaderMock() 20 | let idMapping = {stationNumberByEvaId: () => { return Promise.resolve(1) }} 21 | stationService = new StationService(stationLoaderMock, idMapping) 22 | stationService.relationships = { resolve: function(station) {} } 23 | }); 24 | 25 | it('stationByEvaId should return valid station', () => { 26 | stationLoaderMock.result = require('./SingleStationMockResult') 27 | let promise = stationService.stationByEvaId(1); 28 | expect(stationLoaderMock.evaId).to.be.equal(1); 29 | return Promise.all([ 30 | expect(promise).to.eventually.have.property("primaryEvaId", 8000001), 31 | expect(promise).to.eventually.have.property("stationNumber", 1), 32 | expect(promise).to.eventually.have.property("primaryRil100", "KA"), 33 | expect(promise).to.eventually.have.property("name", "Aachen Hbf"), 34 | expect(promise).to.eventually.have.property("category", 2), 35 | expect(promise).to.eventually.have.property("priceCategory", 2), 36 | expect(promise).to.eventually.have.property("hasParking", true), 37 | expect(promise).to.eventually.have.property("hasBicycleParking", true), 38 | expect(promise).to.eventually.have.property("hasLocalPublicTransport", true), 39 | expect(promise).to.eventually.have.property("hasPublicFacilities", true), 40 | expect(promise).to.eventually.have.property("hasLockerSystem", true), 41 | expect(promise).to.eventually.have.property("hasTaxiRank", true), 42 | expect(promise).to.eventually.have.property("hasWiFi", true), 43 | expect(promise).to.eventually.have.property("hasTravelCenter", true), 44 | expect(promise).to.eventually.have.property("hasRailwayMission", true), 45 | expect(promise).to.eventually.have.property("hasDBLounge", false), 46 | expect(promise).to.eventually.have.property("hasLostAndFound", true), 47 | expect(promise).to.eventually.have.property("hasCarRental", false), 48 | expect(promise).to.eventually.have.property("hasTravelNecessities", true), 49 | expect(promise).to.eventually.have.property("hasSteplessAccess", "yes"), 50 | expect(promise).to.eventually.have.property("hasMobilityService", "Ja, um Voranmeldung unter 01806 512 512 wird gebeten"), 51 | expect(promise).to.eventually.have.property("hasSteamPermission", true), 52 | expect(promise).to.eventually.have.deep.property("location", {latitude: 50.7678, longitude: 6.091499}), 53 | expect(promise).to.eventually.have.deep.property("regionalArea", {name: "RB West", number: 4, shortName: "RB W" }), 54 | expect(promise).to.eventually.have.deep.property("mailingAddress", { city: "Aachen", zipcode: "52064", street: "Bahnhofplatz 2a" }), 55 | expect(promise).to.eventually.have.deep.property("regionalArea", { number: 4, name: "RB West", shortName: "RB W" }), 56 | // expect(promise).to.eventually.deep.include("DBInformationOpeningTimes", {}), 57 | // expect(promise).to.eventually.deep.include("localServiceStaffAvailability", {}), 58 | expect(promise).to.eventually.have.deep.property("aufgabentraeger", {shortName: "NVR", name: "Zweckverband Nahverkehr Rheinland GmbH", phoneNumber: undefined, number: undefined, email: undefined}), 59 | expect(promise).to.eventually.have.deep.property("szentrale", {shortName: undefined, name: "Duisburg Hbf", phoneNumber: "0203/30171055", number: 15, email: undefined}), 60 | expect(promise).to.eventually.have.deep.property("stationManagement", {shortName: undefined, name: "Düsseldorf", phoneNumber: undefined, number: 45, email: undefined}) 61 | 62 | ]); 63 | }); 64 | 65 | it('stationByEvaId should return null', () => { 66 | stationLoaderMock.result = null 67 | let promise = stationService.stationByEvaId(1) 68 | return expect(promise).to.eventually.become(null); 69 | }) 70 | 71 | it('stationByBahnhofsnummer should return valid station', () => { 72 | stationLoaderMock.result = require('./SingleStationMockResult') 73 | let promise = stationService.stationByBahnhofsnummer(1) 74 | expect(stationLoaderMock.stationNumber).to.equal(1) 75 | return Promise.all([ 76 | expect(promise).to.eventually.have.property("primaryEvaId", 8000001), 77 | expect(promise).to.eventually.have.property("stationNumber", 1), 78 | expect(promise).to.eventually.have.property("primaryRil100", "KA"), 79 | expect(promise).to.eventually.have.property("name", "Aachen Hbf"), 80 | expect(promise).to.eventually.have.property("category", 2), 81 | expect(promise).to.eventually.have.property("hasParking", true), 82 | expect(promise).to.eventually.have.property("hasBicycleParking", true), 83 | expect(promise).to.eventually.have.property("hasLocalPublicTransport", true), 84 | expect(promise).to.eventually.have.property("hasPublicFacilities", true), 85 | expect(promise).to.eventually.have.property("hasLockerSystem", true), 86 | expect(promise).to.eventually.have.property("hasTaxiRank", true), 87 | expect(promise).to.eventually.have.property("hasTravelNecessities", true), 88 | expect(promise).to.eventually.have.property("hasSteplessAccess", "yes"), 89 | expect(promise).to.eventually.have.property("hasMobilityService", "Ja, um Voranmeldung unter 01806 512 512 wird gebeten"), 90 | expect(promise).to.eventually.have.property("hasSteamPermission", true), 91 | expect(promise).to.eventually.have.deep.property("location", {latitude: 50.7678, longitude: 6.091499}), 92 | expect(promise).to.eventually.have.deep.property("regionalArea", {name: "RB West", number: 4, shortName: "RB W" }), 93 | expect(promise).to.eventually.have.deep.property("mailingAddress", { city: "Aachen", zipcode: "52064", street: "Bahnhofplatz 2a" }), 94 | expect(promise).to.eventually.have.deep.property("regionalArea", { number: 4, name: "RB West", shortName: "RB W" }), 95 | // expect(promise).to.eventually.deep.include("DBInformationOpeningTimes", {}), 96 | // expect(promise).to.eventually.deep.include("localServiceStaffAvailability", {}), 97 | expect(promise).to.eventually.have.deep.property("aufgabentraeger", {shortName: "NVR", name: "Zweckverband Nahverkehr Rheinland GmbH", phoneNumber: undefined, number: undefined, email: undefined}), 98 | expect(promise).to.eventually.have.deep.property("szentrale", {shortName: undefined, name: "Duisburg Hbf", phoneNumber: "0203/30171055", number: 15, email: undefined}), 99 | expect(promise).to.eventually.have.deep.property("stationManagement", {shortName: undefined, name: "Düsseldorf", phoneNumber: undefined, number: 45, email: undefined}) 100 | 101 | ]); 102 | }) 103 | 104 | it('stationByBahnhofsnummer should return valid station', () => { 105 | stationLoaderMock.result = require('./SingleStationMockResult') 106 | let promise = stationService.stationByRil100("KA") 107 | expect(stationLoaderMock.rill100).to.equal("KA") 108 | return Promise.all([ 109 | expect(promise).to.eventually.have.property("primaryEvaId", 8000001), 110 | expect(promise).to.eventually.have.property("stationNumber", 1), 111 | expect(promise).to.eventually.have.property("primaryRil100", "KA"), 112 | expect(promise).to.eventually.have.property("name", "Aachen Hbf"), 113 | expect(promise).to.eventually.have.property("category", 2), 114 | expect(promise).to.eventually.have.property("hasParking", true), 115 | expect(promise).to.eventually.have.property("hasBicycleParking", true), 116 | expect(promise).to.eventually.have.property("hasLocalPublicTransport", true), 117 | expect(promise).to.eventually.have.property("hasPublicFacilities", true), 118 | expect(promise).to.eventually.have.property("hasLockerSystem", true), 119 | expect(promise).to.eventually.have.property("hasTaxiRank", true), 120 | expect(promise).to.eventually.have.property("hasTravelNecessities", true), 121 | expect(promise).to.eventually.have.property("hasSteplessAccess", "yes"), 122 | expect(promise).to.eventually.have.property("hasMobilityService", "Ja, um Voranmeldung unter 01806 512 512 wird gebeten"), 123 | expect(promise).to.eventually.have.property("hasSteamPermission", true), 124 | expect(promise).to.eventually.have.deep.property("location", {latitude: 50.7678, longitude: 6.091499}), 125 | expect(promise).to.eventually.have.deep.property("regionalArea", {name: "RB West", number: 4, shortName: "RB W" }), 126 | expect(promise).to.eventually.have.deep.property("mailingAddress", { city: "Aachen", zipcode: "52064", street: "Bahnhofplatz 2a" }), 127 | expect(promise).to.eventually.have.deep.property("regionalArea", { number: 4, name: "RB West", shortName: "RB W" }), 128 | // expect(promise).to.eventually.deep.include("DBInformationOpeningTimes", {}), 129 | // expect(promise).to.eventually.deep.include("localServiceStaffAvailability", {}), 130 | expect(promise).to.eventually.have.deep.property("aufgabentraeger", {shortName: "NVR", name: "Zweckverband Nahverkehr Rheinland GmbH", phoneNumber: undefined, number: undefined, email: undefined}), 131 | expect(promise).to.eventually.have.deep.property("szentrale", {shortName: undefined, name: "Duisburg Hbf", phoneNumber: "0203/30171055", number: 15, email: undefined}), 132 | expect(promise).to.eventually.have.deep.property("stationManagement", {shortName: undefined, name: "Düsseldorf", phoneNumber: undefined, number: 45, email: undefined}) 133 | 134 | ]); 135 | }) 136 | 137 | it('stationByBahnhofsnummer should return null', () => { 138 | stationLoaderMock.result = null 139 | let promise = stationService.stationByBahnhofsnummer(1) 140 | return expect(promise).to.eventually.become(null); 141 | }) 142 | 143 | it('searchStations should return array with a station', () => { 144 | stationLoaderMock.result = [require('./SingleStationMockResult')] 145 | let promise = stationService.searchStations("Aachen") 146 | expect(stationLoaderMock.searchTerm).to.equal("Aachen") 147 | promise = promise.then((stations) => stations[0]) 148 | return Promise.all([ 149 | expect(promise).to.eventually.have.property("primaryEvaId", 8000001), 150 | expect(promise).to.eventually.have.property("stationNumber", 1), 151 | expect(promise).to.eventually.have.property("primaryRil100", "KA"), 152 | expect(promise).to.eventually.have.property("name", "Aachen Hbf"), 153 | expect(promise).to.eventually.have.property("category", 2), 154 | expect(promise).to.eventually.have.property("hasParking", true), 155 | expect(promise).to.eventually.have.property("hasBicycleParking", true), 156 | expect(promise).to.eventually.have.property("hasLocalPublicTransport", true), 157 | expect(promise).to.eventually.have.property("hasPublicFacilities", true), 158 | expect(promise).to.eventually.have.property("hasLockerSystem", true), 159 | expect(promise).to.eventually.have.property("hasTaxiRank", true), 160 | expect(promise).to.eventually.have.property("hasTravelNecessities", true), 161 | expect(promise).to.eventually.have.property("hasSteplessAccess", "yes"), 162 | expect(promise).to.eventually.have.property("hasMobilityService", "Ja, um Voranmeldung unter 01806 512 512 wird gebeten"), 163 | expect(promise).to.eventually.have.property("hasSteamPermission", true), 164 | expect(promise).to.eventually.have.deep.property("location", {latitude: 50.7678, longitude: 6.091499}), 165 | expect(promise).to.eventually.have.deep.property("regionalArea", {name: "RB West", number: 4, shortName: "RB W" }), 166 | expect(promise).to.eventually.have.deep.property("mailingAddress", { city: "Aachen", zipcode: "52064", street: "Bahnhofplatz 2a" }), 167 | expect(promise).to.eventually.have.deep.property("regionalArea", { number: 4, name: "RB West", shortName: "RB W" }), 168 | // expect(promise).to.eventually.deep.include("DBInformationOpeningTimes", {}), 169 | // expect(promise).to.eventually.deep.include("localServiceStaffAvailability", {}), 170 | expect(promise).to.eventually.have.deep.property("aufgabentraeger", {shortName: "NVR", name: "Zweckverband Nahverkehr Rheinland GmbH", phoneNumber: undefined, number: undefined, email: undefined}), 171 | expect(promise).to.eventually.have.deep.property("szentrale", {shortName: undefined, name: "Duisburg Hbf", phoneNumber: "0203/30171055", number: 15, email: undefined}), 172 | expect(promise).to.eventually.have.deep.property("stationManagement", {shortName: undefined, name: "Düsseldorf", phoneNumber: undefined, number: 45, email: undefined}) 173 | ]); 174 | }) 175 | 176 | }); 177 | -------------------------------------------------------------------------------- /test/StationPicture/StationPictureLoaderMock.js: -------------------------------------------------------------------------------- 1 | class StationPictureLoaderMock { 2 | constructor(result) { 3 | this.result = result; 4 | } 5 | 6 | stationPictureForStationNumber(stationNumber) { 7 | this.stationNumber = stationNumber; 8 | return Promise.resolve(this.result); 9 | } 10 | } 11 | 12 | module.exports = StationPictureLoaderMock; 13 | -------------------------------------------------------------------------------- /test/StationPicture/StationPictureMockResult.json: -------------------------------------------------------------------------------- 1 | { 2 | "DS100": "FFU", 3 | "country": "de", 4 | "id": 1973, 5 | "lat": 50.5547372607544, 6 | "license": "CC0 1.0 Universell (CC0 1.0)", 7 | "lon": 9.6843855869764, 8 | "photoUrl": "https://railway-stations.org/sites/default/files/previewbig/1973.jpg", 9 | "photographer": "@storchp", 10 | "photographerUrl": "https://railway-stations.org/node/40", 11 | "title": "Fulda" 12 | } -------------------------------------------------------------------------------- /test/StationPicture/StationPicturesServiceTest.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true */ 2 | /* eslint-env mocha */ 3 | 4 | const chai = require('chai'); 5 | const chaiAsPromised = require("chai-as-promised"); 6 | chai.use(chaiAsPromised); 7 | const expect = chai.expect; 8 | 9 | const StationPictureLoaderMock = require('./StationPictureLoaderMock'); 10 | const StationPictureService = require('./../../StationPicture/StationPictureService'); 11 | 12 | 13 | describe('StationPictureService', () => { 14 | var stationPictureLoaderMock = new StationPictureLoaderMock(); 15 | let stationPictureServcie = new StationPictureService(stationPictureLoaderMock); 16 | 17 | beforeEach(function() { 18 | stationPictureLoaderMock = new StationPictureLoaderMock(); 19 | stationPictureServcie = new StationPictureService(stationPictureLoaderMock); 20 | }); 21 | 22 | it('stationPictureForStationNumber should return valid station', () => { 23 | stationPictureLoaderMock.result = require('./StationPictureMockResult'); 24 | let promise = stationPictureServcie.stationPictureForStationNumber(1) 25 | 26 | return Promise.all([ 27 | expect(promise).to.eventually.have.property("id", 1973), 28 | expect(promise).to.eventually.have.property("url", 'https://railway-stations.org/sites/default/files/previewbig/1973.jpg'), 29 | expect(promise).to.eventually.have.property("license", 'CC0 1.0 Universell (CC0 1.0)'), 30 | expect(promise).to.eventually.have.deep.property("photographer", { name: '@storchp', url: 'https://railway-stations.org/node/40' }) 31 | 32 | ]); 33 | }); 34 | 35 | 36 | }); 37 | --------------------------------------------------------------------------------