From 9fcf0d4d26639ce6f45d50d3bb584e60eff24699 Mon Sep 17 00:00:00 2001 From: douglas Date: Sat, 25 Aug 2018 22:22:33 +1000 Subject: [PATCH] #13 - Added support for modified the database structure from the code - Refactored code to create and list rides. - Added missing tests. --- infrastructure/services/database/db-utils.js | 40 -- infrastructure/services/package-lock.json | 415 ++++++++++++++---- infrastructure/services/package.json | 14 +- infrastructure/services/rides/create.js | 91 ---- infrastructure/services/rides/list.js | 67 --- infrastructure/services/rides/ride-status.js | 4 - .../services/{ => src/main}/auth/auth.js | 0 .../services/{ => src/main}/auth/loggedin.js | 0 .../src/main/database/DatabaseManager.js | 92 ++++ .../2018-08-04-1-create-location-table.sql | 0 .../2018-08-04-2-create-rides-table.sql | 2 +- .../2018-08-04-3-create-driver-table.sql | 0 .../2018-08-04-4-create-driver_car-table.sql | 0 .../2018-08-04-5-create-driver_ride-table.sql | 0 .../services/{ => src/main}/database/index.js | 0 .../{ => src/main}/database/reload-db.js | 0 .../{ => src/main}/notification/notify.js | 0 .../services/src/main/rides/Coordinates.js | 8 + .../src/main/rides/CreateRideController.js | 72 +++ .../src/main/rides/ListRidesController.js | 37 ++ .../services/src/main/rides/RideRepository.js | 113 +++++ .../services/src/main/rides/RideStatus.js | 12 + .../services/{ => src/main}/rides/findone.js | 2 +- .../services/src/main/rides/rideAwsLambdas.js | 21 + .../{ => src/main}/rides/rides-mapper.js | 0 .../services/{ => src/main}/rides/update.js | 4 +- .../services/{ => src/main}/schema/ride.json | 0 .../services/{ => src/main}/utils/jwt.js | 1 - .../services/{ => src/main}/utils/ping.js | 0 .../services/src/test/RandomUtils.js | 18 + .../src/test/database/DatabaseTestManager.js | 6 + .../src/test/database/databaseTestConfig.js | 8 + .../test/rides/CreateRide.integration.test.js | 103 +++++ .../src/test/rides/FindRideTestRepository.js | 15 + .../test/rides/ListRides.integration.test.js | 129 ++++++ .../src/test/rides/RideEntityBuilder.js | 31 ++ 36 files changed, 1018 insertions(+), 287 deletions(-) delete mode 100644 infrastructure/services/database/db-utils.js delete mode 100644 infrastructure/services/rides/create.js delete mode 100644 infrastructure/services/rides/list.js delete mode 100644 infrastructure/services/rides/ride-status.js rename infrastructure/services/{ => src/main}/auth/auth.js (100%) rename infrastructure/services/{ => src/main}/auth/loggedin.js (100%) create mode 100644 infrastructure/services/src/main/database/DatabaseManager.js rename infrastructure/services/{ => src/main}/database/changes/2018-08-04-1-create-location-table.sql (100%) rename infrastructure/services/{ => src/main}/database/changes/2018-08-04-2-create-rides-table.sql (92%) rename infrastructure/services/{ => src/main}/database/changes/2018-08-04-3-create-driver-table.sql (100%) rename infrastructure/services/{ => src/main}/database/changes/2018-08-04-4-create-driver_car-table.sql (100%) rename infrastructure/services/{ => src/main}/database/changes/2018-08-04-5-create-driver_ride-table.sql (100%) rename infrastructure/services/{ => src/main}/database/index.js (100%) rename infrastructure/services/{ => src/main}/database/reload-db.js (100%) rename infrastructure/services/{ => src/main}/notification/notify.js (100%) create mode 100644 infrastructure/services/src/main/rides/Coordinates.js create mode 100644 infrastructure/services/src/main/rides/CreateRideController.js create mode 100644 infrastructure/services/src/main/rides/ListRidesController.js create mode 100644 infrastructure/services/src/main/rides/RideRepository.js create mode 100644 infrastructure/services/src/main/rides/RideStatus.js rename infrastructure/services/{ => src/main}/rides/findone.js (95%) create mode 100644 infrastructure/services/src/main/rides/rideAwsLambdas.js rename infrastructure/services/{ => src/main}/rides/rides-mapper.js (100%) rename infrastructure/services/{ => src/main}/rides/update.js (95%) rename infrastructure/services/{ => src/main}/schema/ride.json (100%) rename infrastructure/services/{ => src/main}/utils/jwt.js (97%) rename infrastructure/services/{ => src/main}/utils/ping.js (100%) create mode 100644 infrastructure/services/src/test/RandomUtils.js create mode 100644 infrastructure/services/src/test/database/DatabaseTestManager.js create mode 100644 infrastructure/services/src/test/database/databaseTestConfig.js create mode 100644 infrastructure/services/src/test/rides/CreateRide.integration.test.js create mode 100644 infrastructure/services/src/test/rides/FindRideTestRepository.js create mode 100644 infrastructure/services/src/test/rides/ListRides.integration.test.js create mode 100644 infrastructure/services/src/test/rides/RideEntityBuilder.js diff --git a/infrastructure/services/database/db-utils.js b/infrastructure/services/database/db-utils.js deleted file mode 100644 index 3f956826..00000000 --- a/infrastructure/services/database/db-utils.js +++ /dev/null @@ -1,40 +0,0 @@ -const connection = () => mysql.createConnection({ - host: process.env.MYSQL_HOST, - port: process.env.MYSQL_PORT, - user: process.env.MYSQL_USER, - password: process.env.MYSQL_PW, - database: 'carpal', - multipleStatements: true -}); - -class DbUtils { - - static getConnection(){ - return connection; - } - - static query(connection, queryString) { - if (typeof connection === 'string') { - const db = require('./db').connection; - queryString = connection; - connection = db(); - } - - return new Promise((resolve, reject) => { - connection.query(queryString, (error, results, fields) => { - if (error) { - console.log("Error executing", queryString, error); - } - connection.end(function (err) { - if (error) { - return reject(error); - } - resolve(results); - }); - }); - }); - } - -} - -module.exports = DbUtils; \ No newline at end of file diff --git a/infrastructure/services/package-lock.json b/infrastructure/services/package-lock.json index 57e9bd07..53bec19f 100644 --- a/infrastructure/services/package-lock.json +++ b/infrastructure/services/package-lock.json @@ -9,12 +9,17 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, + "array-uniq": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz", + "integrity": "sha1-X8w3OSB3VyPP1k1lxkvvU7+eum0=" + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -25,6 +30,12 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -65,6 +76,12 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -77,7 +94,7 @@ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bignumber.js": { @@ -85,15 +102,31 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.4.tgz", "integrity": "sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==" }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { - "base64-js": "1.3.0", - "ieee754": "1.1.8", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } }, "buffer-equal-constant-time": { @@ -106,6 +139,32 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "chai-exclude": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/chai-exclude/-/chai-exclude-1.0.9.tgz", + "integrity": "sha512-lnlzkl7OKelBYWvY6ewFJsE3pIExIrPl5Hu8dUGPk+CWBiD7H3B/RBaeC8XXC1xypdYNNB5Zi6qi5MpacO8Lyw==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -116,9 +175,21 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -129,7 +200,33 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" } }, "delayed-stream": { @@ -137,13 +234,19 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "ecdsa-sig-formatter": { @@ -151,9 +254,15 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, + "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 + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -190,19 +299,51 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "^2.1.12" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -213,18 +354,30 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "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 + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "ieee754": { @@ -233,6 +386,16 @@ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -290,16 +453,16 @@ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.2.tgz", "integrity": "sha512-rFFq7ow/JpPzwgaz4IyRL9cp7f4ptjW92eZgsQyqkysLBmDjSSBhnKfQESoq0GU+qJXK/CQ0o4shgwbUPiFCdw==", "requires": { - "jws": "3.1.5", - "lodash.includes": "4.3.0", - "lodash.isboolean": "3.0.3", - "lodash.isinteger": "4.0.4", - "lodash.isnumber": "3.0.3", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.once": "4.1.1", - "ms": "2.1.1", - "xtend": "4.0.1" + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "xtend": "^4.0.1" } }, "jsprim": { @@ -320,7 +483,7 @@ "requires": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "jws": { @@ -328,8 +491,8 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", "requires": { - "jwa": "1.1.6", - "safe-buffer": "5.1.2" + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" } }, "lodash": { @@ -383,9 +546,57 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" } }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + } + }, + "moment": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -414,6 +625,27 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -441,18 +673,26 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, + "randomstring": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/randomstring/-/randomstring-1.1.5.tgz", + "integrity": "sha1-bfBij3XL1ZMpMNn+OrTpVqGFGMM=", + "requires": { + "array-uniq": "1.0.2" + } + }, "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.2", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" } }, "request": { @@ -460,26 +700,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "safe-buffer": { @@ -503,14 +743,14 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" } }, "string_decoder": { @@ -518,7 +758,16 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } }, "tough-cookie": { @@ -526,7 +775,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -541,7 +790,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -550,6 +799,12 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -575,19 +830,25 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, "xml2js": { "version": "0.4.17", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", "dev": true, "requires": { - "sax": "1.2.1", - "xmlbuilder": "4.2.1" + "sax": ">=0.6.0", + "xmlbuilder": "^4.1.0" } }, "xmlbuilder": { @@ -596,7 +857,7 @@ "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", "dev": true, "requires": { - "lodash": "4.17.10" + "lodash": "^4.0.0" } }, "xtend": { diff --git a/infrastructure/services/package.json b/infrastructure/services/package.json index f92cd03f..ffa41e49 100644 --- a/infrastructure/services/package.json +++ b/infrastructure/services/package.json @@ -1,14 +1,22 @@ { "name": "carpal", "version": "1.0.0", + "scripts": { + "test": "mocha './src/test/**/*.test.js'" + }, "dependencies": { "jsonschema": "^1.2.4", "jsonwebtoken": "^8.1.0", + "moment": "^2.22.2", + "mysql": "^2.15.0", + "randomstring": "^1.1.5", "request": "^2.87.0", - "uuid": "^3.2.1", - "mysql": "^2.15.0" + "uuid": "^3.2.1" }, "devDependencies": { - "aws-sdk": "^2.250.1" + "aws-sdk": "^2.250.1", + "chai": "^4.1.2", + "chai-exclude": "^1.0.9", + "mocha": "^5.2.0" } } diff --git a/infrastructure/services/rides/create.js b/infrastructure/services/rides/create.js deleted file mode 100644 index a7bd60a1..00000000 --- a/infrastructure/services/rides/create.js +++ /dev/null @@ -1,91 +0,0 @@ -'use strict'; - -const validate = require('jsonschema').validate; // eslint-disable-line import/no-extraneous-dependencies -const rideSchema = require('../schema/ride.json'); // eslint-disable-line import/no-extraneous-dependencies -const db = require('../database/db-utils').getConnection(); -const rideStatus = require('./ride-status'); -const decodeJwt = require('../utils/jwt').decodeJwt; - -module.exports.create = (event, context, callback) => { - const timestamp = new Date().getTime(); - const connection = db(); - - const data = JSON.parse(event.body); - data.datetime = timestamp; - - let loginData = decodeJwt(event); - let facilitatorEmail = loginData && loginData.email; - const validationResult = validate(data, rideSchema); - - if (validationResult.errors && validationResult.errors.length) { - callback(null, { - statusCode: 400, - headers: { - 'Content-Type': 'text/plain' - }, - body: JSON.stringify({"error": validationResult.errors}) - }); - return; - } - - let escape = function (data) { - return connection.escape(data) - }; - let payload = { - client: `${escape(data.client)}`, - facilitatorEmail: `${escape(facilitatorEmail)}`, - pickupTimeAndDateInUTC: `"${new Date(data.pickupTimeAndDateInUTC).toISOString()}"`, - locationFrom: `POINT(${data.locationFrom.latitude}, ${data.locationFrom.longitude})`, - locationTo: `POINT(${data.locationTo.latitude}, ${data.locationTo.longitude})`, - fbLink: `${escape(data.fbLink)}`, - driverGender: `${escape(data.driverGender)}`, - carType: `${escape(data.carType)}`, - status: `"${rideStatus.open}"`, - deleted: `0`, - suburbFrom: `${escape(data.locationFrom.suburb)}`, - placeNameFrom: `${escape(data.locationFrom.placeName)}`, - postCodeFrom: `${escape(data.locationFrom.postcode)}`, - suburbTo: `${escape(data.locationTo.suburb)}`, - placeNameTo: `${escape(data.locationTo.placeName)}`, - postCodeTo: `${escape(data.locationTo.postcode)}`, - description: `${escape(data.description)}` - }; - - let values = Reflect - .ownKeys(payload) - .map(key => payload[key]) - .join(','); - let query = `INSERT INTO rides(${Reflect.ownKeys(payload)}) VALUES (${values})`; - console.log(query); - - queryDatabase(connection, query, (err, result) => { - if (err) { - console.log('ERROR', err); - return callback(null, { - statusCode: err.statusCode || 501, - headers: { - 'Content-Type': 'text/plain' - }, - body: JSON.stringify(err) - }); - } - callback(null, { - statusCode: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Credentials': true, - 'Content-Type': 'text/plain' - }, - body: JSON.stringify(result) - }) - }); -}; - -function queryDatabase(connection, query, callback) { - connection.query(query, (error, results, fields) => { - connection - .end(function (err) { - callback(error, results); - }); - }); -} diff --git a/infrastructure/services/rides/list.js b/infrastructure/services/rides/list.js deleted file mode 100644 index 3aaff32a..00000000 --- a/infrastructure/services/rides/list.js +++ /dev/null @@ -1,67 +0,0 @@ -'use strict'; -const decodeJwt = require('../utils/jwt').decodeJwt; -const db = require('../database/db-utils').getConnection(); -const mapToDto = require('./rides-mapper').mapToDto; - -module.exports.list = (event, context, callback) => { - let queryParams = event.queryStringParameters || {}; - console.log(JSON.stringify(queryParams)); - console.log('Querying mysql'); - const connection = db(); - const claims = decodeJwt(event); - console.log("Claims: " + JSON.stringify(claims)); - - let query; - - if (claims.role === 'driver') { - let locationQuery = ''; - if (queryParams.toLongitude && queryParams.toLatitude && queryParams.fromLongitude && queryParams.fromLatitude) { - locationQuery = `and ST_Contains(ST_Envelope(ST_GeomFromText('LINESTRING(${queryParams.toLongitude} ${queryParams.toLatitude}, ${queryParams.fromLongitude} ${queryParams.fromLatitude})')), locationFrom)`; - } - query = ` -SELECT * FROM rides where pickupTimeAndDateInUTC > NOW() -and(driverGender = 'any' or driverGender = '${claims.driverGender}') -${locationQuery} -ORDER BY pickupTimeAndDateInUTC ASC - ` - } else if (claims.role === 'facilitator') { - query = ` -SELECT * FROM carpal.rides WHERE facilitatorEmail = '${claims.email}' -ORDER BY pickupTimeAndDateInUTC ASC; - ` - } else if (claims.role === 'admin') { - query = ` -SELECT * FROM carpal.rides -ORDER BY pickupTimeAndDateInUTC ASC; - ` - } - - console.log('Query: ' + query); - - connection.query(query, function (error, results, fields) { - if (error) { - console.error(error); - callback(null, { - statusCode: error.statusCode || 501, - headers: { - 'Content-Type': 'text/plain' - }, - body: 'Couldn\'t fetch rides.' - }); - return; - } - console.log(results); - const response = { - statusCode: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Credentials': true - }, - body: JSON.stringify(mapToDto(results)) - }; - connection.end(function (err) { - callback(null, response); - }); - }); - -}; \ No newline at end of file diff --git a/infrastructure/services/rides/ride-status.js b/infrastructure/services/rides/ride-status.js deleted file mode 100644 index 7b806d27..00000000 --- a/infrastructure/services/rides/ride-status.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - open: 'open', - closed: 'closed' -}; \ No newline at end of file diff --git a/infrastructure/services/auth/auth.js b/infrastructure/services/src/main/auth/auth.js similarity index 100% rename from infrastructure/services/auth/auth.js rename to infrastructure/services/src/main/auth/auth.js diff --git a/infrastructure/services/auth/loggedin.js b/infrastructure/services/src/main/auth/loggedin.js similarity index 100% rename from infrastructure/services/auth/loggedin.js rename to infrastructure/services/src/main/auth/loggedin.js diff --git a/infrastructure/services/src/main/database/DatabaseManager.js b/infrastructure/services/src/main/database/DatabaseManager.js new file mode 100644 index 00000000..7ef74525 --- /dev/null +++ b/infrastructure/services/src/main/database/DatabaseManager.js @@ -0,0 +1,92 @@ +const mysql = require('mysql'); + +class DatabaseManager { + + constructor(databaseConfig) { + this.databaseConfig = databaseConfig || { + host: process.env.MYSQL_HOST, + port: process.env.MYSQL_PORT, + user: process.env.MYSQL_USER, + password: process.env.MYSQL_PW, + database: 'carpal', + multipleStatements: true + }; + } + + createConnection() { + return mysql.createConnection(this.databaseConfig); + } + + query(queryString, connection) { + let closeConnection = false; + if (!connection) { + connection = this.createConnection(); + closeConnection = true; + } + + return new Promise((resolve, reject) => { + connection.query(queryString, (error, results, fields) => { + if (closeConnection) { + return this.closeConnection(connection) + .finally(() => { + if (error) { + return reject(error); + } + resolve(results); + }); + } + if (error) { + console.log("Error executing", queryString, error); + return reject(error); + } + return resolve(results); + }); + }); + } + + closeConnection(connection) { + return new Promise((resolve, reject) => { + connection.end(function (error) { + if (error) { + return reject(error); + } + resolve(); + }); + }); + } + + beginTransaction(connection) { + return new Promise((resolve, reject) => { + connection.beginTransaction(function (error) { + if (error) { + return reject(error); + } + resolve(); + }); + }); + } + + rollback(connection) { + return new Promise((resolve, reject) => { + connection.rollback(function (error) { + if (error) { + return reject(error); + } + resolve(); + }); + }); + } + + commit(connection) { + return new Promise((resolve, reject) => { + connection.commit(function (error) { + if (error) { + return reject(error); + } + resolve(); + }); + }); + } +} + +module.exports = DatabaseManager; \ No newline at end of file diff --git a/infrastructure/services/database/changes/2018-08-04-1-create-location-table.sql b/infrastructure/services/src/main/database/changes/2018-08-04-1-create-location-table.sql similarity index 100% rename from infrastructure/services/database/changes/2018-08-04-1-create-location-table.sql rename to infrastructure/services/src/main/database/changes/2018-08-04-1-create-location-table.sql diff --git a/infrastructure/services/database/changes/2018-08-04-2-create-rides-table.sql b/infrastructure/services/src/main/database/changes/2018-08-04-2-create-rides-table.sql similarity index 92% rename from infrastructure/services/database/changes/2018-08-04-2-create-rides-table.sql rename to infrastructure/services/src/main/database/changes/2018-08-04-2-create-rides-table.sql index f6917744..acff5563 100644 --- a/infrastructure/services/database/changes/2018-08-04-2-create-rides-table.sql +++ b/infrastructure/services/src/main/database/changes/2018-08-04-2-create-rides-table.sql @@ -1,5 +1,5 @@ CREATE TABLE IF NOT EXISTS rides ( - id INT(11) PRIMARY KEY, + id INT(11) AUTO_INCREMENT PRIMARY KEY, client VARCHAR(255), facilitatorEmail VARCHAR(255), pickupTimeAndDateInUTC DATETIME, diff --git a/infrastructure/services/database/changes/2018-08-04-3-create-driver-table.sql b/infrastructure/services/src/main/database/changes/2018-08-04-3-create-driver-table.sql similarity index 100% rename from infrastructure/services/database/changes/2018-08-04-3-create-driver-table.sql rename to infrastructure/services/src/main/database/changes/2018-08-04-3-create-driver-table.sql diff --git a/infrastructure/services/database/changes/2018-08-04-4-create-driver_car-table.sql b/infrastructure/services/src/main/database/changes/2018-08-04-4-create-driver_car-table.sql similarity index 100% rename from infrastructure/services/database/changes/2018-08-04-4-create-driver_car-table.sql rename to infrastructure/services/src/main/database/changes/2018-08-04-4-create-driver_car-table.sql diff --git a/infrastructure/services/database/changes/2018-08-04-5-create-driver_ride-table.sql b/infrastructure/services/src/main/database/changes/2018-08-04-5-create-driver_ride-table.sql similarity index 100% rename from infrastructure/services/database/changes/2018-08-04-5-create-driver_ride-table.sql rename to infrastructure/services/src/main/database/changes/2018-08-04-5-create-driver_ride-table.sql diff --git a/infrastructure/services/database/index.js b/infrastructure/services/src/main/database/index.js similarity index 100% rename from infrastructure/services/database/index.js rename to infrastructure/services/src/main/database/index.js diff --git a/infrastructure/services/database/reload-db.js b/infrastructure/services/src/main/database/reload-db.js similarity index 100% rename from infrastructure/services/database/reload-db.js rename to infrastructure/services/src/main/database/reload-db.js diff --git a/infrastructure/services/notification/notify.js b/infrastructure/services/src/main/notification/notify.js similarity index 100% rename from infrastructure/services/notification/notify.js rename to infrastructure/services/src/main/notification/notify.js diff --git a/infrastructure/services/src/main/rides/Coordinates.js b/infrastructure/services/src/main/rides/Coordinates.js new file mode 100644 index 00000000..a2f8b7df --- /dev/null +++ b/infrastructure/services/src/main/rides/Coordinates.js @@ -0,0 +1,8 @@ +class Coordinates { + constructor(latitude, longitude){ + this.latitude = latitude; + this.longitude = longitude; + } +} + +module.exports = Coordinates; \ No newline at end of file diff --git a/infrastructure/services/src/main/rides/CreateRideController.js b/infrastructure/services/src/main/rides/CreateRideController.js new file mode 100644 index 00000000..c012cb41 --- /dev/null +++ b/infrastructure/services/src/main/rides/CreateRideController.js @@ -0,0 +1,72 @@ +'use strict'; + +const jsonValidator = require('jsonschema'); +const rideSchema = require('../schema/ride.json'); +const RideStatus = require('./RideStatus'); +const RideRepository = require('./RideRepository'); +const Coordinates = require('./Coordinates'); + +class CreateRideController { + + constructor(databaseManager) { + this._databaseManager = databaseManager; + this._rideRepository = new RideRepository(databaseManager); + } + + createRide(body, loginData) { + const connection = this._databaseManager.createConnection(); + + return this._createRide(body, loginData, connection) + .finally(() => this._databaseManager.closeConnection(connection)); + } + + _createRide(rideObject, loginData, connection) { + rideObject.datetime = new Date().getTime(); + + let validationError = this._validate(rideObject); + if (validationError) { + return reject(validationError); + } + + let facilitatorEmail = loginData && loginData.email; + let payload = this._parseBody(rideObject, facilitatorEmail); + return this._rideRepository.create(payload, connection); + } + + _parseBody(data, facilitatorEmail) { + return { + client: `${data.client}`, + facilitatorEmail: facilitatorEmail, + pickupTimeAndDateInUTC: new Date(data.pickupTimeAndDateInUTC), + locationFrom: new Coordinates(data.locationFrom.latitude, data.locationFrom.longitude), + locationTo: new Coordinates(data.locationTo.latitude, data.locationTo.longitude), + fbLink: data.fbLink, + driverGender: data.driverGender, + carType: data.carType, + status: RideStatus.OPEN, + deleted: `0`, + suburbFrom: data.locationFrom.suburb, + placeNameFrom: data.locationFrom.placeName, + postCodeFrom: data.locationFrom.postcode, + suburbTo: data.locationTo.suburb, + placeNameTo: data.locationTo.placeName, + postCodeTo: data.locationTo.postcode, + description: data.description + }; + } + + _validate(data) { + const validationResult = jsonValidator.validate(data, rideSchema); + if (validationResult.errors && validationResult.errors.length) { + return { + statusCode: 400, + headers: { + 'Content-Type': 'text/plain' + }, + body: JSON.stringify({"error": validationResult.errors}) + }; + } + } +} + +module.exports = CreateRideController; \ No newline at end of file diff --git a/infrastructure/services/src/main/rides/ListRidesController.js b/infrastructure/services/src/main/rides/ListRidesController.js new file mode 100644 index 00000000..50208eea --- /dev/null +++ b/infrastructure/services/src/main/rides/ListRidesController.js @@ -0,0 +1,37 @@ +'use strict'; + +const RideRepository = require('./RideRepository'); + +class ListRidesController { + + constructor(databaseManager) { + this._databaseManager = databaseManager; + this._rideRepository = new RideRepository(databaseManager); + } + + listRides(queryParams, loginData) { + const connection = this._databaseManager.createConnection(); + + return this._listRides(queryParams, loginData, connection) + .finally(() => this._databaseManager.closeConnection(connection)); + } + + _listRides(queryParams, loginData, connection) { + let jsonQuery = this._parseParams(queryParams, loginData); + return this._rideRepository.list(jsonQuery, connection); + } + + _parseParams(queryParams, loginData) { + return { + toLongitude: queryParams.toLongitude, + toLatitude: queryParams.toLatitude, + fromLongitude: queryParams.fromLongitude, + fromLatitude: queryParams.fromLatitude, + driverGenders: loginData.role === 'driver' ? ['any', loginData.driverGender] : undefined, + includePickupTimeInPast: loginData.role !== 'driver', + facilitatorEmail: loginData.role === 'facilitator' ? loginData.email : undefined, + }; + } +} + +module.exports = ListRidesController; \ No newline at end of file diff --git a/infrastructure/services/src/main/rides/RideRepository.js b/infrastructure/services/src/main/rides/RideRepository.js new file mode 100644 index 00000000..cde17395 --- /dev/null +++ b/infrastructure/services/src/main/rides/RideRepository.js @@ -0,0 +1,113 @@ +'use strict'; + +const moment = require('moment'); + +class RideRepository { + constructor(databaseManager) { + this._databaseManager = databaseManager; + } + + /** + * @param ride: + * client {string} + * facilitatorEmail {string} + * pickupTimeAndDateInUTC {Date} + * locationFrom {Coordinates} + * locationTo {Coordinates} + * fbLink {string} + * driverGender {string} + * carType {string} + * status {RideStatus} + * deleted {number - 0/1} + * suburbFrom {string} + * placeNameFrom {string} + * postCodeFrom {string} + * suburbTo {string} + * placeNameTo {string} + * postCodeTo {string} + * description + * @param connection + * @returns {*} + */ + create(ride, connection) { + const escape = (data) => connection.escape(data); + const locationFrom = `POINT(${ride.locationFrom.latitude}, ${ride.locationFrom.longitude})`; + const locationTo = `POINT(${ride.locationTo.latitude}, ${ride.locationTo.longitude})`; + let query = `INSERT INTO rides(client, + facilitatorEmail, + pickupTimeAndDateInUTC, + locationFrom, + locationTo, + fbLink, + driverGender, + carType, + status, + deleted, + suburbFrom, + placeNameFrom, + postCodeFrom, + suburbTo, + placeNameTo, + postCodeTo, + description) + VALUES + (${ + [ escape(ride.client), + escape(ride.facilitatorEmail), + escape(moment(ride.pickupTimeAndDateInUTC).format('YYYY-MM-DD HH:mm:ss')), + locationFrom, + locationTo, + escape(ride.fbLink), + escape(ride.driverGender), + escape(ride.carType), + escape(ride.status.statusName), + escape(ride.deleted), + escape(ride.suburbFrom), + escape(ride.placeNameFrom), + escape(ride.postCodeFrom), + escape(ride.suburbTo), + escape(ride.placeNameTo), + escape(ride.postCodeTo), + escape(ride.description) + ].join(',')})`; + console.log(query); + + return this._databaseManager.query(query, connection); + } + + /** + * @param jsonQuery: + * toLongitude {number} + * toLatitude {number} + * fromLongitude {number} + * fromLatitude {number} + * driverGenders {string[]} + * facilitatorEmail {string} + * includePickupTimeInPast {boolean} + * @param connection + */ + list(jsonQuery, connection) { + const escape = (data) => connection.escape(data); + let where = []; + if(jsonQuery.toLongitude && jsonQuery.toLatitude && jsonQuery.fromLongitude && jsonQuery.fromLatitude) { + where.push(`ST_Contains(ST_Envelope(ST_GeomFromText('LINESTRING(${jsonQuery.toLongitude} ${jsonQuery.toLatitude}, ${jsonQuery.fromLongitude} ${jsonQuery.fromLatitude})')), locationFrom)`); + } + if(!jsonQuery.includePickupTimeInPast){ + where.push('pickupTimeAndDateInUTC >= NOW()') + } + if(jsonQuery.driverGenders && jsonQuery.driverGenders.length){ + let genders = jsonQuery.driverGenders.map(g => ` driverGender = '${g}'`).join(' or '); + where.push(genders.length === 1 ? genders : `(${genders})`) + } + if(jsonQuery.facilitatorEmail){ + where.push(`facilitatorEmail = ${escape(jsonQuery.facilitatorEmail)}`) + } + + let query = `SELECT * FROM rides ${where.length ? ' WHERE ' + where.join(' AND ') : ''} ORDER BY pickupTimeAndDateInUTC ASC;`; + console.log(query); + return this._databaseManager.query(query, connection); + } + +} + +module.exports = RideRepository; diff --git a/infrastructure/services/src/main/rides/RideStatus.js b/infrastructure/services/src/main/rides/RideStatus.js new file mode 100644 index 00000000..5ccd6686 --- /dev/null +++ b/infrastructure/services/src/main/rides/RideStatus.js @@ -0,0 +1,12 @@ +class RideStatus { + constructor(status) { + this.statusName = status; + } + + static get OPEN(){ return new RideStatus('OPEN'); } + static get CONFIRMED(){ return new RideStatus('CONFIRMED'); } + static get ENDED(){ return new RideStatus('ENDED'); } + static get CANCELLED(){ return new RideStatus('CANCELLED'); } +} + +module.exports = RideStatus; \ No newline at end of file diff --git a/infrastructure/services/rides/findone.js b/infrastructure/services/src/main/rides/findone.js similarity index 95% rename from infrastructure/services/rides/findone.js rename to infrastructure/services/src/main/rides/findone.js index 14e40a45..226edc63 100644 --- a/infrastructure/services/rides/findone.js +++ b/infrastructure/services/src/main/rides/findone.js @@ -1,5 +1,5 @@ 'use strict'; -const db = require('../database/db-utils').getConnection(); +const db = require('../database/db-utils').createConnection(); const mapToDto = require('./rides-mapper').mapToDto; module.exports.findone = (event, context, callback) => { diff --git a/infrastructure/services/src/main/rides/rideAwsLambdas.js b/infrastructure/services/src/main/rides/rideAwsLambdas.js new file mode 100644 index 00000000..7bf5d3f2 --- /dev/null +++ b/infrastructure/services/src/main/rides/rideAwsLambdas.js @@ -0,0 +1,21 @@ +const decodeJwt = require('../utils/jwt').decodeJwt; +const CreateRideController = require('./CreateRideController'); +const DatabaseManager = require('../database/DatabaseManager'); +const databaseManager = new DatabaseManager(); + +module.exports = { + create: function(event, context, callback){ + let controller = new CreateRideController(databaseManager); + let loginData = decodeJwt(event); + controller.createRide(JSON.parse(event.body), loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + }, + list: function(event, context, callback){ + let controller = new CreateRideController(databaseManager); + let loginData = decodeJwt(event); + controller.createRide(JSON.parse(event.body), loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + } +}; \ No newline at end of file diff --git a/infrastructure/services/rides/rides-mapper.js b/infrastructure/services/src/main/rides/rides-mapper.js similarity index 100% rename from infrastructure/services/rides/rides-mapper.js rename to infrastructure/services/src/main/rides/rides-mapper.js diff --git a/infrastructure/services/rides/update.js b/infrastructure/services/src/main/rides/update.js similarity index 95% rename from infrastructure/services/rides/update.js rename to infrastructure/services/src/main/rides/update.js index 5b035ac2..2726a373 100644 --- a/infrastructure/services/rides/update.js +++ b/infrastructure/services/src/main/rides/update.js @@ -1,6 +1,6 @@ 'use strict'; -const db = require('../database/db-utils').getConnection() +const db = require('../database/db-utils').createConnection() const rideStatus = require('./ride-status'); const decodeJwt = require('../utils/jwt').decodeJwt; @@ -36,7 +36,7 @@ module.exports.update = (event, context, callback) => { fbLink: `${escape(data.fbLink)}`, driverGender: `${escape(data.driverGender)}`, carType: `${escape(data.carType)}`, - status: `${escape(data.status)}`, + status: `${rideStatus[escape(data.status).toUpperCase()]}`, deleted: `0`, suburbFrom: `${escape(data.locationFrom.suburb)}`, placeNameFrom: `${escape(data.locationFrom.placeName)}`, diff --git a/infrastructure/services/schema/ride.json b/infrastructure/services/src/main/schema/ride.json similarity index 100% rename from infrastructure/services/schema/ride.json rename to infrastructure/services/src/main/schema/ride.json diff --git a/infrastructure/services/utils/jwt.js b/infrastructure/services/src/main/utils/jwt.js similarity index 97% rename from infrastructure/services/utils/jwt.js rename to infrastructure/services/src/main/utils/jwt.js index b5c7fbdc..699c50ca 100644 --- a/infrastructure/services/utils/jwt.js +++ b/infrastructure/services/src/main/utils/jwt.js @@ -21,6 +21,5 @@ module.exports.decodeJwt = (event) => { return claims; } catch (err) { console.log('catch error. Invalid token', err); - return; } } \ No newline at end of file diff --git a/infrastructure/services/utils/ping.js b/infrastructure/services/src/main/utils/ping.js similarity index 100% rename from infrastructure/services/utils/ping.js rename to infrastructure/services/src/main/utils/ping.js diff --git a/infrastructure/services/src/test/RandomUtils.js b/infrastructure/services/src/test/RandomUtils.js new file mode 100644 index 00000000..da9e0e74 --- /dev/null +++ b/infrastructure/services/src/test/RandomUtils.js @@ -0,0 +1,18 @@ +const randomstring = require("randomstring"); + +class RandomUtils { + static randomString(length) { + return randomstring.generate(length); + } + + static randomNumber(digits) { + digits = digits ? digits : 8; + return Math.floor(Math.random() * Math.pow(10, digits)); + } + + static randomEmail() { + return `${RandomUtils.randomString(6)}.${Date.now()}@${RandomUtils.randomString(6)}.com`; + } +} + +module.exports = RandomUtils; \ No newline at end of file diff --git a/infrastructure/services/src/test/database/DatabaseTestManager.js b/infrastructure/services/src/test/database/DatabaseTestManager.js new file mode 100644 index 00000000..5eb29ab1 --- /dev/null +++ b/infrastructure/services/src/test/database/DatabaseTestManager.js @@ -0,0 +1,6 @@ +class DatabaseTestManager { + afterTest(){ + + } + beforeTest(); +} \ No newline at end of file diff --git a/infrastructure/services/src/test/database/databaseTestConfig.js b/infrastructure/services/src/test/database/databaseTestConfig.js new file mode 100644 index 00000000..d964334c --- /dev/null +++ b/infrastructure/services/src/test/database/databaseTestConfig.js @@ -0,0 +1,8 @@ +module.exports = { + host: 'localhost', + port: 3306, + user: 'root', + password: 'admin', + database: 'carpal', + multipleStatements: true +}; \ No newline at end of file diff --git a/infrastructure/services/src/test/rides/CreateRide.integration.test.js b/infrastructure/services/src/test/rides/CreateRide.integration.test.js new file mode 100644 index 00000000..d2cd07e8 --- /dev/null +++ b/infrastructure/services/src/test/rides/CreateRide.integration.test.js @@ -0,0 +1,103 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const CreateRideController = require('../../main/rides/CreateRideController'); +const FindRideTestRepository = require('./FindRideTestRepository'); +const databaseTestConfig = require('../database/databaseTestConfig'); +const RandomUtils = require('../RandomUtils'); +const moment = require('moment'); +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +chai.use(chaiExclude); + +let createRideController; +let rideRequest; +let expectedRide; +let databaseManager; +let mockDatabaseManager; +let findRideTestRepository; +let connection; +let loginData; + +before(async () => { + databaseManager = new DatabaseManager(databaseTestConfig); + mockDatabaseManager = new DatabaseManager(databaseTestConfig); + connection = databaseManager.createConnection() ; + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => null; +}); + +after(async () => { + await databaseManager.closeConnection(connection); +}); + +beforeEach(async () => { + await databaseManager.beginTransaction(connection); + + createRideController = new CreateRideController(mockDatabaseManager); + findRideTestRepository = new FindRideTestRepository(databaseManager); +}); + +afterEach(async () => { + await databaseManager.rollback(connection); +}); + +beforeEach(function setupData() { + loginData = {email: RandomUtils.randomEmail()} + const currentTime = moment().format('YYYY-MM-DD HH:mm:ss.SSS'); + const clientEmail = `client.test.${Date.now()}@carpal.com`; + rideRequest = { + 'carType': 'suv', + 'client': clientEmail, + 'deleted': 0, + 'description': 'wanna surf', + 'driverGender': 'male', + 'fbLink': 'http://facebook.com/profile/facilitator.test.1', + 'locationFrom': { + "latitude": 1234, + "longitude": 4567, + "placeName": "home", + "suburb": "Bondi Junction", + "postcode": "2010" + }, + 'locationTo': { + "latitude": 9999, + "longitude": 7777, + "placeName": "beach", + "suburb": "Bondi", + "postcode": "2011" + }, + 'pickupTimeAndDateInUTC': currentTime, + 'status': 'OPEN' + }; + expectedRide = { + "carType": "suv", + "client": clientEmail, + "deleted": 0, + "description": "wanna surf", + "driverGender": "male", + "facilitatorEmail": loginData.email, + "fbLink": "http://facebook.com/profile/facilitator.test.1", + "locationFrom": {"x": 1234, "y": 4567}, + "locationTo": {"x": 9999, "y": 7777}, + "pickupTimeAndDateInUTC": new Date(new Date(currentTime).setMilliseconds(0)), + "placeNameFrom": "home", + "placeNameTo": "beach", + "postCodeFrom": "2010", + "postCodeTo": "2011", + "status": "OPEN", + "suburbFrom": "Bondi Junction", + "suburbTo": "Bondi", + } +}); + +describe('SQL', function () { + it('should insert and retrieve ride', async function () { + await createRideController.createRide(rideRequest, loginData); + let storedRide = await findRideTestRepository.findOneByClientEmail(rideRequest.client, connection); + + assert.deepEqualExcluding(storedRide, expectedRide, 'id', 'datetime'); + }); +}); + + + diff --git a/infrastructure/services/src/test/rides/FindRideTestRepository.js b/infrastructure/services/src/test/rides/FindRideTestRepository.js new file mode 100644 index 00000000..cc6015fb --- /dev/null +++ b/infrastructure/services/src/test/rides/FindRideTestRepository.js @@ -0,0 +1,15 @@ +'use strict'; + +class RideRepository { + constructor(databaseManager) { + this._databaseManager = databaseManager; + } + + findOneByClientEmail(email, connection){ + return this._databaseManager.query(`SELECT * FROM rides WHERE client = '${email}'`, connection) + .then(result => result instanceof Array && result[0]); + } +} + + +module.exports = RideRepository; diff --git a/infrastructure/services/src/test/rides/ListRides.integration.test.js b/infrastructure/services/src/test/rides/ListRides.integration.test.js new file mode 100644 index 00000000..7ae9b144 --- /dev/null +++ b/infrastructure/services/src/test/rides/ListRides.integration.test.js @@ -0,0 +1,129 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const ListRidesController = require('../../main/rides/ListRidesController'); +const FindRideTestRepository = require('./FindRideTestRepository'); +const databaseTestConfig = require('../database/databaseTestConfig'); +const RideEntityBuilder = require('./RideEntityBuilder'); +const RideRepository = require('../../main/rides/RideRepository'); +const RandomUtils = require('../RandomUtils'); + +let listRideController; +let databaseManager; +let mockDatabaseManager; +let findRideTestRepository; +let rideRepository; +let connection; + +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +chai.use(chaiExclude); + +before(async () => { + databaseManager = new DatabaseManager(databaseTestConfig); + mockDatabaseManager = new DatabaseManager(databaseTestConfig); + connection = databaseManager.createConnection() ; + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => null; +}); + +after(async () => { + await databaseManager.closeConnection(connection); +}); + +beforeEach(async () => { + await databaseManager.beginTransaction(connection); + + listRideController = new ListRidesController(mockDatabaseManager); + findRideTestRepository = new FindRideTestRepository(databaseManager); + rideRepository = new RideRepository(databaseManager); +}); + +afterEach(async () => { + await databaseManager.rollback(connection); +}); + +describe('When listing rides', async () => { + it('should show rides for facilitator', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'facilitator'}; + const email = loginData.email; + const ride1 = randomRideWithFacilitator(email); + const ride2 = randomRideWithFacilitator(email); + await databaseContainsRides(ride1, ride2); + + // when + const rides = await listRideController.listRides({}, loginData); + + assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride1)); + assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride2)); + }); + + it('should show all rides for admin', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'admin'}; + const ride1 = RideEntityBuilder.randomRide(); + const ride2 = RideEntityBuilder.randomRide(); + await databaseContainsRides(ride1, ride2); + + // when + const rides = await listRideController.listRides({}, loginData); + + assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride1)); + assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride2)); + }); + + it('should show all rides for driver', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'driver', driverGender: 'male'}; + const ride1 = randomRideWithGender('male'); + const ride2 = randomRideWithGender('female'); + const ride3 = randomRideWithGender('any'); + await databaseContainsRides(ride1, ride2, ride3); + + // when + const rides = await listRideController.listRides({}, loginData); + + assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride1)); + assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride3)); + assert.notDeepInclude(rides.map(removeId), transformRideToDbFormat(ride2)); + }); + + function transformRideToDbFormat(ride){ + ride.locationFrom.x = ride.locationFrom.latitude; + ride.locationFrom.y = ride.locationFrom.longitude; + ride.locationTo.x = ride.locationTo.latitude; + ride.locationTo.y = ride.locationTo.longitude; + ride.status = ride.status.statusName; + delete ride.locationFrom.latitude; + delete ride.locationFrom.longitude; + delete ride.locationTo.latitude; + delete ride.locationTo.longitude; + return ride; + } + + function removeId(ride){ + delete ride.id; + return ride; + } + + async function databaseContainsRides(...rides) { + for(let ride of rides){ + await rideRepository.create(ride, connection); + } + } + + function randomRideWithGender(gender) { + const ride = RideEntityBuilder.randomRide(); + ride.driverGender = gender; + return ride; + } + + function randomRideWithFacilitator(facilitatorEmail) { + const ride = RideEntityBuilder.randomRide(); + ride.facilitatorEmail = facilitatorEmail; + return ride; + } +}); + + + diff --git a/infrastructure/services/src/test/rides/RideEntityBuilder.js b/infrastructure/services/src/test/rides/RideEntityBuilder.js new file mode 100644 index 00000000..f90086ac --- /dev/null +++ b/infrastructure/services/src/test/rides/RideEntityBuilder.js @@ -0,0 +1,31 @@ +const RandomUtils = require('../RandomUtils'); +const RideStatus = require('../../main/rides/RideStatus'); + +const now = new Date(); +class RideEntityBuilder { + static randomRide() { + const client = `client.${Date.now()}`; + const facilitator = `facilitator.${Date.now()}`; + return { + "carType": "suv", + "client": `${client}@${RandomUtils.randomString(5)}.com`, + "deleted": 0, + "description": RandomUtils.randomString(10), + "driverGender": (Math.random() * 1000) % 2 ? "male" : "female", + "facilitatorEmail": `${facilitator}@${RandomUtils.randomString(5)}.com`, + "fbLink": `http://facebook.com/profile/${client}`, + "locationFrom": {"latitude": RandomUtils.randomNumber(4), "longitude": RandomUtils.randomNumber(4)}, + "locationTo": {"latitude": RandomUtils.randomNumber(4), "longitude": RandomUtils.randomNumber(4)}, + "pickupTimeAndDateInUTC": new Date(new Date(now.setMilliseconds(0)).setDate(now.getDate() + 5)), + "placeNameFrom": RandomUtils.randomString(10), + "placeNameTo": RandomUtils.randomString(10), + "postCodeFrom": RandomUtils.randomNumber(4).toString(), + "postCodeTo": RandomUtils.randomNumber(4).toString(), + "status": RideStatus.OPEN, + "suburbFrom": RandomUtils.randomString(10), + "suburbTo": RandomUtils.randomString(10), + } + } +} + +module.exports = RideEntityBuilder; \ No newline at end of file