From 4379b4d231fcf0a34263e0fa209cbaf904305091 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sat, 8 Feb 2020 19:59:51 +0100 Subject: [PATCH 01/10] Complete backend --- Instructions.txt | 17 - api/models/citiesModel.js | 22 + {models => api/models}/countriesModel.js | 2 +- api/models/roadsModel.js | 9 + api/routes/citiesRouter.js | 14 + api/routes/countriesRouter.js | 14 + api/routes/pathRouter.js | 40 ++ api/routes/roadsRouter.js | 13 + api/server.js | 17 +- data/test_data/cities.js | 50 ++ data/test_data/roads.js | 84 +++ helpers/dijkstra_algo.js | 55 ++ helpers/graph.js | 18 + helpers/queue.js | 21 + helpers/test_dijkstra.js | 18 + models/citiesModel.js | 0 models/roadsModel.js | 0 package-lock.json | 807 ++++++++++++++++++++++- package.json | 6 +- 19 files changed, 1178 insertions(+), 29 deletions(-) delete mode 100644 Instructions.txt create mode 100644 api/models/citiesModel.js rename {models => api/models}/countriesModel.js (81%) create mode 100644 api/models/roadsModel.js create mode 100644 api/routes/citiesRouter.js create mode 100644 api/routes/countriesRouter.js create mode 100644 api/routes/pathRouter.js create mode 100644 api/routes/roadsRouter.js create mode 100644 data/test_data/cities.js create mode 100644 data/test_data/roads.js create mode 100644 helpers/dijkstra_algo.js create mode 100644 helpers/graph.js create mode 100644 helpers/queue.js create mode 100644 helpers/test_dijkstra.js delete mode 100644 models/citiesModel.js delete mode 100644 models/roadsModel.js diff --git a/Instructions.txt b/Instructions.txt deleted file mode 100644 index 65bebd6..0000000 --- a/Instructions.txt +++ /dev/null @@ -1,17 +0,0 @@ -# start - -By creating a new web app that you will call Dijkstra, I would like you to: - -- Find a DB modelization that allows you to store any country given its cities and its roads between them (see Belgian graph); - -- Store the Belgian data (see belgium.rb); - -- Allow a user to select Belgium as a country (or if he/she navigates to /countries/belgium), then allow to select a starting point (a Belgian city) and a destination (also a Belgian city); - -- Once the user has selected the starting point and the destination, tell the shortest distance between those two cities; - -- Describe to the user the path (for example: Bruges -> Ghent -> Brussels -> Liège) that corresponds to the shortest distance you returned to the user; - -- Make your code shines! - -# end \ No newline at end of file diff --git a/api/models/citiesModel.js b/api/models/citiesModel.js new file mode 100644 index 0000000..1072e35 --- /dev/null +++ b/api/models/citiesModel.js @@ -0,0 +1,22 @@ +const db = require('../../data/dbConfig'); + +module.exports = { + getCities, + getCitiesByCountry + // getCity, +}; + +function getCities() { + return db('cities') +} + +function getCitiesByCountry(country_id) { + return db('cities') + .where({ country_id }) +} + +// function getCity(name) { +// return db('cities') +// .where({ name }) +// .first() +// } \ No newline at end of file diff --git a/models/countriesModel.js b/api/models/countriesModel.js similarity index 81% rename from models/countriesModel.js rename to api/models/countriesModel.js index f29f60a..919de9d 100644 --- a/models/countriesModel.js +++ b/api/models/countriesModel.js @@ -1,4 +1,4 @@ -const db = require('../data/dbConfig'); +const db = require('../../data/dbConfig'); module.exports = { getCountries, diff --git a/api/models/roadsModel.js b/api/models/roadsModel.js new file mode 100644 index 0000000..cbff16b --- /dev/null +++ b/api/models/roadsModel.js @@ -0,0 +1,9 @@ +const db = require('../../data/dbConfig'); + +module.exports = { + getRoads +}; + +function getRoads() { + return db('roads') +} \ No newline at end of file diff --git a/api/routes/citiesRouter.js b/api/routes/citiesRouter.js new file mode 100644 index 0000000..6ac07ed --- /dev/null +++ b/api/routes/citiesRouter.js @@ -0,0 +1,14 @@ +const router = require('express').Router(); + +const Cities = require('../models/citiesModel'); + +router.get('', async (req, res) => { + try { + const cities = await Cities.getCities(); + res.status(200).json(cities); + } catch (e) { + res.status(500).json(e); + } +}) + +module.exports = router; diff --git a/api/routes/countriesRouter.js b/api/routes/countriesRouter.js new file mode 100644 index 0000000..ea0a831 --- /dev/null +++ b/api/routes/countriesRouter.js @@ -0,0 +1,14 @@ +const router = require('express').Router(); +const Cities = require('../models/citiesModel'); + +router.get('/:country_id', async (req, res) => { + const { country_id } = req.params; + try { + const cities = await Cities.getCitiesByCountry(country_id); + res.status(200).json(cities); + } catch (e) { + res.status(500).json(e); + } +}) + +module.exports = router; diff --git a/api/routes/pathRouter.js b/api/routes/pathRouter.js new file mode 100644 index 0000000..1cad8e3 --- /dev/null +++ b/api/routes/pathRouter.js @@ -0,0 +1,40 @@ +const router = require('express').Router(); + +const Roads = require('../models/roadsModel'); +const Cities = require('../models/citiesModel'); + +const searchPath = require('../../helpers/dijkstra_algo'); + +router.get('', async (req, res) => { + const { start_city_id, end_city_id } = req.body; + + try { + const cities = await Cities.getCities(); + const roads = await Roads.getRoads(); + + let { path, distance } = searchPath(cities, roads, start_city_id, end_city_id); + const formatedPath = formatPath(path, cities) + + let response = { path: formatedPath, distance } + + res.status(200).json(response); + } catch (e) { + res.status(500).json(e); + } +}) + +function formatPath(path, cities) { + const complete_path = [] + + for (let path_city of path) { + for (let city of cities) { + if (city.id == path_city) { + complete_path.push({ id: city.id, name: city.name }); + } + } + } + + return complete_path +} + +module.exports = router; diff --git a/api/routes/roadsRouter.js b/api/routes/roadsRouter.js new file mode 100644 index 0000000..591c98f --- /dev/null +++ b/api/routes/roadsRouter.js @@ -0,0 +1,13 @@ +const router = require('express').Router(); +const Roads = require('../models/roadsModel'); + +router.get('', async (req, res) => { + try { + const roads = await Roads.getRoads(); + res.status(200).json(roads); + } catch (e) { + res.status(500).json(e); + } +}) + +module.exports = router; diff --git a/api/server.js b/api/server.js index 955c363..a2d037a 100644 --- a/api/server.js +++ b/api/server.js @@ -1,17 +1,18 @@ const express = require("express"); -// const authRouter = require('../services/auth/authRouter'); -// const usersRouter = require('../services/users/usersRouter'); -// const sessionsRouter = require('../services/sessions/sessionsRouter'); -// const dailyAveragesRouter = require('../services/dailyAverages/dailyAveragesRouter'); +const citiesRouter = require('./routes/citiesRouter'); +const roadsRouter = require('./routes/roadsRouter'); +const countriesRouter = require('./routes/countriesRouter'); +const pathRouter = require('./routes/pathRouter') + const server = express(); server.use(express.json()); -// server.use('/api/auth', authRouter); -// server.use('/api/users', authenticate, usersRouter); -// server.use('/api/users', authenticate, sessionsRouter); -// server.use('/api/users', authenticate, dailyAveragesRouter); +server.use('/api/cities', citiesRouter); +server.use('/api/roads', roadsRouter); +server.use('/api/countries', countriesRouter); +server.use('/api/path', pathRouter); module.exports = server; diff --git a/data/test_data/cities.js b/data/test_data/cities.js new file mode 100644 index 0000000..214f0c4 --- /dev/null +++ b/data/test_data/cities.js @@ -0,0 +1,50 @@ +module.exports = [{ + "id": 1, + "name": 'bruges', + "country_id": 1 +}, +{ + "id": 2, + "name": 'antwerp', + "country_id": 1 +}, +{ + "id": 3, + "name": 'ghent', + "country_id": 1 +}, +{ + "id": 4, + "name": 'mechelen', + "country_id": 1 +}, +{ + "id": 5, + "name": 'brussels', + "country_id": 1 +}, +{ + "id": 6, + "name": 'mons', + "country_id": 1 +}, +{ + "id": 7, + "name": 'namur', + "country_id": 1 +}, +{ + "id": 8, + "name": 'liege', + "country_id": 1 +}, +{ + "id": 9, + "name": 'arlon', + "country_id": 1 +}, +{ + "id": 10, + "name": 'tournai', + "country_id": 1 +}] \ No newline at end of file diff --git a/data/test_data/roads.js b/data/test_data/roads.js new file mode 100644 index 0000000..9cd47eb --- /dev/null +++ b/data/test_data/roads.js @@ -0,0 +1,84 @@ +module.exports = [{ +"id": 1, +"start_city_id": 1, +"end_city_id": 3, +"distance": 50 +}, +{ +"id": 2, +"start_city_id": 3, +"end_city_id": 10, +"distance": 80 +}, +{ +"id": 3, +"start_city_id": 10, +"end_city_id": 5, +"distance": 89 +}, +{ +"id": 4, +"start_city_id": 3, +"end_city_id": 5, +"distance": 56 +}, +{ +"id": 5, +"start_city_id": 3, +"end_city_id": 2, +"distance": 60 +}, +{ +"id": 6, +"start_city_id": 2, +"end_city_id": 4, +"distance": 25 +}, +{ +"id": 7, +"start_city_id": 4, +"end_city_id": 5, +"distance": 27 +}, +{ +"id": 8, +"start_city_id": 5, +"end_city_id": 6, +"distance": 80 +}, +{ +"id": 9, +"start_city_id": 6, +"end_city_id": 7, +"distance": 91 +}, +{ +"id": 10, +"start_city_id": 6, +"end_city_id": 10, +"distance": 51 +}, +{ +"id": 11, +"start_city_id": 7, +"end_city_id": 9, +"distance": 129 +}, +{ +"id": 12, +"start_city_id": 9, +"end_city_id": 7, +"distance": 123 +}, +{ +"id": 13, +"start_city_id": 8, +"end_city_id": 7, +"distance": 65 +}, +{ +"id": 14, +"start_city_id": 8, +"end_city_id": 5, +"distance": 97 +}] \ No newline at end of file diff --git a/helpers/dijkstra_algo.js b/helpers/dijkstra_algo.js new file mode 100644 index 0000000..3048463 --- /dev/null +++ b/helpers/dijkstra_algo.js @@ -0,0 +1,55 @@ +const Queue = require('./queue'); +const Graph = require('./graph'); + +function findPathWithDijkstra(cities, roads, startNode, endNode) { + const graph = new Graph(); + + // Add cities and roads to graph + for (let city of cities) { + graph.addNode(city.id) + } + + for (let { start_city_id, end_city_id, distance } of roads) { + graph.addEdge(start_city_id, end_city_id, distance) + } + + // Dijkstra path search algo + let times = {}; + let backtrace = {}; + let queue = new Queue(); + + times[startNode] = 0; + + graph.nodes.forEach(node => { + if (node !== startNode) { + times[node] = Infinity + } + }); + + queue.enqueue([startNode, 0]); + + while (queue.size()) { + let shortestStep = queue.dequeue(); + let currentNode = shortestStep[0]; + graph.adjacencyList[currentNode].forEach(neighbor => { + let time = times[currentNode] + neighbor.distance; + + if (time < times[neighbor.node]) { + times[neighbor.node] = time; + backtrace[neighbor.node] = currentNode; + queue.enqueue([neighbor.node, time]); + } + }); + } + + let path = [endNode]; + let lastStep = endNode; while(lastStep !== startNode) { + path.unshift(backtrace[lastStep]) + lastStep = backtrace[lastStep] + } + + // return `Path is ${path} and time is ${times[endNode]}` + return {path, distance: times[endNode]} +} + +module.exports = findPathWithDijkstra; diff --git a/helpers/graph.js b/helpers/graph.js new file mode 100644 index 0000000..ee42b64 --- /dev/null +++ b/helpers/graph.js @@ -0,0 +1,18 @@ +class Graph { + constructor() { + this.nodes = []; + this.adjacencyList = {}; + } + + addNode(node) { + this.nodes.push(node); + this.adjacencyList[node] = []; + } + + addEdge(node1, node2, distance) { + this.adjacencyList[node1].push({node:node2, distance}); + this.adjacencyList[node2].push({node:node1, distance}); + } +} + +module.exports = Graph; \ No newline at end of file diff --git a/helpers/queue.js b/helpers/queue.js new file mode 100644 index 0000000..6d3335e --- /dev/null +++ b/helpers/queue.js @@ -0,0 +1,21 @@ +class Queue { + constructor() { + this.store = []; + } + + size() { + return this.store.length + } + + enqueue(node) { + this.store.push(node); + } + + dequeue() { + if (this.size() > 0) { + return this.store.shift(); + } + } +} + +module.exports = Queue; \ No newline at end of file diff --git a/helpers/test_dijkstra.js b/helpers/test_dijkstra.js new file mode 100644 index 0000000..f07b860 --- /dev/null +++ b/helpers/test_dijkstra.js @@ -0,0 +1,18 @@ +const searchPath = require('../helpers/dijkstra_algo'); +const cities = require('../data/test_data/cities'); +const roads = require('../data/test_data/roads'); + +let { path, distance} = searchPath(cities, roads, 1, 9); + +const formatedPath = []; + +// I didn't use map or filter because it doesn't preserve the order +for (let path_city of path) { + for (let city of cities) { + if (city.id == path_city) { + formatedPath.push({ id: city.id, name: city.name }); + } + } +} + +console.log(formatedPath) \ No newline at end of file diff --git a/models/citiesModel.js b/models/citiesModel.js deleted file mode 100644 index e69de29..0000000 diff --git a/models/roadsModel.js b/models/roadsModel.js deleted file mode 100644 index e69de29..0000000 diff --git a/package-lock.json b/package-lock.json index a9769a0..bd410d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,8 @@ { - "requires": true, + "name": "dijkstra", + "version": "1.0.0", "lockfileVersion": 1, + "requires": true, "dependencies": { "abbrev": { "version": "1.1.1", @@ -27,11 +29,72 @@ "uri-js": "^4.2.2" } }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -182,6 +245,12 @@ "tweetnacl": "^0.14.3" } }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -204,6 +273,54 @@ "type-is": "~1.6.17" } }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -261,16 +378,96 @@ "unset-value": "^1.0.0" } }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "chownr": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==" }, + "ci-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -292,6 +489,12 @@ } } }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -306,6 +509,21 @@ "object-visit": "^1.0.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "colorette": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz", @@ -334,6 +552,20 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -372,6 +604,32 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -465,6 +723,21 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -489,6 +762,12 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, "esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -499,6 +778,21 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -796,6 +1090,13 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -811,6 +1112,12 @@ "wide-align": "^1.1.0" } }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -842,6 +1149,24 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -864,6 +1189,31 @@ "which": "^1.2.14" } }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -878,6 +1228,12 @@ "har-schema": "^2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -950,6 +1306,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, "ignore-walk": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", @@ -958,6 +1320,18 @@ "minimatch": "^3.0.4" } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1014,11 +1388,29 @@ } } }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, + "is-ci": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "dev": true, + "requires": { + "ci-info": "^1.5.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -1080,6 +1472,22 @@ "is-extglob": "^2.1.1" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -1098,6 +1506,21 @@ } } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -1106,6 +1529,12 @@ "isobject": "^3.0.1" } }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", @@ -1114,6 +1543,18 @@ "is-unc-path": "^1.0.0" } }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1231,6 +1672,15 @@ } } }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -1251,6 +1701,39 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + }, + "dependencies": { + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -1462,6 +1945,41 @@ "resolved": "https://registry.npmjs.org/nodejs/-/nodejs-0.0.0.tgz", "integrity": "sha1-RyL6LhisTrc6Qq4W0B41hKErdTE=" }, + "nodemon": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz", + "integrity": "sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw==", + "dev": true, + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.5.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "nopt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", @@ -1471,6 +1989,12 @@ "osenv": "^0.1.4" } }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, "npm-bundled": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", @@ -1494,6 +2018,15 @@ "npm-normalize-package-bin": "^1.0.1" } }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -1619,6 +2152,24 @@ "os-tmpdir": "^1.0.0" } }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, "parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -1649,6 +2200,18 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -1682,11 +2245,29 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.1.0.tgz", "integrity": "sha512-bhlV7Eq09JrRIvo1eKngpwuqKtJnNhZdpdOlvrPrA4dxqXPjxSrbNrfnIDmTpwMyRszrcV4kU5ZA4mMsQUrjdg==" }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1701,11 +2282,23 @@ "ipaddr.js": "1.9.0" } }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "psl": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, + "pstree.remy": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", + "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -1764,6 +2357,15 @@ "util-deprecate": "~1.0.1" } }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.7" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -1781,6 +2383,25 @@ "safe-regex": "^1.1.0" } }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", @@ -1888,6 +2509,15 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, "send": { "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", @@ -1957,6 +2587,21 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -2165,11 +2810,26 @@ "ansi-regex": "^2.0.0" } }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, "tar": { "version": "4.4.13", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", @@ -2189,11 +2849,26 @@ "resolved": "https://registry.npmjs.org/tarn/-/tarn-2.0.0.tgz", "integrity": "sha512-7rNMCZd3s9bhQh47ksAQd92ADFcJUjjbyOvyFjNLwTPpGieFHMC84S+LOzw0fx1uh6hnDz/19r8CPMnIjJlMMA==" }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, "tildify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==" }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -2237,6 +2912,26 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -2280,6 +2975,15 @@ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -2291,6 +2995,15 @@ "set-value": "^2.0.1" } }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2332,6 +3045,30 @@ } } }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -2345,6 +3082,15 @@ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -2404,11 +3150,70 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "dev": true, + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index dfc5359..5c85787 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "nodejs": "^0.0.0", "sqlite3": "^4.1.1" }, - "devDependencies": {}, + "devDependencies": { + "nodemon": "^2.0.2" + }, "scripts": { - "test": "test", + "watch": "nodemon index.js", "start": "node index.js" }, "author": "", From 35e2dcb8da6ffff0861a9c3dfb7ffa37cef63041 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sat, 8 Feb 2020 20:50:22 +0100 Subject: [PATCH 02/10] Add Readme --- README.md | 122 ++++++++++++++++++++++++++++++++++++++++ data/seeds/02-cities.js | 8 +-- index.js | 2 +- 3 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa01398 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# dijkstra-backend + +Dijkstra is an app thas uses Dijkstra algorithm to display the shortest path between different cities in Belgium. + +Deployed backend: https://dijkstra-rui.herokuapp.com/ + +## API Documentation +___ +> Values required in **`bold`**. + +### COUNTRIES | **`countries`** +| field | data type | metadata | +| :--------| :---------------- | :-------------------------------------------------- | +| id | unsigned integer | primary key, auto-increments, generated by database | +| name | string | | + +#### Get countries list +**`GET /api/countries`** + +##### Response +A json object with the `name` and `id`. + +``` +{ + "id": 1, + "name": "Belgium" +} +``` + +### CITIES | **`cities`** +| field | data type | metadata | +| :--------| :--------------------- | :-------------------------------------------------- | +| id | unsigned integer | primary key, auto-increments, generated by database | +| name | string | | +| integer | unsigned integer | foreign key referencing `countries.id` | + +#### Get cities list +**`GET /api/cities`** + +##### Response +A json object with `id`, `name` and `country_id`. + +``` +{ + "id": 1, + "name": 'bruges', + "country_id": 1 +} +``` + +### ROADS| **`roads`** +| field | data type | metadata | +| :-------------| :--------------- | :-------------------------------------------------- | +| id | unsigned integer | primary key, auto-increments, generated by database | +| start_city_id | string | required | +| end_city_id | string | required | +| distance | unsigned integer | | + +#### Get roads list +**`GET /api/roads`** + +##### Response +A json object with `id`, `start_city_id`, 'end_city_id` and `distance`. + +``` +{ + "id": 1, + "start_city_id": 1, + "end_city_id": 3, + "distance": 50 +} +``` + +### Shortest Path | **`cities`** + +#### Get the shortest path between two cities and the total distance +**`GET /api/path`** + +##### Request +A json object with **`start_city_id`** and **`end_city_id`**. + +``` +{ + "start_city_id": 2, + "end_city_id": 9 +} +``` + +##### Response +A json object composed of the `path` (in the correct order) and the total `distance`. + +``` +{ + "path": [ + { + "id": 2, + "name": "antwerp" + }, + { + "id": 4, + "name": "mechelen" + }, + { + "id": 5, + "name": "brussels" + }, + { + "id": 8, + "name": "liege" + }, + { + "id": 7, + "name": "namur" + }, + { + "id": 9, + "name": "arlon" + } + ], + "distance": 337 +} +``` \ No newline at end of file diff --git a/data/seeds/02-cities.js b/data/seeds/02-cities.js index 3b4af50..0532481 100644 --- a/data/seeds/02-cities.js +++ b/data/seeds/02-cities.js @@ -3,10 +3,10 @@ exports.seed = function(knex) { return knex('cities').truncate() .then(function () { return knex('cities').insert([ - { - "id": 1, - "name": 'bruges', - "country_id": 1 + { + "id": 1, + "name": 'bruges', + "country_id": 1 }, { "id": 2, diff --git a/index.js b/index.js index ca3cb87..8ea6315 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ const server = require("./api/server"); server.get("/", (req, res) => { res.json({ message: `API server for Dijkstra. :)`, - documentation: `Coming` + documentation: `Check out: https://github.com/ruihildt/dijkstra/tree/completed-backend` }); }); From 51e4989174d23b83a95624faf6db04c8b19568a2 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sat, 8 Feb 2020 20:58:55 +0100 Subject: [PATCH 03/10] Correct documentation --- README.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index aa01398..4e281f4 100644 --- a/README.md +++ b/README.md @@ -15,24 +15,37 @@ ___ | name | string | | #### Get countries list -**`GET /api/countries`** +**`GET /api/countries/:country_id`** ##### Response -A json object with the `name` and `id`. +A json object with the `name` and `id` and `country_id`. ``` -{ - "id": 1, - "name": "Belgium" -} +[ + { + "id": 1, + "name": "bruges", + "country_id": 1 + }, + { + "id": 2, + "name": "antwerp", + "country_id": 1 + }, + { + "id": 3, + "name": "ghent", + "country_id": 1 + }, +] ``` ### CITIES | **`cities`** -| field | data type | metadata | -| :--------| :--------------------- | :-------------------------------------------------- | -| id | unsigned integer | primary key, auto-increments, generated by database | -| name | string | | -| integer | unsigned integer | foreign key referencing `countries.id` | +| field | data type | metadata | +| :----------| :--------------------- | :-------------------------------------------------- | +| id | unsigned integer | primary key, auto-increments, generated by database | +| name | string | | +| country_id | unsigned integer | foreign key referencing `countries.id` | #### Get cities list **`GET /api/cities`** From ffdbe8c06b27353cf317e12bd0e78e9459fed9f7 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sat, 8 Feb 2020 20:59:53 +0100 Subject: [PATCH 04/10] Update documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e281f4..ea349aa 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ___ **`GET /api/countries/:country_id`** ##### Response -A json object with the `name` and `id` and `country_id`. +A json object with a list of cities in the selected country `name` and `id` and `country_id`. ``` [ From a660140f498396512f094ee562bb158f859a6adb Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sat, 8 Feb 2020 21:09:36 +0100 Subject: [PATCH 05/10] Remove unused files --- data/test_data/cities.js | 50 ------------------------ data/test_data/roads.js | 84 ---------------------------------------- helpers/test_dijkstra.js | 18 --------- 3 files changed, 152 deletions(-) delete mode 100644 data/test_data/cities.js delete mode 100644 data/test_data/roads.js delete mode 100644 helpers/test_dijkstra.js diff --git a/data/test_data/cities.js b/data/test_data/cities.js deleted file mode 100644 index 214f0c4..0000000 --- a/data/test_data/cities.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = [{ - "id": 1, - "name": 'bruges', - "country_id": 1 -}, -{ - "id": 2, - "name": 'antwerp', - "country_id": 1 -}, -{ - "id": 3, - "name": 'ghent', - "country_id": 1 -}, -{ - "id": 4, - "name": 'mechelen', - "country_id": 1 -}, -{ - "id": 5, - "name": 'brussels', - "country_id": 1 -}, -{ - "id": 6, - "name": 'mons', - "country_id": 1 -}, -{ - "id": 7, - "name": 'namur', - "country_id": 1 -}, -{ - "id": 8, - "name": 'liege', - "country_id": 1 -}, -{ - "id": 9, - "name": 'arlon', - "country_id": 1 -}, -{ - "id": 10, - "name": 'tournai', - "country_id": 1 -}] \ No newline at end of file diff --git a/data/test_data/roads.js b/data/test_data/roads.js deleted file mode 100644 index 9cd47eb..0000000 --- a/data/test_data/roads.js +++ /dev/null @@ -1,84 +0,0 @@ -module.exports = [{ -"id": 1, -"start_city_id": 1, -"end_city_id": 3, -"distance": 50 -}, -{ -"id": 2, -"start_city_id": 3, -"end_city_id": 10, -"distance": 80 -}, -{ -"id": 3, -"start_city_id": 10, -"end_city_id": 5, -"distance": 89 -}, -{ -"id": 4, -"start_city_id": 3, -"end_city_id": 5, -"distance": 56 -}, -{ -"id": 5, -"start_city_id": 3, -"end_city_id": 2, -"distance": 60 -}, -{ -"id": 6, -"start_city_id": 2, -"end_city_id": 4, -"distance": 25 -}, -{ -"id": 7, -"start_city_id": 4, -"end_city_id": 5, -"distance": 27 -}, -{ -"id": 8, -"start_city_id": 5, -"end_city_id": 6, -"distance": 80 -}, -{ -"id": 9, -"start_city_id": 6, -"end_city_id": 7, -"distance": 91 -}, -{ -"id": 10, -"start_city_id": 6, -"end_city_id": 10, -"distance": 51 -}, -{ -"id": 11, -"start_city_id": 7, -"end_city_id": 9, -"distance": 129 -}, -{ -"id": 12, -"start_city_id": 9, -"end_city_id": 7, -"distance": 123 -}, -{ -"id": 13, -"start_city_id": 8, -"end_city_id": 7, -"distance": 65 -}, -{ -"id": 14, -"start_city_id": 8, -"end_city_id": 5, -"distance": 97 -}] \ No newline at end of file diff --git a/helpers/test_dijkstra.js b/helpers/test_dijkstra.js deleted file mode 100644 index f07b860..0000000 --- a/helpers/test_dijkstra.js +++ /dev/null @@ -1,18 +0,0 @@ -const searchPath = require('../helpers/dijkstra_algo'); -const cities = require('../data/test_data/cities'); -const roads = require('../data/test_data/roads'); - -let { path, distance} = searchPath(cities, roads, 1, 9); - -const formatedPath = []; - -// I didn't use map or filter because it doesn't preserve the order -for (let path_city of path) { - for (let city of cities) { - if (city.id == path_city) { - formatedPath.push({ id: city.id, name: city.name }); - } - } -} - -console.log(formatedPath) \ No newline at end of file From f4c8b6e75c32d5a2a410824741330183b8a4fab9 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sun, 9 Feb 2020 11:59:23 +0100 Subject: [PATCH 06/10] Add package to allow CORS and helmet for security --- api/server.js | 5 +- package-lock.json | 144 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 150 insertions(+), 1 deletion(-) diff --git a/api/server.js b/api/server.js index a2d037a..6b28834 100644 --- a/api/server.js +++ b/api/server.js @@ -1,14 +1,17 @@ const express = require("express"); +const helmet = require('helmet'); +const cors = require('cors'); const citiesRouter = require('./routes/citiesRouter'); const roadsRouter = require('./routes/roadsRouter'); const countriesRouter = require('./routes/countriesRouter'); const pathRouter = require('./routes/pathRouter') - const server = express(); +server.use(helmet()); server.use(express.json()); +server.use(cors()); server.use('/api/cities', citiesRouter); server.use('/api/roads', roadsRouter); diff --git a/package-lock.json b/package-lock.json index bd410d2..f9ae435 100644 --- a/package-lock.json +++ b/package-lock.json @@ -273,6 +273,11 @@ "type-is": "~1.6.17" } }, + "bowser": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", + "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -384,6 +389,11 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -579,6 +589,11 @@ "safe-buffer": "5.1.2" } }, + "content-security-policy-builder": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", + "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -604,6 +619,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", @@ -638,6 +662,11 @@ "assert-plus": "^1.0.0" } }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -723,6 +752,16 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "dns-prefetch-control": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz", + "integrity": "sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q==" + }, + "dont-sniff-mimetype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", + "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -833,6 +872,11 @@ "homedir-polyfill": "^1.0.1" } }, + "expect-ct": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz", + "integrity": "sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==" + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -968,6 +1012,11 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "feature-policy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", + "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -1072,6 +1121,11 @@ "map-cache": "^0.2.2" } }, + "frameguard": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz", + "integrity": "sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==" + }, "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -1268,6 +1322,56 @@ } } }, + "helmet": { + "version": "3.21.2", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.21.2.tgz", + "integrity": "sha512-okUo+MeWgg00cKB8Csblu8EXgcIoDyb5ZS/3u0W4spCimeVuCUvVZ6Vj3O2VJ1Sxpyb8jCDvzu0L1KKT11pkIg==", + "requires": { + "depd": "2.0.0", + "dns-prefetch-control": "0.2.0", + "dont-sniff-mimetype": "1.1.0", + "expect-ct": "0.2.0", + "feature-policy": "0.3.0", + "frameguard": "3.1.0", + "helmet-crossdomain": "0.4.0", + "helmet-csp": "2.9.4", + "hide-powered-by": "1.1.0", + "hpkp": "2.0.0", + "hsts": "2.2.0", + "ienoopen": "1.1.0", + "nocache": "2.1.0", + "referrer-policy": "1.2.0", + "x-xss-protection": "1.3.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "helmet-crossdomain": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", + "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" + }, + "helmet-csp": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.9.4.tgz", + "integrity": "sha512-qUgGx8+yk7Xl8XFEGI4MFu1oNmulxhQVTlV8HP8tV3tpfslCs30OZz/9uQqsWPvDISiu/NwrrCowsZBhFADYqg==", + "requires": { + "bowser": "^2.7.0", + "camelize": "1.0.0", + "content-security-policy-builder": "2.1.0", + "dasherize": "2.0.0" + } + }, + "hide-powered-by": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", + "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -1276,6 +1380,26 @@ "parse-passwd": "^1.0.0" } }, + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hsts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", + "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "requires": { + "depd": "2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "http-errors": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", @@ -1306,6 +1430,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ienoopen": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.1.0.tgz", + "integrity": "sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==" + }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1923,6 +2052,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nocache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -2374,6 +2508,11 @@ "resolve": "^1.1.6" } }, + "referrer-policy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -3208,6 +3347,11 @@ "signal-exit": "^3.0.2" } }, + "x-xss-protection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", + "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/package.json b/package.json index 5c85787..d2c44c0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "", "main": "index.js", "dependencies": { + "cors": "^2.8.5", "express": "^4.17.1", + "helmet": "^3.21.2", "knex": "^0.20.8", "nodejs": "^0.0.0", "sqlite3": "^4.1.1" From b3c3663509944d681e4ec712b348d2882350fb97 Mon Sep 17 00:00:00 2001 From: rui Date: Sun, 9 Feb 2020 12:04:54 +0100 Subject: [PATCH 07/10] Update backend deployed url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea349aa..26574de 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Dijkstra is an app thas uses Dijkstra algorithm to display the shortest path between different cities in Belgium. -Deployed backend: https://dijkstra-rui.herokuapp.com/ +Deployed backend: https://dijkstra-backend.herokuapp.com/ ## API Documentation ___ @@ -132,4 +132,4 @@ A json object composed of the `path` (in the correct order) and the total `dista ], "distance": 337 } -``` \ No newline at end of file +``` From d3097c565fdba81efcbcc064bef823888e46c5e5 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sun, 9 Feb 2020 17:57:50 +0100 Subject: [PATCH 08/10] Use query strings to pass the start/destination --- api/routes/pathRouter.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/api/routes/pathRouter.js b/api/routes/pathRouter.js index 1cad8e3..7ab37d9 100644 --- a/api/routes/pathRouter.js +++ b/api/routes/pathRouter.js @@ -6,14 +6,20 @@ const Cities = require('../models/citiesModel'); const searchPath = require('../../helpers/dijkstra_algo'); router.get('', async (req, res) => { - const { start_city_id, end_city_id } = req.body; + const { start_city_id, end_city_id } = req.query; + console.log(typeof start_city_id) + + start = Math.floor(start_city_id); + end = Math.floor(end_city_id); + console.log(start, end) try { const cities = await Cities.getCities(); const roads = await Roads.getRoads(); - let { path, distance } = searchPath(cities, roads, start_city_id, end_city_id); + let { path, distance } = searchPath(cities, roads, start, end); const formatedPath = formatPath(path, cities) + console.log(path) let response = { path: formatedPath, distance } From b659af9cb4bae2c14cd449f02b5eee4855f2bcb4 Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sun, 9 Feb 2020 18:06:33 +0100 Subject: [PATCH 09/10] Update doc --- README.md | 17 +++++++---------- api/routes/pathRouter.js | 11 ++++------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 26574de..5be4179 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Deployed backend: https://dijkstra-backend.herokuapp.com/ ___ > Values required in **`bold`**. -### COUNTRIES | **`countries`** +### COUNTRIES **`countries`** | field | data type | metadata | | :--------| :---------------- | :-------------------------------------------------- | | id | unsigned integer | primary key, auto-increments, generated by database | @@ -40,7 +40,7 @@ A json object with a list of cities in the selected country `name` and `id` and ] ``` -### CITIES | **`cities`** +### CITIES **`cities`** | field | data type | metadata | | :----------| :--------------------- | :-------------------------------------------------- | | id | unsigned integer | primary key, auto-increments, generated by database | @@ -61,7 +61,7 @@ A json object with `id`, `name` and `country_id`. } ``` -### ROADS| **`roads`** +### ROADS **`roads`** | field | data type | metadata | | :-------------| :--------------- | :-------------------------------------------------- | | id | unsigned integer | primary key, auto-increments, generated by database | @@ -84,23 +84,20 @@ A json object with `id`, `start_city_id`, 'end_city_id` and `distance`. } ``` -### Shortest Path | **`cities`** +### Shortest Path **`cities`** #### Get the shortest path between two cities and the total distance **`GET /api/path`** ##### Request -A json object with **`start_city_id`** and **`end_city_id`**. +Add the **`start_city_id`** and **`end_city_id`** as query strings to the url. ``` -{ - "start_city_id": 2, - "end_city_id": 9 -} +/api/path?start_city_id=2?end_city_id=9 ``` ##### Response -A json object composed of the `path` (in the correct order) and the total `distance`. +A json object composed of the `path` correctly ordered and the total `distance`. ``` { diff --git a/api/routes/pathRouter.js b/api/routes/pathRouter.js index 7ab37d9..74342cc 100644 --- a/api/routes/pathRouter.js +++ b/api/routes/pathRouter.js @@ -3,25 +3,22 @@ const router = require('express').Router(); const Roads = require('../models/roadsModel'); const Cities = require('../models/citiesModel'); -const searchPath = require('../../helpers/dijkstra_algo'); +const findShortestPath = require('../../helpers/dijkstra_algo'); router.get('', async (req, res) => { const { start_city_id, end_city_id } = req.query; - console.log(typeof start_city_id) start = Math.floor(start_city_id); end = Math.floor(end_city_id); - console.log(start, end) try { const cities = await Cities.getCities(); const roads = await Roads.getRoads(); - let { path, distance } = searchPath(cities, roads, start, end); - const formatedPath = formatPath(path, cities) - console.log(path) + let { path, distance } = findShortestPath(cities, roads, start, end); + const shortestPath = formatPath(path, cities) - let response = { path: formatedPath, distance } + let response = { path: shortestPath, distance } res.status(200).json(response); } catch (e) { From f9c5eeecf51fe07419d6c37f7b100e13483c258d Mon Sep 17 00:00:00 2001 From: rui hildt Date: Sun, 9 Feb 2020 20:47:45 +0100 Subject: [PATCH 10/10] Update docimentation --- README.md | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5be4179..1d6cef4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ___ **`GET /api/countries/:country_id`** ##### Response -A json object with a list of cities in the selected country `name` and `id` and `country_id`. +An array of json objects with a list of cities in the selected country with `name`, `id` and `country_id`. ``` [ @@ -51,14 +51,16 @@ A json object with a list of cities in the selected country `name` and `id` and **`GET /api/cities`** ##### Response -A json object with `id`, `name` and `country_id`. +An array of json objects with `id`, `name` and `country_id`. ``` -{ - "id": 1, - "name": 'bruges', - "country_id": 1 -} +[ + { + "id": 1, + "name": 'bruges', + "country_id": 1 + } +] ``` ### ROADS **`roads`** @@ -73,15 +75,17 @@ A json object with `id`, `name` and `country_id`. **`GET /api/roads`** ##### Response -A json object with `id`, `start_city_id`, 'end_city_id` and `distance`. +A array of json objects with `id`, `start_city_id`, 'end_city_id` and `distance`. ``` -{ - "id": 1, - "start_city_id": 1, - "end_city_id": 3, - "distance": 50 -} +[ + { + "id": 1, + "start_city_id": 1, + "end_city_id": 3, + "distance": 50 + } +] ``` ### Shortest Path **`cities`**