diff --git a/README.md b/README.md index 79c08840..4d88193f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # hills-carpal [![CircleCI](https://circleci.com/gh/RHoKAustralia/hills-carpal.svg?style=svg)](https://circleci.com/gh/RHoKAustralia/hills-carpal) + +# Frontend + + + diff --git a/infrastructure/services/.gitignore b/backend/.gitignore similarity index 100% rename from infrastructure/services/.gitignore rename to backend/.gitignore diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 00000000..8d2653df --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1304 @@ +{ + "name": "carpal", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "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", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "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", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sdk": { + "version": "2.250.1", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.250.1.tgz", + "integrity": "sha1-DMBO38QfoKal7MvFaIiDL1knAUE=", + "dev": true, + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.1.0", + "xml2js": "0.4.17" + }, + "dependencies": { + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.7.0", + "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", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bignumber.js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.4.tgz", + "integrity": "sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==" + }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "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.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "caseless": { + "version": "0.12.0", + "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", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "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 + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "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": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "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.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "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 + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "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" + } + }, + "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", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "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-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "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.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "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", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonschema": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.4.tgz", + "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==" + }, + "jsonwebtoken": { + "version": "8.2.2", + "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.0.0", + "ms": "^2.1.1", + "xtend": "^4.0.1" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "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" + } + }, + "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", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "mysql": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.15.0.tgz", + "integrity": "sha512-C7tjzWtbN5nzkLIV+E8Crnl9bFyc7d3XJcBAvHKEVkjrYjogz3llo22q6s/hw+UcsE4/844pDob9ac+3dVjQSA==", + "requires": { + "bignumber.js": "4.0.4", + "readable-stream": "2.3.3", + "safe-buffer": "5.1.1", + "sqlstring": "2.3.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + } + } + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "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 + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "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", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "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" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, + "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.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": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "requires": { + "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": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", + "dev": true + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "sqlstring": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.0.tgz", + "integrity": "sha1-UluKT9Jtb3GqYegipsr5dtMa0qg=" + }, + "sshpk": { + "version": "1.14.1", + "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.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "string_decoder": { + "version": "1.0.3", + "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.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": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "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 + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "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": ">=0.6.0", + "xmlbuilder": "^4.1.0" + } + }, + "xmlbuilder": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", + "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", + "dev": true, + "requires": { + "lodash": "^4.0.0" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 00000000..53df909c --- /dev/null +++ b/backend/package.json @@ -0,0 +1,27 @@ +{ + "name": "carpal", + "version": "1.0.0", + "scripts": { + "test": "mocha './src/test/**/*.test.js'", + "start": "node src/test/expressApis.js", + "refresh-db": "node ./src/main/database/index.js", + "refresh-test-db": "node ./src/test/database/refreshDatabase.js" + }, + "dependencies": { + "body-parser": "^1.18.3", + "express": "^4.16.3", + "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" + }, + "devDependencies": { + "aws-sdk": "^2.250.1", + "chai": "^4.1.2", + "chai-exclude": "^1.0.9", + "mocha": "^5.2.0" + } +} diff --git a/infrastructure/services/props.json b/backend/props.json similarity index 100% rename from infrastructure/services/props.json rename to backend/props.json diff --git a/infrastructure/services/public_key b/backend/public_key similarity index 98% rename from infrastructure/services/public_key rename to backend/public_key index 7cb399d7..3aef10fc 100644 --- a/infrastructure/services/public_key +++ b/backend/public_key @@ -1,19 +1,19 @@ ------BEGIN CERTIFICATE----- -MIIDATCCAemgAwIBAgIJRppYVkre6ervMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV -BAMTE2NhcnBhbC5hdS5hdXRoMC5jb20wHhcNMTgwNjAyMDMyOTA5WhcNMzIwMjA5 -MDMyOTA5WjAeMRwwGgYDVQQDExNjYXJwYWwuYXUuYXV0aDAuY29tMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqvdMuabJiGr2ntuurtTZB/Mu6GNVyBQN -tnDWjktZIkPKqDiIyl2xxbufIpIw4qEQjOUlJDg4YSi8K9T/mCv+wRes65cjQcw2 -x0TNlZtdZ6vG8luWVWLvNE+DjqmsdMIgiLNirdKzNL3Ku2VvuFFvwcBAmXsqORT8 -LcLOE2etTqdPuBdC0sGzAMncf+3J+BxLoZNCpxJUsh1vbw6mz6/TJjGmmBzOC70o -LyXX9K+OevsW0bLkZeOJZAsGxDWDJ8B31TOarzvko66IlGukUSmmcg/PPiWdBjp5 -IHdjjSjKVdApVrZmltjR7glh3RswVrvqEKGa7dH62928alo2yi0zRwIDAQABo0Iw -QDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTqR7LijtYwkBuqB8txw+TDugz+ -6TAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAC9tL9V5NOs1OFkg -wEzRynYByZZpij2YHhZmnJxuIhdHKBf2+/AXcq4zN78y97D6lfdXVueJrB5OSm3R -6yhffgY7JSURIE96LfEjoh63Y7Viy1Pdk4A9aYWvpoAjlnIb+s3vg+13ChH4b5p5 -m4w3hOU8jJV2VoGooiTAWeHJsUYUPsMLneVKV7wv8Wl7sgLvO+xeRKpa+Sc71Cym -dLGt3NP+EP9zhFRPxpaCIW3rsMnEgSIuekFmVqzv7exMy+srY2V3p0uqiCHdjDQo -ZoumDRlverqMTWICzZsCRRpT4XcM4NlUMLw+zkQtymmDJGIBkZ7pxF+OfQXLRCHr -bOPxM6M= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDATCCAemgAwIBAgIJRppYVkre6ervMA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNV +BAMTE2NhcnBhbC5hdS5hdXRoMC5jb20wHhcNMTgwNjAyMDMyOTA5WhcNMzIwMjA5 +MDMyOTA5WjAeMRwwGgYDVQQDExNjYXJwYWwuYXUuYXV0aDAuY29tMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqvdMuabJiGr2ntuurtTZB/Mu6GNVyBQN +tnDWjktZIkPKqDiIyl2xxbufIpIw4qEQjOUlJDg4YSi8K9T/mCv+wRes65cjQcw2 +x0TNlZtdZ6vG8luWVWLvNE+DjqmsdMIgiLNirdKzNL3Ku2VvuFFvwcBAmXsqORT8 +LcLOE2etTqdPuBdC0sGzAMncf+3J+BxLoZNCpxJUsh1vbw6mz6/TJjGmmBzOC70o +LyXX9K+OevsW0bLkZeOJZAsGxDWDJ8B31TOarzvko66IlGukUSmmcg/PPiWdBjp5 +IHdjjSjKVdApVrZmltjR7glh3RswVrvqEKGa7dH62928alo2yi0zRwIDAQABo0Iw +QDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTqR7LijtYwkBuqB8txw+TDugz+ +6TAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAC9tL9V5NOs1OFkg +wEzRynYByZZpij2YHhZmnJxuIhdHKBf2+/AXcq4zN78y97D6lfdXVueJrB5OSm3R +6yhffgY7JSURIE96LfEjoh63Y7Viy1Pdk4A9aYWvpoAjlnIb+s3vg+13ChH4b5p5 +m4w3hOU8jJV2VoGooiTAWeHJsUYUPsMLneVKV7wv8Wl7sgLvO+xeRKpa+Sc71Cym +dLGt3NP+EP9zhFRPxpaCIW3rsMnEgSIuekFmVqzv7exMy+srY2V3p0uqiCHdjDQo +ZoumDRlverqMTWICzZsCRRpT4XcM4NlUMLw+zkQtymmDJGIBkZ7pxF+OfQXLRCHr +bOPxM6M= +-----END CERTIFICATE----- diff --git a/backend/readme.md b/backend/readme.md new file mode 100644 index 00000000..501e24ce --- /dev/null +++ b/backend/readme.md @@ -0,0 +1,54 @@ +## Initial Setup + +### Database +The project holds sql scripts that can be used to incrementally apply changes to the database. +You can therefore also use this scripts to initialize your local database. +To run it: +``` +npm run refresh-db +``` +By default it will try to connect to a local mysql database with username `root` and password `admin`. If you want to change those configurations you can set as environment variable, for instance: +``` +MYSQL_USER=myuser MYSQL_PW=myPassword MYSQL_HOST=myHost MYSQL_PORT=3316 MYSQL_DB=myDB npm run refresh-db +``` + +### Running the application locally + +The application was designed to work with AWS lambdas. Although an *expressJs* layer was added to be able to run the application locally. To start it: + +``` +npm start +``` +**or** to be able to debug it: +``` +node ./backend/src/test/expressApis.js +``` + +**Attention**: The application and tests make use of the database, therefore make sure that you have your environment variables set to configure them to run against the right mysql instance. + + +### Running the tests +In the `backend/` directory run: + +``` +npm test +``` + +### Deploying with serverless +Run the following in the ./infrastructure/services directory to create the API Gateway config and lambdas: +``` +npm install +npm install serverless -g +serverless deploy +``` + +### API Gateway Custom Domain +Services are published on api.carpal.org.au. + +This is achieved using: +1. An SSL certificate in North Virginia for the domain +2. A configuration in API Gateway for the custom domain as below: +![image](api-gateway-custom-doman.png) +3. A Route53 entry for api.carpal.org.au is ALIAS'd (like a hidden CNAME) to the above Target Domain Name. + +Ref: https://docs.aws.amazon.com/console/apigateway/custom-domains diff --git a/infrastructure/services/serverless.yml b/backend/serverless.yml similarity index 91% rename from infrastructure/services/serverless.yml rename to backend/serverless.yml index 3f6665c0..c164d29e 100644 --- a/infrastructure/services/serverless.yml +++ b/backend/serverless.yml @@ -9,8 +9,11 @@ provider: timeout: 30 environment: AUTH0_CLIENT_ID: ${file(./props.json):AUTH0_CLIENT_ID} + MYSQL_HOST: carpal.cttgjqpjknhf.ap-southeast-2.rds.amazonaws.com MYSQL_PW: ${file(./secrets.json):MYSQL_PW} MYSQL_PORT: ${file(./secrets.json):MYSQL_PORT} + MYSQL_USER: carpaladmin + DOMAIN: carpal.org.au AUTH0_CLIENT_PUBLIC_KEY: ${file(./public_key)} vpc: @@ -86,6 +89,11 @@ functions: path: map method: get cors: true + notify: + handler: notify + events: + - schedule: rate(2 hours) + - schedule: cron(0 12 * * ? *) resources: Resources: GatewayResponse: diff --git a/infrastructure/services/auth/auth.js b/backend/src/main/auth/auth.js similarity index 100% rename from infrastructure/services/auth/auth.js rename to backend/src/main/auth/auth.js diff --git a/infrastructure/services/auth/loggedin.js b/backend/src/main/auth/loggedin.js similarity index 100% rename from infrastructure/services/auth/loggedin.js rename to backend/src/main/auth/loggedin.js diff --git a/backend/src/main/controller/awsLambdaApis.js b/backend/src/main/controller/awsLambdaApis.js new file mode 100644 index 00000000..e05c7be2 --- /dev/null +++ b/backend/src/main/controller/awsLambdaApis.js @@ -0,0 +1,16 @@ +const CreateRideService = require('../rides/CreateRideService'); +const ListRidesService = require('../rides/ListRidesService'); +const FindOneRideService = require('../rides/FindOneRideService'); +const DatabaseManager = require('../database/DatabaseManager'); +const AwsLambdaRideApis = require('../rides/aws/AwsLambdaRideApis'); +const databaseManager = new DatabaseManager(); + +const createRideService = new CreateRideService(databaseManager); +const listRidesService = new ListRidesService(databaseManager); +const findOneRideService = new FindOneRideService(databaseManager); + +const rides = new AwsLambdaRideApis(createRideService, listRidesService, findOneRideService); + +module.exports = { + rides: rides +}; \ No newline at end of file diff --git a/backend/src/main/database/DatabaseManager.js b/backend/src/main/database/DatabaseManager.js new file mode 100644 index 00000000..fa3b4eb0 --- /dev/null +++ b/backend/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 || 'localhost', + port: process.env.MYSQL_PORT || 3306, + user: process.env.MYSQL_USER || 'root', + password: process.env.MYSQL_PW || 'admin', + database: process.env.MYSQL_DB || '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) { + let closePromise = this.closeConnection(connection); + return closePromise.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/backend/src/main/database/RefreshDatabase.js b/backend/src/main/database/RefreshDatabase.js new file mode 100644 index 00000000..46f731e1 --- /dev/null +++ b/backend/src/main/database/RefreshDatabase.js @@ -0,0 +1,30 @@ +const fs = require('fs'); +const path = require('path'); + +const changeSet = [ + '2018-08-04-1-create-database.sql', + '2018-08-04-2-create-location-table.sql', + '2018-08-04-3-create-rides-table.sql', + '2018-08-04-4-create-driver-table.sql', + '2018-08-04-5-create-driver_car-table.sql', + '2018-08-04-6-create-driver_ride-table.sql' +]; + +class RefreshDatabase { + constructor(databaseManager) { + this.databaseManager = databaseManager; + } + + async executeAll() { + for (let fileName of changeSet) { + await this.execute(fileName); + } + } + + async execute(fileName) { + let sql = fs.readFileSync(path.resolve(__dirname, './changes/' + fileName)).toString().trim(); + return this.databaseManager.query(sql); + } +} + +module.exports = RefreshDatabase; \ No newline at end of file diff --git a/backend/src/main/database/changes/2018-08-04-1-create-database.sql b/backend/src/main/database/changes/2018-08-04-1-create-database.sql new file mode 100644 index 00000000..95e1904f --- /dev/null +++ b/backend/src/main/database/changes/2018-08-04-1-create-database.sql @@ -0,0 +1 @@ +CREATE DATABASE IF NOT EXISTS carpal; \ No newline at end of file diff --git a/backend/src/main/database/changes/2018-08-04-2-create-location-table.sql b/backend/src/main/database/changes/2018-08-04-2-create-location-table.sql new file mode 100644 index 00000000..884ad0a3 --- /dev/null +++ b/backend/src/main/database/changes/2018-08-04-2-create-location-table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS carpal.location ( + id INT(11), + location GEOMETRY NOT NULL, + suburb TEXT, + placeName TEXT, + postCode VARCHAR(10) +); \ No newline at end of file diff --git a/backend/src/main/database/changes/2018-08-04-3-create-rides-table.sql b/backend/src/main/database/changes/2018-08-04-3-create-rides-table.sql new file mode 100644 index 00000000..c01c83f1 --- /dev/null +++ b/backend/src/main/database/changes/2018-08-04-3-create-rides-table.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS carpal.rides ( + id INT(11) AUTO_INCREMENT PRIMARY KEY, + client VARCHAR(255), + facilitatorEmail VARCHAR(255), + pickupTimeAndDateInUTC DATETIME, + locationFrom POINT, + locationTo POINT, + fbLink VARCHAR(255), + driverGender VARCHAR(10), + carType VARCHAR(255), + status ENUM('OPEN','CONFIRMED','ENDED','CANCELLED') DEFAULT 'OPEN', + deleted TINYINT(4), + suburbFrom VARCHAR(255), + placeNameFrom VARCHAR(255), + postCodeFrom VARCHAR(10), + suburbTo VARCHAR(255), + placeNameTo VARCHAR(255), + postCodeTo VARCHAR(10), + description VARCHAR(1024) +); + diff --git a/backend/src/main/database/changes/2018-08-04-4-create-driver-table.sql b/backend/src/main/database/changes/2018-08-04-4-create-driver-table.sql new file mode 100644 index 00000000..18ad69f8 --- /dev/null +++ b/backend/src/main/database/changes/2018-08-04-4-create-driver-table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS carpal.driver ( + id INT(11) PRIMARY KEY, + name VARCHAR(255), + phoneNumber VARCHAR(20), + facebookUrl VARCHAR(255) +); + diff --git a/backend/src/main/database/changes/2018-08-04-5-create-driver_car-table.sql b/backend/src/main/database/changes/2018-08-04-5-create-driver_car-table.sql new file mode 100644 index 00000000..747b7462 --- /dev/null +++ b/backend/src/main/database/changes/2018-08-04-5-create-driver_car-table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS carpal.driver_car ( + id INT(11) PRIMARY KEY, + driver_id INT(11), + carModel VARCHAR(255), + color VARCHAR(255), + licensePlateNumber VARCHAR(255), + FOREIGN KEY (driver_id) REFERENCES driver(id) +); \ No newline at end of file diff --git a/backend/src/main/database/changes/2018-08-04-6-create-driver_ride-table.sql b/backend/src/main/database/changes/2018-08-04-6-create-driver_ride-table.sql new file mode 100644 index 00000000..44aee2b2 --- /dev/null +++ b/backend/src/main/database/changes/2018-08-04-6-create-driver_ride-table.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS carpal.driver_ride ( + id INT(11) PRIMARY KEY, + driver_id INT(11), + ride_id INT(11), + confirmed TINYINT(1), + notified24h TINYINT(1), + notified5m TINYINT(1), + FOREIGN KEY (driver_id) REFERENCES driver(id), + FOREIGN KEY (ride_id) REFERENCES rides(id) +); \ No newline at end of file diff --git a/backend/src/main/database/index.js b/backend/src/main/database/index.js new file mode 100644 index 00000000..5c3c6cd3 --- /dev/null +++ b/backend/src/main/database/index.js @@ -0,0 +1,11 @@ +const DatabaseManager = require("./DatabaseManager"); +const RefreshDatabase = require("./RefreshDatabase"); +const refreshDatabase = new RefreshDatabase(new DatabaseManager()); + +process.on('unhandledRejection', error => { + console.log('unhandledRejection', error); +}); + + +refreshDatabase.executeAll(refreshDatabase) + .catch(e => console.log(e)); \ No newline at end of file diff --git a/backend/src/main/notification/notify.js b/backend/src/main/notification/notify.js new file mode 100644 index 00000000..d67c878f --- /dev/null +++ b/backend/src/main/notification/notify.js @@ -0,0 +1,37 @@ +exports.handler = function (event, context, callback) { + + let AWS = require('aws-sdk'); + + // Create chron job + // search rides with time below 24 hours + // send sms to each one + // update entry to say that driver was notified + + // create amazon account for hillscarpal + + sendSMS(); + + +}; + +function sendSMS() { + let sns = new AWS.SNS(); + sns.publish({ + Message: 'Hello World', + PhoneNumber: '+610123456789', + MessageStructure: 'text' + }, function (err, data) { + console.log("test2") + if (err) { + console.log(err.stack); + } + + console.log('push sent'); + + callback(null, { + "statusCode": 200, + "headers": {"Date": new Date()}, + "body": "" + }); + }); +} \ No newline at end of file diff --git a/backend/src/main/rides/Coordinates.js b/backend/src/main/rides/Coordinates.js new file mode 100644 index 00000000..a2f8b7df --- /dev/null +++ b/backend/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/backend/src/main/rides/CreateRideService.js b/backend/src/main/rides/CreateRideService.js new file mode 100644 index 00000000..db4ae7a1 --- /dev/null +++ b/backend/src/main/rides/CreateRideService.js @@ -0,0 +1,52 @@ +'use strict'; + +const jsonValidator = require('jsonschema'); +const rideSchema = require('../schema/ride.json'); +const RideStatus = require('./RideStatus'); +const RideRepository = require('./RideRepository'); +const RideMapper = require('./RideMapper'); + +class CreateRideService { + + 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 Promise.reject(validationError); + } + + const payload = RideMapper.dtoToEntity(rideObject, loginData && loginData.email); + payload.status = RideStatus.OPEN; + payload.deleted = '0'; + + return this._rideRepository.create(payload, connection); + } + + _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 = CreateRideService; \ No newline at end of file diff --git a/backend/src/main/rides/FindOneRideService.js b/backend/src/main/rides/FindOneRideService.js new file mode 100644 index 00000000..f53981f4 --- /dev/null +++ b/backend/src/main/rides/FindOneRideService.js @@ -0,0 +1,38 @@ +'use strict'; + +const RideRepository = require('./RideRepository'); +const RidesMapper = require('./RideMapper'); + +class FindOneRideService { + + constructor(databaseManager) { + this._databaseManager = databaseManager; + this._rideRepository = new RideRepository(databaseManager); + } + + findOne(id, loginData) { + if (loginData.role === 'driver') { + return Promise.resolve(null); + } + + const connection = this._databaseManager.createConnection(); + return this._findOne(id, loginData, connection) + .finally(() => this._databaseManager.closeConnection(connection)); + } + + _findOne(id, loginData, connection) { + let jsonQuery = this._createQuery(id, loginData); + return this._rideRepository.findOne(jsonQuery, connection) + .then(item => RidesMapper.entityToDto(item)); + } + + _createQuery(id, loginData) { + return { + id: id, + facilitatorId: loginData.role === 'facilitator' ? loginData.email : undefined, + includePickupTimeInPast: true + }; + } +} + +module.exports = FindOneRideService; \ No newline at end of file diff --git a/backend/src/main/rides/ListRidesService.js b/backend/src/main/rides/ListRidesService.js new file mode 100644 index 00000000..48b20115 --- /dev/null +++ b/backend/src/main/rides/ListRidesService.js @@ -0,0 +1,39 @@ +'use strict'; + +const RideRepository = require('./RideRepository'); +const RidesMapper = require('./RideMapper'); + +class ListRidesService { + + constructor(databaseManager) { + this._databaseManager = databaseManager; + this._rideRepository = new RideRepository(databaseManager); + } + + listRides(queryParams, loginData) { + const connection = this._databaseManager.createConnection(); + + return this._listRides(queryParams, loginData, connection) + .then(rides => rides.map(RidesMapper.entityToDto)) + .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', + facilitatorId: loginData.role === 'facilitator' ? loginData.email : undefined, + }; + } +} + +module.exports = ListRidesService; \ No newline at end of file diff --git a/backend/src/main/rides/RideMapper.js b/backend/src/main/rides/RideMapper.js new file mode 100644 index 00000000..0da9ff32 --- /dev/null +++ b/backend/src/main/rides/RideMapper.js @@ -0,0 +1,66 @@ +class RideMapper { + static entityToDto(ride) { + if(!ride){ + return null; + } + return { + "client": ride.client, + "pickupTimeAndDateInUTC": new Date(ride.pickupTimeAndDateInUTC), + "locationFrom": { + "latitude": ride.locationFrom.x, + "longitude": ride.locationFrom.y, + "suburb": ride.suburbFrom, + "postcode": ride.postCodeFrom, + "placeName": ride.placeNameFrom + }, + "locationTo": { + "latitude": ride.locationTo.x, + "longitude": ride.locationTo.y, + "suburb": ride.suburbTo, + "postcode": ride.postCodeTo, + "placeName": ride.placeNameTo + }, + "fbLink": ride.fbLink, + "driverGender": ride.driverGender, + "carType": ride.carType, + "status": ride.status, + "deleted": parseInt(ride.deleted + ''), + "facilitatorId": ride.facilitatorId || ride.facilitatorEmail, + "description": ride.description, + "id": ride.id + } + } + + static dtoToEntity(ride, facilitatorId) { + if(!ride){ + return null; + } + return { + client: `${ride.client}`, + pickupTimeAndDateInUTC: new Date(ride.pickupTimeAndDateInUTC), + locationFrom: { + latitude: ride.locationFrom.latitude, + longitude: ride.locationFrom.longitude, + suburb: ride.locationFrom.suburb, + placeName: ride.locationFrom.placeName, + postcode: ride.locationFrom.postcode, + }, + locationTo: { + latitude: ride.locationTo.latitude, + longitude: ride.locationTo.longitude, + suburb: ride.locationTo.suburb, + placeName: ride.locationTo.placeName, + postcode: ride.locationTo.postcode, + }, + fbLink: ride.fbLink, + driverGender: ride.driverGender, + carType: ride.carType, + status: ride.status, + deleted: 0, + facilitatorId: facilitatorId, + description: ride.description + } + } +} + +module.exports = RideMapper; diff --git a/backend/src/main/rides/RideRepository.js b/backend/src/main/rides/RideRepository.js new file mode 100644 index 00000000..520edab7 --- /dev/null +++ b/backend/src/main/rides/RideRepository.js @@ -0,0 +1,137 @@ +'use strict'; + +const moment = require('moment'); + +class RideRepository { + constructor(databaseManager) { + this._databaseManager = databaseManager; + } + + 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.facilitatorId), + 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), + escape(ride.deleted), + escape(ride.locationFrom.suburb), + escape(ride.locationFrom.placeName), + escape(ride.locationFrom.postcode), + escape(ride.locationTo.suburb), + escape(ride.locationTo.placeName), + escape(ride.locationTo.postcode), + escape(ride.description) + ].join(',')})`; + console.log(query); + + return this._databaseManager.query(query, connection); + } + + update(id, ride, connection) { + if (!id) { + throw new Error('No id specified when updating ride.'); + } + 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 = `UPDATE rides SET client = ${escape(ride.client)}, + facilitatorEmail = ${escape(ride.facilitatorId)}, + pickupTimeAndDateInUTC = ${escape(moment(ride.pickupTimeAndDateInUTC).format('YYYY-MM-DD HH:mm:ss'))}, + locationFrom = ${locationFrom}, + locationTo = ${locationTo}, + fbLink = ${escape(ride.fbLink)}, + driverGender = ${escape(ride.driverGender)}, + carType = ${escape(ride.carType)}, + status = ${escape(ride.status)}, + deleted = ${ride.deleted}, + suburbFrom = ${escape(ride.locationFrom.suburb)}, + placeNameFrom = ${escape(ride.locationFrom.placeName)}, + postCodeFrom = ${escape(ride.locationFrom.postcode)}, + suburbTo = ${escape(ride.locationTo.suburb)}, + placeNameTo = ${escape(ride.locationTo.placeName)}, + postCodeTo = ${escape(ride.locationTo.postcode)}, + description = ${escape(ride.description)} + WHERE + id = ${id}`; + console.log(query); + + return this._databaseManager.query(query, connection); + } + + findOne(jsonQuery, connection) { + return this.list(jsonQuery, connection) + .then(results => results[0] || null); + } + + /** + * @param jsonQuery: + * toLongitude {number} + * toLatitude {number} + * fromLongitude {number} + * fromLatitude {number} + * driverGenders {string[]} + * facilitatorId {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.id) { + where.push(`id = ${jsonQuery.id}`) + } + 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.facilitatorId) { + where.push(`facilitatorEmail = ${escape(jsonQuery.facilitatorId)}`) + } + + let query = `SELECT * FROM rides ${where.length ? ' WHERE ' + where.join(' AND ') : ''} ORDER BY pickupTimeAndDateInUTC ASC;`; + console.log(query); + return this._databaseManager.query(query, connection) + .then(rides => + rides.map(ride => { + // Workaround to map facilitatorEmail from database to the facilitatorId in the entity + ride.facilitatorId = ride.facilitatorId || ride.facilitatorEmail; + delete ride.facilitatorEmail; + return ride; + }) + ) + } +} + +module.exports = RideRepository; diff --git a/backend/src/main/rides/RideStatus.js b/backend/src/main/rides/RideStatus.js new file mode 100644 index 00000000..6bba9867 --- /dev/null +++ b/backend/src/main/rides/RideStatus.js @@ -0,0 +1,8 @@ +const RideStatus = { + OPEN: 'OPEN', + CONFIRMED: 'CONFIRMED', + ENDED: 'ENDED', + CANCELLED: 'CANCELLED', +}; + +module.exports = RideStatus; \ No newline at end of file diff --git a/backend/src/main/rides/UpdateRideService.js b/backend/src/main/rides/UpdateRideService.js new file mode 100644 index 00000000..08f331f5 --- /dev/null +++ b/backend/src/main/rides/UpdateRideService.js @@ -0,0 +1,60 @@ +'use strict'; + +const jsonValidator = require('jsonschema'); +const rideSchema = require('../schema/ride.json'); +const RideRepository = require('./RideRepository'); +const RideMapper = require('./RideMapper'); + +class UpdateRideService { + + constructor(databaseManager) { + this._databaseManager = databaseManager; + this._rideRepository = new RideRepository(databaseManager); + } + + updateRide(id, ride, loginData) { + const connection = this._databaseManager.createConnection(); + + let updatePromise = this._updateRide(id, ride, loginData, connection); + return updatePromise.finally(() => this._databaseManager.closeConnection(connection)); + } + + _updateRide(id, rideObject, loginData, connection) { + rideObject.datetime = new Date().getTime(); + + let validationError = this._validate(rideObject); + if (validationError) { + return Promise.reject(validationError); + } + + return this._rideRepository + .findOne({ + id: id, + facilitatorId: loginData.role === 'facilitator' ? loginData.email : undefined, + includePickupTimeInPast: true + }) + .then(ride => { + if (!ride || ride.deleted) { + return null; + } + let rideEntity = RideMapper.dtoToEntity(rideObject); + rideEntity.facilitatorId = ride.facilitatorId; + return this._rideRepository.update(ride.id, rideEntity, connection); + }); + } + + _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 = UpdateRideService; \ No newline at end of file diff --git a/backend/src/main/rides/aws/AwsLambdaRideApis.js b/backend/src/main/rides/aws/AwsLambdaRideApis.js new file mode 100644 index 00000000..b2fa5e99 --- /dev/null +++ b/backend/src/main/rides/aws/AwsLambdaRideApis.js @@ -0,0 +1,47 @@ +const decodeJwt = require('../../utils/jwt').decodeJwt; + +class AwsLambdaRideApis { + constructor(createRideService, + listRidesService, + findOneRideService, + updateRideService) { + this.createRideService = createRideService; + this.listRidesService = listRidesService; + this.findOneRideService = findOneRideService; + this.updateRideService = updateRideService; + } + + create(event, context, callback) { + let loginData = decodeJwt(event); + return this.createRideService.createRide(JSON.parse(event.body), loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + } + + update(event, context, callback) { + const loginData = decodeJwt(event); + const ride = JSON.parse(event.body); + const id = event.pathParameters.id; + return this.updateRideService.updateRide(id, ride, loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + } + + list(event, context, callback) { + let loginData = decodeJwt(event); + let queryParams = event.queryStringParameters || {}; + return this.listRidesService.listRides(queryParams, loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + } + + findOne(event, context, callback) { + let loginData = decodeJwt(event); + let pathParams = event.pathParameters || {}; + return this.findOneRideService.findOne(pathParams.id, loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + } +} + +module.exports = AwsLambdaRideApis; \ No newline at end of file diff --git a/infrastructure/services/schema/ride.json b/backend/src/main/schema/ride.json similarity index 100% rename from infrastructure/services/schema/ride.json rename to backend/src/main/schema/ride.json diff --git a/backend/src/main/utils/jwt.js b/backend/src/main/utils/jwt.js new file mode 100644 index 00000000..27586656 --- /dev/null +++ b/backend/src/main/utils/jwt.js @@ -0,0 +1,26 @@ +const jsonwebtoken = require('jsonwebtoken'); + +module.exports.decodeJwt = (event) => { + if (!event.headers.Authorization) { + return; + } + const tokenValue = event + .headers + .Authorization + .split(' ')[1]; + + try { + const domain = process.env.DOMAIN || 'carpal.org.au'; + const decodedToken = jsonwebtoken.decode(tokenValue); + const claims = {}; + claims.email = decodedToken[`https://${domain}/email`]; + claims.role = decodedToken[`https://${domain}/role`]; + if (claims.role === 'driver') { + claims.driverGender = decodedToken[`https://${domain}/gender`]; + claims.car = decodedToken[`https://${domain}/car`]; + } + return claims; + } catch (err) { + console.log('catch error. Invalid token', err); + } +} \ No newline at end of file diff --git a/infrastructure/services/utils/ping.js b/backend/src/main/utils/ping.js similarity index 100% rename from infrastructure/services/utils/ping.js rename to backend/src/main/utils/ping.js diff --git a/backend/src/test/RandomUtils.js b/backend/src/test/RandomUtils.js new file mode 100644 index 00000000..da9e0e74 --- /dev/null +++ b/backend/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/backend/src/test/auth/ExpressAuthApis.js b/backend/src/test/auth/ExpressAuthApis.js new file mode 100644 index 00000000..bd64c332 --- /dev/null +++ b/backend/src/test/auth/ExpressAuthApis.js @@ -0,0 +1,122 @@ +const moment = require('moment'); +const fs = require('fs'); +const path = require('path'); +const jwt = require('jsonwebtoken'); + +function toBase64(value){ + return Buffer.from(value).toString('base64') +} + +class ExpressAuthApis { + constructor(app) { + this.app = app; + this.app.get('/authorize', this.auth.bind(this)); + this.app.get('/userinfo', this.userinfo.bind(this)); + this.app.get('/authcheck', this.authcheck.bind(this)); + this.app.get('/.well-known/jwks.json', this.wellKnown.bind(this)); + } + + wellKnown(req, res) { + let expiry = moment().add(100, 'd'); + console.log(req.query); + let jwks = fs.readFileSync(path.resolve(__dirname, '../config/express/certs/jwks.json')); + res.status(200).send(JSON.parse(jwks)); + } + + auth(req, res) { + let queryParams = req.query || {}; + let nonce = queryParams.nonce; + let state = queryParams.state; + let expiry = moment().add(100, 'd'); + let date = expiry.toDate(); + let host = req.get('host'); + + let urls = { + driver: this.extractUrl(queryParams, expiry, state, date, host, this._getDriver()), + admin: this.extractUrl(queryParams, expiry, state, date, host, this._getAdmin()), + falicitator: this.extractUrl(queryParams, expiry, state, date, host, this._getFacilitator()) + }; + + let redirectNow = queryParams.loginAs ? `window.location = "${urls[queryParams.loginAs]}"` : ''; + res.status(200).send(` + + +
+ + + `); + } + + extractUrl(queryParams, expiry, state, date, host, userInfo) { + let {accessToken, jwtToken} = this._authAs(queryParams, host, userInfo, expiry); + let url = `${queryParams.redirect_uri}#access_token=${accessToken}&id_token=${jwtToken}&refresh_token=6789&state=${state}&expires_in=${date.getTime()}`; + return url; + } + + _authAs(queryParams, host, userInfo, expiry) { + let payload = this._completeJWT(userInfo, host, expiry.toDate(), queryParams.nonce); + + let accessToken = userInfo.role; + let cert = fs.readFileSync(path.resolve(__dirname, '../config/express/certs/private.key')); + let jwtToken = jwt.sign(payload, cert, {algorithm: 'RS256'}); + return {accessToken, jwtToken}; + } + + _completeJWT(data, host, expiryDate, nonce){ + let result = { + "nonce": nonce.replace("@", "~"), + "iss": `https://${host}/`, + "aud": '1234', + "exp": expiryDate.getTime() / 1000, + "nbf": new Date().getTime() / 1000, + ...this._uriBased(data, `https://${host}/`) + }; + return result; + } + + _uriBased(data, uri){ + let result = {}; + Reflect.ownKeys(data).forEach(k => result[uri + k] = data[k]); + return result; + } + + _getDriver(uri){ + let data = require('../users/driver.json'); + return uri ? this._uriBased(data, uri) : data; + } + + _getAdmin(uri){ + let data = require('../users/admin.json'); + return uri ? this._uriBased(data, uri) : data; + } + + _getFacilitator(uri){ + let data = require('../users/facilitator.json'); + return uri ? this._uriBased(data, uri) : data; + } + + userinfo(req, res){ + let uri = req.get('origin') + '/'; + let tokens = (req.get('authorization') || '').split(' '); + switch (tokens[1]){ + case 'admin': + return res.status(200).send(this._getAdmin(uri)); + case 'facilitator': + return res.status(200).send(this._getFacilitator(uri)); + default: + return res.status(200).send(this._getDriver(uri)); + } + } + + authcheck(req, res){ + res.status(200).send({ name: 'Foo' }); + } + +} + +module.exports = ExpressAuthApis; \ No newline at end of file diff --git a/backend/src/test/config/express/certs/certificate.p12 b/backend/src/test/config/express/certs/certificate.p12 new file mode 100644 index 00000000..59e3a11f Binary files /dev/null and b/backend/src/test/config/express/certs/certificate.p12 differ diff --git a/backend/src/test/config/express/certs/certificate.pem b/backend/src/test/config/express/certs/certificate.pem new file mode 100644 index 00000000..8bd84ba7 --- /dev/null +++ b/backend/src/test/config/express/certs/certificate.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC9jCCAd4CCQDfAD2g+Y7RJzANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQGEwJh +dTEMMAoGA1UECAwDbnN3MQ8wDQYDVQQHDAZzeWRuZXkxDzANBgNVBAoMBmNhcnBh +bDAeFw0xODA4MjYwODI0NDBaFw0xOTA4MjYwODI0NDBaMD0xCzAJBgNVBAYTAmF1 +MQwwCgYDVQQIDANuc3cxDzANBgNVBAcMBnN5ZG5leTEPMA0GA1UECgwGY2FycGFs +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8HoBueljoBmBbe54tbu5 +ge0SFtebybklIKvmghgqR/Busi4azNwtGYBvvA0Ibodi/OGFJPvH2L89RuwWpxvp +Nb74NHEGlwN8QNq7jUSLrcgZ13zc9y4N030Xis4YNGO/dnsweGfqG/I3LjEl/ZJV +fDf5c+cZvFep7Z3OaPz9UawE5MkCA+GT5hycTLYhLNu4ODQfTJu3YmMHI5J0GyaF ++80Rj3vZ4XOwAgIlQk3uFVOrKswFvN/WM7HZTLwW6gX3SbG7LaCEE1toBoEtm3Nf +vKq2QaAe1AcjXWGgF7UEjzZ0B1kkp/LyV/fJkMxeQL5r3pz8tBIJzH478t3IuU22 +HQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBg5xdJx/0weyC5FXlc4i5cajs3ziEX +oGr704AgK3A+DNPkNPy5ZwfG7Axz0PSzrLWRAWhjnAGC4x+AcdGJtkoZ6/RfbkVO +s0cI7dYNLlGbBIwyiZnC36CtMYJ1blUBPZGF7hEdQER2Bq0yfgju9yr6YrpGwuUo +Kwnsl/nj03MnwbUc75nIPZRDLJmx7QsoTJm5R88l3MjCqcD6xSA9pX9wugnfKCNq +RRXV2ZRxiUsgAhcMEkyglYrawZ4gQi5Rp6/jNNjq0c5ARBYCjcZcBkk2buuKAZjv +AkXAAT/3E01s3af3+XXTEkfTj4/f84FloutSUVqXuLgNIYEztS9fHVho +-----END CERTIFICATE----- diff --git a/backend/src/test/config/express/certs/jwks.json b/backend/src/test/config/express/certs/jwks.json new file mode 100644 index 00000000..816546a5 --- /dev/null +++ b/backend/src/test/config/express/certs/jwks.json @@ -0,0 +1,17 @@ +{ + "keys": [ + { + "kty": "RSA", + "n": "wkRco_VjxfT_Zp2FCN-WnKTls9GaoRpUcrvwukH65u-Q0R9JUdjJiUQHxCgkBAOzEmsIeeEYRW19-E3Yk7fYzftbaD5cC9nZyf3jA9fF9kbbnD2wPFtk2Go9g5-TtWVKB4r_dd9D88tNAfTpa4b20uh9Uj92E6H0-fw7ZSg4kkkMv7mb0utRtsERi8-6hAu_kv3b7aayryBYVxKwwgkuXJ0SHJ3A9o1XL74i8l6vuJdcgdfSM8Sn9GYFbezuAR6JBbTdi55kSYf06rJqiUH-1Ep3yRv7MgWwWe8W_TRVYjGgbwJZxTdjE3Js-hU8q8mp6_cQtgjJ9wY2EjiliYPV_Vv9v1NfWFf-lodqe2eJCcfYKFDep3mUeV4pR_TJypbRWtlcnyuLNeSeL4ubpHZ8jWKWgHJ5iZd2O2-s16B_tEfRZ0cCEYNufPGbZzPGsw6YrhKJ-2kxAkrn7FJPK3NXMI3imEDQxCiEJl07DZm5Fg-cQsG4F7s_V8AG4s_BRTS6BBHtAkKpt9_G6nxWCOWMPdjTbXCHGdh2Ikruk4p-AlevYNuEbhOyGzSyHEc1Jgsu8vSOmffhzw9r5dWjIZ-fzWZTEh0PZDec1b-23Dv9fHnqjqtx3gorRg8cKv5u2KS_JfhbQq7luCZj0Yy8n6cTnYWbzb5m4-fNK-LDbukx0QE", + "e": "AQAB", + "d": "FKbdzlRM6gN0jz0ZomragQaKoMYckRnSdmwCWFqhSKGZHJId4kSQkDYaOUEnKwIVrFSSjxpd0Sulo5rq9jEqLCOXgQp4EI51N6L02ji7A8ZenEp2jMQOgW8x4Oc69aKx7wFYzPgSKj2njxixI8wyOxOaFv9GFyo1d8nXqpZPBUYcw9kAdrirdCksK1fG9TimggLYuXfqMPHYUlFbVxVQx2mvPjmLBOrbpOiqoGdueMomfGssHnK-hB3AYAI6_wV2_ZNPpgMNmD-KytRyvOq2I21TNJ7z4kp2h-kB4aMJnKm363Utg-iycKPOb_uH-p8yoDIgpaljhw4y-1boSikou1nVbLHpSuNtgnUsj4-JxvJHZorEqnizeJiFLECwYa6YVviPEySkIkfMCmfU1NekbHOmQmTCE_Uy5btgOFXCnsfdViU11mRUTQ1PCbVvDcPSMZSKHN2Fhh4u56ckuwLajzsOxYrjkOVEroZv5eW0fydg_zotEkcAyQtyAW90F7iVMcke8IvQPYeeiXjrXmIYvSulxD8Ze-nymQtr7Quru8WLzkUEdbLMS-776JYK4NevDbGqdKLSQbKi9ykU1xg8Cah4RI2A0gTKOZPES1epe0xPRJknvy2Uq8eUaDjBQnGqoAqMPeOWV8YuDF_aJ4UpURLCKttEazKNLdguNFqd1tE", + "p": "8b5rcAFydgFB0yZAy1ATxIaUpUp7GetdgAymQwzH7JnF6ebY0CpSqmVAZoTL-ZfU-FgvaS11b2wkyQFHcPUrtfmHP_Wuada_al_qc9FDvYeqnoNUGjzBN1HKnUBbXlpXv8nbbwI9NW65vC28U0J2w-g9XoDz7TgeD6ZzfkUAb2grV0CIM_qSPNMU0PqdTHcZ4nk6M_416cdU2Xw2iG_otJrVEg2EHRtqCc54LRFE9QCwb93VdH7ndj-hKlNjwkj_rL_R8Pxf09-Jm1Zh4IodjL2CVy7QxHODY1DjrDbcJ6O525Q7XeOA-MLm-HPwE3ltH94Q68b9mgMNqUHaN7EyXQ", + "q": "zbkwtg-jpHbi6ue3NS2WzjW7ILaOucK1f1GzAY-nZF1fCOK70lj0IewhYSh1VViu29TuabwM1YQoIagCdlmIcRAOrFOTfI6v6QsYDVoIzb4aUXXmx6VgbjKrf3hWePLsOGYZie4irfIezLBqKnx4Rwf11pUnG-yxptuk4AkH7v3C5Csl2QIRLMd7AOeNpKcUlr68xtCUJNNdHTE8EBmC79r5VEu2cBEPxlMtRY4mJVwtQOQgJOt3KeK_6LY3TY1o_TmOX4oyl4pbGPQ5ZO_vyBRuwKVeSEI83-QgEmmMKbsgFNSI2P36BBOvrmPTkyFv_-myxoaHW1WfeqcT1iE29Q", + "dp": "OUYbIDtHigu8M7GBcmnzdQFJoGuoBLkNuvPERGh3yWeZS1RlE9SjJzm7604VIXpGe9wwx2N8yjw97t19tpZvl7qZv56OhwbY7PPykSQIP5Qv6URGHb09LcUUEvOXciBHX-oMMh2-sLUeDiZr0vIRP1L7jzNQF2jPPnf6LvVcKAjvE8n3OxFnqj98VBK8R8yD9nMwRfc5gLy4LprONL8GzBtO1esb3OWM1uvy7wKDauSR6L8O_n2-ivaCUYvWO8adxKjhrY2tin4QlRv_Lnqqi5iuk8dEsPaJL3OPrVKGEGIq-4oAErt-5_ENVdnqDEMdhopPSf0oQl_s0agHcmBqBQ", + "dq": "tjsWSB-gYH4jUdwCMOv6Sx3Tbg3obeppJaf0PizHJaOEHHXj1FeRoj7t7oeNMBXHhtmeezDl2Xkgp3eQ_s8eirCjnsjFu68VagS2wJUBeWatH32l-TGoMtVVjyvExYzB7M-cTc8RDy6LU9vtn0b0sE7_2J30r8rCL0EfUokNawmESuNhulIXbqghcFKs6K5MEaBRzndS0zAbqlFDmCtRDjDVbXNH3wtuAssJLjTV24BEYdfyFWIzA3plbrmCUYMy_iSu1jivygb33SnxcVUM_RoEZUfXNYDfNaB5PHYcf5bPu0PtGPIZNuVNWNf6wbj74iBciXj_i8tT4qz_aE2xxQ", + "qi": "6R57N1Qyk2GUPUGJ0Yz50c9ooK5basDNXIKnzYGmmqaZRRF-cMywIwiR5PbfIyAd6MuW4tIoEvvv3VGW6NszpH-c6YhRCFBW4kvuv4lzdKNHpRP57MuFJiXlBe0KKso4G4vMaEnsx7ifNsWVjzNWgmNx06avpng7VTdyZSFbxe_JVRNUbkN5WJtZsh-IXF9L9LMsEE05r-VGnsjAz9ShD9-R-_TUk4Wsqzhm-ILo0NNCHfsW_qvC7kdysRa96tncMeQ45xuj_BuG2l6p8gdIjvGDErvfG4JhrdY3kyX7G_dISH5Do1U8mkIJQ2I-Z49MAN5BrBHmpeCSt8b1KUHzYg", + "alg": "RS256", + "use": "sig" + } + ] +} \ No newline at end of file diff --git a/backend/src/test/config/express/certs/key.pem b/backend/src/test/config/express/certs/key.pem new file mode 100644 index 00000000..ac797abe --- /dev/null +++ b/backend/src/test/config/express/certs/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDwegG56WOgGYFt +7ni1u7mB7RIW15vJuSUgq+aCGCpH8G6yLhrM3C0ZgG+8DQhuh2L84YUk+8fYvz1G +7BanG+k1vvg0cQaXA3xA2ruNRIutyBnXfNz3Lg3TfReKzhg0Y792ezB4Z+ob8jcu +MSX9klV8N/lz5xm8V6ntnc5o/P1RrATkyQID4ZPmHJxMtiEs27g4NB9Mm7diYwcj +knQbJoX7zRGPe9nhc7ACAiVCTe4VU6sqzAW839YzsdlMvBbqBfdJsbstoIQTW2gG +gS2bc1+8qrZBoB7UByNdYaAXtQSPNnQHWSSn8vJX98mQzF5AvmvenPy0EgnMfjvy +3ci5TbYdAgMBAAECggEAQzjX4rBrOQXoOGLBO4wOf1NWCyyaT/mBd3CWLyeyKgn7 +57Mbqsihks9kbJz4Dm2qLiacoYoAg4ZyCrFUY8JZnryThZVS0kQXJ8n9Q7A1m46k +Kqis3CvzkXsWaabS/VIk42nsUrw5pTZAVplGlWuimebLxKqFdzDKP7ItUQvnhDyX +JQ1eIfbdf/Xf+QosNDSMyVLC2z3hnyP/Q+ajRS034wKNmkth7GPIdwPbti5nbVHD +m+QzL/wI13dyp+3tnnNVuVGeelWcqiJ5+5v7wjyuRDmk/POp0ETvX1EYO3xm03Jw +XWwA/kvX13n2ILgHry1gNV/Nqe13+gQWSyUJBzgfwQKBgQD8PfmpR4loz1Xj1p5o +xahJL3MHRaE3LHtl8RNoeUSOILIat2LeFrIHRETnd7/XZ/3knLeexqAT6XD/bkfg +iDuZIazU/+k8QCZX3NXhUvl2ja7zENDQI9O3q5FNKtQpBwOh0rUa6+koJLtQSWBc +7SMp3G8ZITDujyrgb0SnZHRWZwKBgQD0Dyi8A9IG/0DL7ZENKbSmSdKWJ5xqydY2 +cFh9RRb/dFX7It8hBXp+HCRGJaJ3xVp1NFCO7KVdZ/xLO63RmQBqgAHiNe5oP26w +fzeaj644xABUM9GsFxDFSoNOgGB9aBjVHTctI9LtQYUrxVLYwfwTsFk4nz6JXDwZ +ysMa5mJU2wKBgQCo8QFkJWuc7TEDll0moyvhSIxsVHBzubE0R9DN6lrGLpPbXPQ0 +91Jpl4nm1ceBiD7+fRBmoXXZoEJ0cfJmKhhwqaNOTdBy8Cw+MMR3U9GNW7vPRHX2 +0egdXiXFX2gVyoLeQXfW/iZ9IozqaxrFYnZotSEb3aUeQnlfNGbxDlD1BQKBgQDG +w6nLJdlhNXUSIFKnw7WKUEkfXPc4yVSaVXjb3O7T8W0s8MOD4zBVkJnCP1hH7wSy +u354SCaNIekJZHs1XkRGeCYQkxes8yw0tgcU67taI9aYvPMgElDoPL0fq9HpkGPY +/Mg5DRn1fAz3Dudf/OxNJKwlPxvxRprz7nxjZgnjTQKBgDfW/bjsMGn/XFodVsqS +j3CcNLcIqRMw4fhiRTO6AfZL9wMhqglzt2uZuXibPz3+7aN/QeqRm1vWwjC+ckzK +UUHDvbMJqBJ/Wj9bpeVifjxML8yRkhWnxlCmxWywyFNHToBaI0/1FT+jEsYfd1bE +TX6ZfR8fcsygXYzco244LGmY +-----END PRIVATE KEY----- diff --git a/backend/src/test/config/express/certs/private.key b/backend/src/test/config/express/certs/private.key new file mode 100644 index 00000000..8ca6861a --- /dev/null +++ b/backend/src/test/config/express/certs/private.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEAwkRco/VjxfT/Zp2FCN+WnKTls9GaoRpUcrvwukH65u+Q0R9J +UdjJiUQHxCgkBAOzEmsIeeEYRW19+E3Yk7fYzftbaD5cC9nZyf3jA9fF9kbbnD2w +PFtk2Go9g5+TtWVKB4r/dd9D88tNAfTpa4b20uh9Uj92E6H0+fw7ZSg4kkkMv7mb +0utRtsERi8+6hAu/kv3b7aayryBYVxKwwgkuXJ0SHJ3A9o1XL74i8l6vuJdcgdfS +M8Sn9GYFbezuAR6JBbTdi55kSYf06rJqiUH+1Ep3yRv7MgWwWe8W/TRVYjGgbwJZ +xTdjE3Js+hU8q8mp6/cQtgjJ9wY2EjiliYPV/Vv9v1NfWFf+lodqe2eJCcfYKFDe +p3mUeV4pR/TJypbRWtlcnyuLNeSeL4ubpHZ8jWKWgHJ5iZd2O2+s16B/tEfRZ0cC +EYNufPGbZzPGsw6YrhKJ+2kxAkrn7FJPK3NXMI3imEDQxCiEJl07DZm5Fg+cQsG4 +F7s/V8AG4s/BRTS6BBHtAkKpt9/G6nxWCOWMPdjTbXCHGdh2Ikruk4p+AlevYNuE +bhOyGzSyHEc1Jgsu8vSOmffhzw9r5dWjIZ+fzWZTEh0PZDec1b+23Dv9fHnqjqtx +3gorRg8cKv5u2KS/JfhbQq7luCZj0Yy8n6cTnYWbzb5m4+fNK+LDbukx0QECAwEA +AQKCAgAUpt3OVEzqA3SPPRmiatqBBoqgxhyRGdJ2bAJYWqFIoZkckh3iRJCQNho5 +QScrAhWsVJKPGl3RK6Wjmur2MSosI5eBCngQjnU3ovTaOLsDxl6cSnaMxA6BbzHg +5zr1orHvAVjM+BIqPaePGLEjzDI7E5oW/0YXKjV3ydeqlk8FRhzD2QB2uKt0KSwr +V8b1OKaCAti5d+ow8dhSUVtXFVDHaa8+OYsE6tuk6KqgZ254yiZ8aywecr6EHcBg +Ajr/BXb9k0+mAw2YP4rK1HK86rYjbVM0nvPiSnaH6QHhowmcqbfrdS2D6LJwo85v ++4f6nzKgMiClqWOHDjL7VuhKKSi7WdVsselK422CdSyPj4nG8kdmisSqeLN4mIUs +QLBhrphW+I8TJKQiR8wKZ9TU16Rsc6ZCZMIT9TLlu2A4VcKex91WJTXWZFRNDU8J +tW8Nw9IxlIoc3YWGHi7npyS7AtqPOw7FiuOQ5USuhm/l5bR/J2D/Oi0SRwDJC3IB +b3QXuJUxyR7wi9A9h56JeOteYhi9K6XEPxl76fKZC2vtC6u7xYvORQR1ssxL7vvo +lgrg168Nsap0otJBsqL3KRTXGDwJqHhEjYDSBMo5k8RLV6l7TE9EmSe/LZSrx5Ro +OMFCcaqgCow945ZXxi4MX9onhSlREsIq20RrMo0t2C40Wp3W0QKCAQEA8b5rcAFy +dgFB0yZAy1ATxIaUpUp7GetdgAymQwzH7JnF6ebY0CpSqmVAZoTL+ZfU+FgvaS11 +b2wkyQFHcPUrtfmHP/Wuada/al/qc9FDvYeqnoNUGjzBN1HKnUBbXlpXv8nbbwI9 +NW65vC28U0J2w+g9XoDz7TgeD6ZzfkUAb2grV0CIM/qSPNMU0PqdTHcZ4nk6M/41 +6cdU2Xw2iG/otJrVEg2EHRtqCc54LRFE9QCwb93VdH7ndj+hKlNjwkj/rL/R8Pxf +09+Jm1Zh4IodjL2CVy7QxHODY1DjrDbcJ6O525Q7XeOA+MLm+HPwE3ltH94Q68b9 +mgMNqUHaN7EyXQKCAQEAzbkwtg+jpHbi6ue3NS2WzjW7ILaOucK1f1GzAY+nZF1f +COK70lj0IewhYSh1VViu29TuabwM1YQoIagCdlmIcRAOrFOTfI6v6QsYDVoIzb4a +UXXmx6VgbjKrf3hWePLsOGYZie4irfIezLBqKnx4Rwf11pUnG+yxptuk4AkH7v3C +5Csl2QIRLMd7AOeNpKcUlr68xtCUJNNdHTE8EBmC79r5VEu2cBEPxlMtRY4mJVwt +QOQgJOt3KeK/6LY3TY1o/TmOX4oyl4pbGPQ5ZO/vyBRuwKVeSEI83+QgEmmMKbsg +FNSI2P36BBOvrmPTkyFv/+myxoaHW1WfeqcT1iE29QKCAQA5RhsgO0eKC7wzsYFy +afN1AUmga6gEuQ2688REaHfJZ5lLVGUT1KMnObvrThUhekZ73DDHY3zKPD3u3X22 +lm+Xupm/no6HBtjs8/KRJAg/lC/pREYdvT0txRQS85dyIEdf6gwyHb6wtR4OJmvS +8hE/UvuPM1AXaM8+d/ou9VwoCO8Tyfc7EWeqP3xUErxHzIP2czBF9zmAvLgums40 +vwbMG07V6xvc5YzW6/LvAoNq5JHovw7+fb6K9oJRi9Y7xp3EqOGtja2KfhCVG/8u +eqqLmK6Tx0Sw9okvc4+tUoYQYir7igASu37n8Q1V2eoMQx2Gik9J/ShCX+zRqAdy +YGoFAoIBAQC2OxZIH6BgfiNR3AIw6/pLHdNuDeht6mklp/Q+LMclo4QcdePUV5Gi +Pu3uh40wFceG2Z57MOXZeSCnd5D+zx6KsKOeyMW7rxVqBLbAlQF5Zq0ffaX5Magy +1VWPK8TFjMHsz5xNzxEPLotT2+2fRvSwTv/YnfSvysIvQR9SiQ1rCYRK42G6Uhdu +qCFwUqzorkwRoFHOd1LTMBuqUUOYK1EOMNVtc0ffC24CywkuNNXbgERh1/IVYjMD +emVuuYJRgzL+JK7WOK/KBvfdKfFxVQz9GgRlR9c1gN81oHk8dhx/ls+7Q+0Y8hk2 +5U1Y1/rBuPviIFyJeP+Ly1PirP9oTbHFAoIBAQDpHns3VDKTYZQ9QYnRjPnRz2ig +rltqwM1cgqfNgaaapplFEX5wzLAjCJHk9t8jIB3oy5bi0igS++/dUZbo2zOkf5zp +iFEIUFbiS+6/iXN0o0elE/nsy4UmJeUF7Qoqyjgbi8xoSezHuJ82xZWPM1aCY3HT +pq+meDtVN3JlIVvF78lVE1RuQ3lYm1myH4hcX0v0sywQTTmv5UaeyMDP1KEP35H7 +9NSThayrOGb4gujQ00Id+xb+q8LuR3KxFr3q2dwx5DjnG6P8G4baXqnyB0iO8YMS +u98bgmGt1jeTJfsb90hIfkOjVTyaQglDYj5nj0wA3kGsEeal4JK3xvUpQfNi +-----END RSA PRIVATE KEY----- diff --git a/backend/src/test/config/express/certs/public.key b/backend/src/test/config/express/certs/public.key new file mode 100644 index 00000000..84babe0a --- /dev/null +++ b/backend/src/test/config/express/certs/public.key @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDCRFyj9WPF9P9mnYUI35acpOWz0ZqhGlRyu/C6Qfrm75DRH0lR2MmJRAfEKCQEA7MSawh54RhFbX34TdiTt9jN+1toPlwL2dnJ/eMD18X2RtucPbA8W2TYaj2Dn5O1ZUoHiv9130Pzy00B9OlrhvbS6H1SP3YTofT5/DtlKDiSSQy/uZvS61G2wRGLz7qEC7+S/dvtprKvIFhXErDCCS5cnRIcncD2jVcvviLyXq+4l1yB19IzxKf0ZgVt7O4BHokFtN2LnmRJh/TqsmqJQf7USnfJG/syBbBZ7xb9NFViMaBvAlnFN2MTcmz6FTyryanr9xC2CMn3BjYSOKWJg9X9W/2/U19YV/6Wh2p7Z4kJx9goUN6neZR5XilH9MnKltFa2VyfK4s15J4vi5ukdnyNYpaAcnmJl3Y7b6zXoH+0R9FnRwIRg2588ZtnM8azDpiuEon7aTECSufsUk8rc1cwjeKYQNDEKIQmXTsNmbkWD5xCwbgXuz9XwAbiz8FFNLoEEe0CQqm338bqfFYI5Yw92NNtcIcZ2HYiSu6Tin4CV69g24RuE7IbNLIcRzUmCy7y9I6Z9+HPD2vl1aMhn5/NZlMSHQ9kN5zVv7bcO/18eeqOq3HeCitGDxwq/m7YpL8l+FtCruW4JmPRjLyfpxOdhZvNvmbj580r4sNu6THRAQ== test@carpal.com diff --git a/backend/src/test/expressApis.js b/backend/src/test/expressApis.js new file mode 100644 index 00000000..4946516b --- /dev/null +++ b/backend/src/test/expressApis.js @@ -0,0 +1,57 @@ +const express = require('express'); +const fs = require('fs'); +const path = require('path'); +const CreateRideService = require('../main/rides/CreateRideService'); +const ListRidesService = require('../main/rides/ListRidesService'); +const FindOneRideService = require('../main/rides/FindOneRideService'); +const UpdateRideService = require('../main/rides/UpdateRideService'); +const DatabaseManager = require('../main/database/DatabaseManager'); +const ExpressRideApis = require('./rides/express/ExpressRidesApis'); +const ExpressAuthApis = require('./auth/ExpressAuthApis'); +const AwsLambdaRideApis = require('../main/rides/aws/AwsLambdaRideApis'); +const bodyParser = require('body-parser'); +const databaseManager = new DatabaseManager(); + +const https = require('https'); +const http = require('http'); + +process.on('uncaughtException', function (err) { + console.log(err); +}); + +process.env.DOMAIN = 'localhost:8081'; + +const createRideService = new CreateRideService(databaseManager); +const listRidesService = new ListRidesService(databaseManager); +const findOneRideService = new FindOneRideService(databaseManager); +const updateRideService = new UpdateRideService(databaseManager); +const app = express(); +app.use(bodyParser.json()); // for parsing application/json +app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded + +app.use(function (req, res, next) { + res.header("Access-Control-Allow-Origin", req.get("Origin")); + res.header("Access-Control-Allow-Headers", "*"); + res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE"); + next(); +}); + +new ExpressAuthApis(app); +let awsLambdaRideApis = new AwsLambdaRideApis(createRideService, + listRidesService, + findOneRideService, + updateRideService); + +new ExpressRideApis(app, awsLambdaRideApis); + +const options = { + key: fs.readFileSync(path.resolve(__dirname, './config/express/certs/key.pem')), + cert: fs.readFileSync(path.resolve(__dirname, './config/express/certs/certificate.pem')) +}; + +http.createServer(app).listen(8080, () => { + console.log("HTTP Server started and listening on port 8080") +}); +https.createServer(options, app).listen(8081, () => { + console.log("HTTPS Server started and listening on port 8081") +}); \ No newline at end of file diff --git a/backend/src/test/rides/CreateRide.integration.test.js b/backend/src/test/rides/CreateRide.integration.test.js new file mode 100644 index 00000000..3459c250 --- /dev/null +++ b/backend/src/test/rides/CreateRide.integration.test.js @@ -0,0 +1,86 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const RideStatus = require('../../main/rides/RideStatus'); +const CreateRideService = require('../../main/rides/CreateRideService'); +const FindRideTestRepository = require('./RideTestRepository'); +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 createRideService; +let rideRequest; +let databaseManager; +let mockDatabaseManager; +let findRideTestRepository; +let connection; +let loginData; +let pickupTimeAndDateInUTC; + +before(async () => { + databaseManager = new DatabaseManager(); + mockDatabaseManager = new DatabaseManager(); + connection = databaseManager.createConnection() ; + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => null; +}); + +after(async () => { + await databaseManager.closeConnection(connection); +}); + +beforeEach(async () => { + await databaseManager.beginTransaction(connection); + + createRideService = new CreateRideService(mockDatabaseManager); + findRideTestRepository = new FindRideTestRepository(databaseManager); +}); + +afterEach(async () => { + await databaseManager.rollback(connection); +}); + +beforeEach(function setupData() { + loginData = {email: RandomUtils.randomEmail()} + pickupTimeAndDateInUTC = moment(); + 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': pickupTimeAndDateInUTC.format('YYYY-MM-DD HH:mm:ss.SSS'), + 'status': RideStatus.OPEN + }; +}); + +describe('SQL', function () { + it('should insert and retrieve ride', async function () { + await createRideService.createRide(rideRequest, loginData); + let storedRide = await findRideTestRepository.findOneByClientEmail(rideRequest.client, connection); + + assert.deepEqualExcluding(storedRide, rideRequest, ['id', 'datetime', 'pickupTimeAndDateInUTC', 'facilitatorId']); + assert.equal(storedRide.facilitatorId, loginData.email); + assert.equal(storedRide.pickupTimeAndDateInUTC.setMilliseconds(0), pickupTimeAndDateInUTC.toDate().setMilliseconds(0)); + }); +}); + + + diff --git a/backend/src/test/rides/FindOneRide.integration.test.js b/backend/src/test/rides/FindOneRide.integration.test.js new file mode 100644 index 00000000..2a6f57a0 --- /dev/null +++ b/backend/src/test/rides/FindOneRide.integration.test.js @@ -0,0 +1,112 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const FindOneRideService = require('../../main/rides/FindOneRideService'); +const RideTestRepository = require('./RideTestRepository'); +const RideEntityBuilder = require('./RideEntityBuilder'); +const RideRepository = require('../../main/rides/RideRepository'); +const RandomUtils = require('../RandomUtils'); + +let findOneRideService; +let databaseManager; +let mockDatabaseManager; +let rideRepository; +let connection; +let rideTestRepository; + +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +chai.use(chaiExclude); + +before(async () => { + databaseManager = new DatabaseManager(); + mockDatabaseManager = new DatabaseManager(); + connection = databaseManager.createConnection(); + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => Promise.resolve(null); +}); + +after(async () => { + await databaseManager.closeConnection(connection); +}); + +beforeEach(async () => { + await databaseManager.beginTransaction(connection); + + findOneRideService = new FindOneRideService(mockDatabaseManager); + rideTestRepository = new RideTestRepository(mockDatabaseManager); + rideRepository = new RideRepository(databaseManager); +}); + +afterEach(async () => { + await databaseManager.rollback(connection); +}); + +describe('When find one ride', async () => { + it('should show single ride that was created by facilitator', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'facilitator'}; + const email = loginData.email; + const ride = randomRideWithFacilitator(email); + const rideEntity = await databaseContainsRide(ride); + + // when + const storedRide = await findOneRideService.findOne(rideEntity.id, loginData); + + // then + assert.deepEqualExcluding(ride, storedRide, 'id'); + }); + + it('should NOT show ride that was created by other facilitator', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'facilitator'}; + const ride = randomRideWithFacilitator(RandomUtils.randomEmail()); + const rideEntity = await databaseContainsRide(ride); + + // when + const storedRide = await findOneRideService.findOne(rideEntity.id, loginData); + + // then + assert.isNull(storedRide); + }); + + it('should show single ride when user is admin', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'admin'}; + const ride = randomRideWithFacilitator(RandomUtils.randomEmail()); + const rideEntity = await databaseContainsRide(ride); + + // when + const storedRide = await findOneRideService.findOne(rideEntity.id, loginData); + + // then + assert.deepEqualExcluding(ride, storedRide, 'id'); + }); + + it('should not show single ride when user is driver', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'driver'}; + const ride = randomRideWithFacilitator(RandomUtils.randomEmail()); + const rideEntity = await databaseContainsRide(ride); + + // when + const storedRide = await findOneRideService.findOne(rideEntity.id, loginData); + + // then + assert.isNull(storedRide); + }); + + async function databaseContainsRide(ride) { + await rideRepository.create(ride, connection); + let rideEntity = rideTestRepository.findOneByClientEmail(ride.client); + return rideEntity; + } + + function randomRideWithFacilitator(facilitatorId) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.facilitatorId = facilitatorId; + return ride; + } +}); + + + diff --git a/backend/src/test/rides/ListRides.integration.test.js b/backend/src/test/rides/ListRides.integration.test.js new file mode 100644 index 00000000..42d96784 --- /dev/null +++ b/backend/src/test/rides/ListRides.integration.test.js @@ -0,0 +1,115 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const ListRidesController = require('../../main/rides/ListRidesService'); +const FindRideTestRepository = require('./RideTestRepository'); +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(); + mockDatabaseManager = new DatabaseManager(); + connection = databaseManager.createConnection(); + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => Promise.resolve(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.deepEqualExcluding(rides, [ride1, ride2], 'id'); + }); + + it('should show all rides for admin', async function () { + // given + const loginData = {email: RandomUtils.randomEmail(), role: 'admin'}; + const ride1 = RideEntityBuilder.randomRideEntity(); + const ride2 = RideEntityBuilder.randomRideEntity(); + await databaseContainsRides(ride1, ride2); + + // when + const rides = (await listRideController.listRides({}, loginData)).map(removeId); + + assert.deepInclude(rides, ride1); + assert.deepInclude(rides, 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)).map(removeId); + + assert.deepInclude(rides, ride1); + assert.deepInclude(rides, ride3); + assert.notDeepInclude(rides, ride2); + // assert.deepEqualExcluding(rides, [ride1, ride3], 'id'); + }); + + async function databaseContainsRides(...rides) { + for (let ride of rides) { + await rideRepository.create(ride, connection); + } + } + + function removeId(ride){ + delete ride.id; + return ride; + } + + function randomRideWithGender(gender) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.driverGender = gender; + return ride; + } + + function randomRideWithFacilitator(facilitatorId) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.facilitatorId = facilitatorId; + return ride; + } +}); + + + diff --git a/backend/src/test/rides/RideEntityBuilder.js b/backend/src/test/rides/RideEntityBuilder.js new file mode 100644 index 00000000..974f41e5 --- /dev/null +++ b/backend/src/test/rides/RideEntityBuilder.js @@ -0,0 +1,38 @@ +const RandomUtils = require('../RandomUtils'); +const RideStatus = require('../../main/rides/RideStatus'); + +const now = new Date(); + +class RideEntityBuilder { + static randomRideEntity() { + const client = `client.${Date.now()}`; + const facilitator = `facilitator.${Date.now()}`; + return { + carType: "suv", + client: `${client}@${RandomUtils.randomString(5)}.com`, + deleted: 0, + driverGender: (Math.random() * 1000) % 2 ? "male" : "female", + facilitatorId: `${facilitator}@${RandomUtils.randomString(5)}.com`, + fbLink: `http://facebook.com/profile/${client}`, + locationFrom: { + latitude: RandomUtils.randomNumber(4), + longitude: RandomUtils.randomNumber(4), + placeName: RandomUtils.randomString(10), + postcode: RandomUtils.randomNumber(4).toString(), + suburb: RandomUtils.randomString(10) + }, + locationTo: { + latitude: RandomUtils.randomNumber(4), + longitude: RandomUtils.randomNumber(4), + placeName: RandomUtils.randomString(10), + postcode: RandomUtils.randomNumber(4).toString(), + suburb: RandomUtils.randomString(10) + }, + pickupTimeAndDateInUTC: new Date(new Date(now.setMilliseconds(0)).setDate(now.getDate() + 5)), + status: RideStatus.OPEN, + description: RandomUtils.randomString(10), + } + } +} + +module.exports = RideEntityBuilder; \ No newline at end of file diff --git a/backend/src/test/rides/RideMapper.unit.test.js b/backend/src/test/rides/RideMapper.unit.test.js new file mode 100644 index 00000000..e78ad4d5 --- /dev/null +++ b/backend/src/test/rides/RideMapper.unit.test.js @@ -0,0 +1,110 @@ +const RideMapper = require('../../main/rides/RideMapper'); +const RandomUtils = require('../RandomUtils'); +const RideStatus = require('../../main/rides/RideStatus'); +const RideEntityBuilder = require('./RideEntityBuilder'); + +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +const moment = require('moment'); +chai.use(chaiExclude); + + +describe('RideMapper', async () => { + it('should convert dto to entity', async function () { + const pickupTimeAndDateInUTC = moment(); + const facilitatorId = RandomUtils.randomEmail(); + const dto = { + 'carType': 'suv', + 'client': RandomUtils.randomEmail(), + 'deleted': 0, + 'description': RandomUtils.randomString(10), + 'driverGender': (RandomUtils.randomNumber(2) % 2) ? 'male' : 'female', + 'fbLink': 'http://facebook.com/profile/' + RandomUtils.randomString(10), + 'locationFrom': { + "latitude": RandomUtils.randomNumber(4), + "longitude": RandomUtils.randomNumber(4), + "placeName": RandomUtils.randomString(10), + "suburb": RandomUtils.randomString(10), + "postcode": RandomUtils.randomNumber(4) + }, + 'locationTo': { + "latitude": RandomUtils.randomNumber(4), + "longitude": RandomUtils.randomNumber(4), + "placeName": RandomUtils.randomString(10), + "suburb": RandomUtils.randomString(10), + "postcode": RandomUtils.randomNumber(4) + }, + 'pickupTimeAndDateInUTC': pickupTimeAndDateInUTC.format('YYYY-MM-DD HH:mm:ss.SSS'), + 'status': RideStatus.OPEN + }; + + const entity = RideMapper.dtoToEntity(dto, facilitatorId); + + assert.deepEqual(entity, { + client: dto.client, + pickupTimeAndDateInUTC: new Date(dto.pickupTimeAndDateInUTC), + locationFrom: { + latitude: dto.locationFrom.latitude, + longitude: dto.locationFrom.longitude, + suburb: dto.locationFrom.suburb, + placeName: dto.locationFrom.placeName, + postcode: dto.locationFrom.postcode, + }, + locationTo: { + latitude: dto.locationTo.latitude, + longitude: dto.locationTo.longitude, + suburb: dto.locationTo.suburb, + placeName: dto.locationTo.placeName, + postcode: dto.locationTo.postcode, + }, + fbLink: dto.fbLink, + driverGender: dto.driverGender, + carType: dto.carType, + status: dto.status, + deleted: 0, + facilitatorId: facilitatorId, + description: dto.description + }); + }); + + it('should convert entity to dto', async function () { + // given + const entity = RideEntityBuilder.randomRideEntity(); + + // then + const dto = RideMapper.entityToDto(entity); + + // then + assert.deepEqual(dto, { + client: entity.client, + pickupTimeAndDateInUTC: new Date(entity.pickupTimeAndDateInUTC), + locationFrom: { + latitude: entity.locationFrom.x, + longitude: entity.locationFrom.y, + suburb: entity.suburbFrom, + postcode: entity.postCodeFrom, + placeName: entity.placeNameFrom + }, + locationTo: { + latitude: entity.locationTo.x, + longitude: entity.locationTo.y, + suburb: entity.suburbTo, + postcode: entity.postCodeTo, + placeName: entity.placeNameTo + }, + fbLink: entity.fbLink, + driverGender: entity.driverGender, + carType: entity.carType, + status: entity.status, + deleted: entity.deleted, + facilitatorId: entity.facilitatorId, + description: entity.description, + id: entity.id + }); + }); + +}); + + + diff --git a/backend/src/test/rides/RideTestRepository.js b/backend/src/test/rides/RideTestRepository.js new file mode 100644 index 00000000..c8705d3b --- /dev/null +++ b/backend/src/test/rides/RideTestRepository.js @@ -0,0 +1,15 @@ +const RidesMapper = require('../../main/rides/RideMapper'); + +class RideTestRepository { + constructor(databaseManager) { + this._databaseManager = databaseManager; + } + + findOneByClientEmail(email, connection){ + return this._databaseManager.query(`SELECT * FROM rides WHERE client = '${email}'`, connection) + .then(result => result instanceof Array && RidesMapper.entityToDto(result[0])); + } +} + + +module.exports = RideTestRepository; diff --git a/backend/src/test/rides/UpdateRide.integration.test.js b/backend/src/test/rides/UpdateRide.integration.test.js new file mode 100644 index 00000000..d63a07db --- /dev/null +++ b/backend/src/test/rides/UpdateRide.integration.test.js @@ -0,0 +1,110 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const RideStatus = require('../../main/rides/RideStatus'); +const UpdateRideService = require('../../main/rides/UpdateRideService'); +const FindRideTestRepository = require('./RideTestRepository'); +const RideEntityBuilder = require('./RideEntityBuilder'); +const RideRepository = require('../../main/rides/RideRepository'); +const RandomUtils = require('../RandomUtils'); +const RideMapper = require('../../main/rides/RideMapper'); +const moment = require('moment'); +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +chai.use(chaiExclude); + +let updateRideService; +let databaseManager; +let mockDatabaseManager; +let findRideTestRepository; +let connection; +let loginData; +let pickupTimeAndDateInUTC; +let rideRepository; + +before(async () => { + databaseManager = new DatabaseManager(); + mockDatabaseManager = new DatabaseManager(); + rideRepository = new RideRepository(databaseManager); + connection = databaseManager.createConnection(); + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => Promise.resolve(); +}); + +after(async () => { + await databaseManager.closeConnection(connection); +}); + +beforeEach(async () => { + await databaseManager.beginTransaction(connection); + + updateRideService = new UpdateRideService(mockDatabaseManager); + findRideTestRepository = new FindRideTestRepository(databaseManager); +}); + +afterEach(async () => { + await databaseManager.rollback(connection); +}); + +beforeEach(function setupData() { + loginData = {email: RandomUtils.randomEmail()}; + pickupTimeAndDateInUTC = moment(); +}); + +describe('SQL', function () { + it('should update and retrieve ride', async function () { + // given + const ride = randomRideWithFacilitator(loginData.email); + await databaseContainsRide(ride); + const modifiedRide = modifyRide(ride); + let storedRide = await findRideTestRepository.findOneByClientEmail(ride.client, connection); + + // when + await updateRideService.updateRide(storedRide.id, modifiedRide, loginData); + let modifiedRideFromDb = await findRideTestRepository.findOneByClientEmail(storedRide.client, connection); + + // then + assert.deepEqualExcluding(modifiedRideFromDb, RideMapper.dtoToEntity(modifiedRide), ['id', 'datetime', 'facilitatorId', 'pickupTimeAndDateInUTC']); + assert.equal(modifiedRideFromDb.facilitatorId, loginData.email); + assert.equal(modifiedRideFromDb.pickupTimeAndDateInUTC.setMilliseconds(0), pickupTimeAndDateInUTC.toDate().setMilliseconds(0)); + }); +}); + +function modifyRide(ride) { + return { + 'carType': 'suv', + 'client': ride.client, + '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': pickupTimeAndDateInUTC.format('YYYY-MM-DD HH:mm:ss.SSS'), + 'status': RideStatus.OPEN + }; +} + +async function databaseContainsRide(ride) { + await rideRepository.create(ride, connection); +} + +function randomRideWithFacilitator(facilitatorId) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.facilitatorId = facilitatorId; + return ride; +} + + + diff --git a/backend/src/test/rides/express/ExpressRidesApis.js b/backend/src/test/rides/express/ExpressRidesApis.js new file mode 100644 index 00000000..01879698 --- /dev/null +++ b/backend/src/test/rides/express/ExpressRidesApis.js @@ -0,0 +1,60 @@ +class ExpressRideApis { + constructor(app, awsLambdaRideApis) { + this.app = app; + this.awsLambdaRideApis = awsLambdaRideApis; + this.app.post('/rides', this.create.bind(this)); + this.app.put('/rides/:id', this.update.bind(this)); + this.app.get('/rides/:id', this.findOne.bind(this)); + this.app.get('/rides', this.list.bind(this)); + } + + create(req, res) { + this.awsLambdaRideApis.create(this._extractAwsEvent(req), {}, (error, result) => { + if (error) { + return res.status(500).send(error); + } + res.status(200).send(result); + }); + } + + update(req, res) { + this.awsLambdaRideApis.update(this._extractAwsEvent(req), {}, (error, result) => { + if (error) { + return res.status(500).send(error); + } + res.status(200).send(result); + }); + } + + list(req, res) { + this.awsLambdaRideApis.list(this._extractAwsEvent(req), {}, (error, result) => { + if (error) { + return res.status(500).send(error); + } + res.status(200).send(result); + }); + } + + findOne(req, res) { + this.awsLambdaRideApis.findOne(this._extractAwsEvent(req), {}, (error, result) => { + if (error) { + return res.status(500).send(error); + } + res.status(200).send(result); + }); + } + + _extractAwsEvent(req) { + let event = { + headers: { + Authorization: req.get('authorization') + }, + body: JSON.stringify(req.body), + pathParameters: req.params || {}, + queryStringParameters: req.query || {}, + }; + return event; + } +} + +module.exports = ExpressRideApis; \ No newline at end of file diff --git a/backend/src/test/users/admin.json b/backend/src/test/users/admin.json new file mode 100644 index 00000000..22fa25bc --- /dev/null +++ b/backend/src/test/users/admin.json @@ -0,0 +1,5 @@ +{ + "email": "test-admin@carpal.com", + "gender": "male", + "role": "admin" +} \ No newline at end of file diff --git a/backend/src/test/users/driver.json b/backend/src/test/users/driver.json new file mode 100644 index 00000000..4b1948f1 --- /dev/null +++ b/backend/src/test/users/driver.json @@ -0,0 +1,6 @@ +{ + "email": "test-driver@carpal.com", + "gender": "male", + "car": "suv", + "role": "driver" +} \ No newline at end of file diff --git a/backend/src/test/users/facilitator.json b/backend/src/test/users/facilitator.json new file mode 100644 index 00000000..f74e3445 --- /dev/null +++ b/backend/src/test/users/facilitator.json @@ -0,0 +1,5 @@ +{ + "email": "test-facilitator@carpal.com", + "gender": "female", + "role": "facilitator" +} \ No newline at end of file diff --git a/doc/carpal-api.yaml b/doc/carpal-api.yaml index d1d69ae9..3ed95ba0 100644 --- a/doc/carpal-api.yaml +++ b/doc/carpal-api.yaml @@ -162,7 +162,7 @@ components: type: string status: type: string - description: Can be Open / Closed + description: Can be Open / Confirmed / Canceled / Ended Location: properties: latitude: diff --git a/doc/sampledata.json b/doc/sampledata.json index 7140af8b..17d20e24 100644 --- a/doc/sampledata.json +++ b/doc/sampledata.json @@ -65,7 +65,7 @@ "fbLink": "www.facebook.com/events/965341583633198", "driverGender": "Female", "carType": "SUV", - "status": "Closed", + "status": "Ended", "facilitatorId": 1 }, { @@ -111,7 +111,7 @@ "fbLink": "www.facebook.com/events/965341583633198", "driverGender": "All", "carType": "SUV", - "status": "Closed", + "status": "Ended", "facilitatorId": 2 }, { @@ -180,7 +180,7 @@ "fbLink": "www.facebook.com/events/965341583633198", "driverGender": "All", "carType": "Normal", - "status": "Closed", + "status": "Ended", "facilitatorId": 3 }, { diff --git a/frontend/public/sampledata.json b/frontend/public/sampledata.json index 7140af8b..17d20e24 100644 --- a/frontend/public/sampledata.json +++ b/frontend/public/sampledata.json @@ -65,7 +65,7 @@ "fbLink": "www.facebook.com/events/965341583633198", "driverGender": "Female", "carType": "SUV", - "status": "Closed", + "status": "Ended", "facilitatorId": 1 }, { @@ -111,7 +111,7 @@ "fbLink": "www.facebook.com/events/965341583633198", "driverGender": "All", "carType": "SUV", - "status": "Closed", + "status": "Ended", "facilitatorId": 2 }, { @@ -180,7 +180,7 @@ "fbLink": "www.facebook.com/events/965341583633198", "driverGender": "All", "carType": "Normal", - "status": "Closed", + "status": "Ended", "facilitatorId": 3 }, { diff --git a/frontend/src/facilitator/CreateNewRide.js b/frontend/src/facilitator/CreateNewRide.js index 26132224..76de8a1c 100644 --- a/frontend/src/facilitator/CreateNewRide.js +++ b/frontend/src/facilitator/CreateNewRide.js @@ -37,7 +37,7 @@ class CreateNewRide extends Component { }, }) .then(res => { - const data = res.data[0]; + const data = res.data; this.setState(data); }); @@ -102,8 +102,8 @@ class CreateNewRide extends Component {