From eb580132cccc4ac716575d2fa09727ad17599996 Mon Sep 17 00:00:00 2001 From: Fred Boniface Date: Thu, 26 Jun 2025 16:26:04 +0100 Subject: [PATCH] Initial --- .gitignore | 1 + LICENSE | 30 + package-lock.json | 1170 ++++++++++++++++++++++++++++ package.json | 27 + src/formHandler.ts | 83 ++ src/index.ts | 27 + static/index.html | 52 ++ static/script.js | 176 +++++ static/style.css | 165 ++++ static/units.converted.json | 1435 +++++++++++++++++++++++++++++++++++ 10 files changed, 3166 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/formHandler.ts create mode 100644 src/index.ts create mode 100644 static/index.html create mode 100644 static/script.js create mode 100644 static/style.css create mode 100644 static/units.converted.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..81cbc66 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ + +Copyright (c) 2025 Frederick Boniface. All Rights Reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistribution of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistribution in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +You acknowledge that this software is not designed, licensed or intended for use +in the design, construction, operation or maintenance of any military facility. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bfd7c84 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1170 @@ +{ + "name": "tracreport", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tracreport", + "version": "0.0.1", + "license": "BSD-3-Clause-No-Military-License", + "dependencies": { + "express": "^5.1.0", + "mongoose": "^8.16.0", + "nodemailer": "^7.0.3" + }, + "devDependencies": { + "@types/express": "^5.0.3", + "@types/node": "^24.0.4", + "@types/nodemailer": "^6.4.17" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", + "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mongodb": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.0.tgz", + "integrity": "sha512-gLuAZsbwY0PHjrvfuXvUkUq9tXjyAjN3ioXph5Y6Seu7/Uo8xJaM+rrMbL/x34K4T3UTgtXRyfoq1YU16qKyIw==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.4", + "kareem": "2.6.3", + "mongodb": "~6.17.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b39dc9b --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "tracreport", + "version": "0.0.1", + "description": "Collect reports of failed air conditioning on trains", + "main": "index.js", + "type": "module", + "scripts": { + "start": "npx tsx --watch src/index.ts", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git.fjla.uk/fred.boniface/tracreport" + }, + "author": "Frederick Boniface", + "license": "BSD-3-Clause-No-Military-License", + "dependencies": { + "express": "^5.1.0", + "mongoose": "^8.16.0", + "nodemailer": "^7.0.3" + }, + "devDependencies": { + "@types/express": "^5.0.3", + "@types/node": "^24.0.4", + "@types/nodemailer": "^6.4.17" + } +} diff --git a/src/formHandler.ts b/src/formHandler.ts new file mode 100644 index 0000000..4a4b90b --- /dev/null +++ b/src/formHandler.ts @@ -0,0 +1,83 @@ +import nodemailer from 'nodemailer'; +import type { Transporter, SendMailOptions } from 'nodemailer'; + +interface Fault { + coach: string; + zone: string; +} + +interface Report { + unitNumber: string; + reported: string; + comments: string; + utcTimestamp: string; + faults: Fault[]; +} + +export async function handleFormData(data) { + // Uploads form data to database and emails + // to defined subscribers. + + const knownKeys = ['unitNumber', 'reported', 'comments', 'utcTimestamp']; + + const faults: Fault[] = []; + + const report: Report = { + unitNumber: data.unitNumber, + reported: data.reported, + comments: data.comments || '', + utcTimestamp: data.utcTimestamp, + faults: [] + }; + + for (const key in data) { + if (!knownKeys.includes(key) && data[key] === 'on') { + const match = key.match(/^(.+)-(.+)$/); + if (match) { + const [_, zone, coach] = match; + report.faults.push({ coach, zone }); + } + } + } + + submit(report); + sendMail(report); +} + +async function submit(report: Report): Promise { + console.log(report); + // Send to database + // Send to email +} + +async function sendMail(report: Report): Promise { + const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: parseInt(process.env.SMTP_PORT || '587'), + secure: true, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS, + } + }); + + const faultList = report.faults.map(f => `- ${f.zone} (${f.coach})`).join('\n'); + + const mailOptions: SendMailOptions = { + from: `"AC Reporter ${process.env.SMTP_USER}"`, + to: process.env.MAIL_RECIPIENTS, + subject: `New A/C Report: ${report.unitNumber}`, + text: `A report has been submitted for unit ${report.unitNumber}.\n\n` + + `Timestamp: ${report.utcTimestamp}\n\n` + + `Faults:\n${faultList}\n\n` + + `Reported to Maintenance: ${report.reported}` + + `Comments:\n${report.comments} || 'None'` + }; + + try { + const info = await transporter.sendMail(mailOptions); + console.log('📧 Report sent:', info.messageId); + } catch (err) { + console.error('❌ Failed to send email:', err); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1366e6d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,27 @@ +import express, { Request, Response } from "express"; +import { handleFormData } from "./formHandler"; + +const app = express(); +const port = process.env.port || 3000; + +app.use(express.static('static')); +app.use(express.json()); + +app.get("/test", (req: Request, res: Response) => { + res.json({message: "TrACreport is running"}); +}); + +app.post('/submit', (req, res) => { + const report = req.body; + + // For now, just log it + console.log('📥 New form submission:'); + + // TODO: Validate and write to MongoDB later + handleFormData(report); + res.status(200).json({ status: 'ok', message: 'Form received' }); +}); + +app.listen(port, () => { + console.log("Server running on port:", port); +}); \ No newline at end of file diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..f1ddef4 --- /dev/null +++ b/static/index.html @@ -0,0 +1,52 @@ + + + + + + + TrACreport + + + +

Report an A/C Defect

+
+
+ +
+ +
+
+
+

Have you reported this to maintenance?

+ + + + +
+ + +

eg. If you did not report, why? Did a fitter meet the train? Is there already a response in the defect book?

+ +

+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..23993b9 --- /dev/null +++ b/static/script.js @@ -0,0 +1,176 @@ +let data = {} + +const dataPromise = fetch("units.converted.json") + .then(res => { + if (!res.ok) throw new Error("Failed to load JSON"); + return res.json(); + }) + .then(json => { + data = json; + console.log("Loaded unit data"); + }) + .catch(err => { + console.error("Error loading unit data", err); + window.location.reload(); + }); + + +window.addEventListener('DOMContentLoaded', () => { + const input = document.getElementById('unitNumber'); + const resultDiv = document.getElementById('formExpansion'); + + input.addEventListener('input', async () => { + await dataPromise; + + const value = input.value.trim(); + + if (value in data) { + const match = data[value]; // this is now an array of { id, zones } + resultDiv.textContent = ""; + loadForm(match); +} else { + resultDiv.textContent = "Enter a valid unit number"; + document.getElementById('formHidden').style.display = "none"; +} + }) +}); + +document.getElementById("defectForm").addEventListener("submit", formSubmit); +window.addEventListener("load", retryOfflineReports); + +async function loadForm(values) { + const formExpansion = document.getElementById('formExpansion'); + const formHidden = document.getElementById('formHidden'); + + formExpansion.innerHTML = ''; + + const heading = document.createElement('h2'); + heading.textContent = "Choose all areas where A/C failed"; + formExpansion.appendChild(heading); + + for (const vehicle of values) { + const coachTitle = document.createElement('h3'); + coachTitle.textContent = vehicle.id; + formExpansion.appendChild(coachTitle); + + for (const zone of vehicle.zones) { + const checkboxId = `${zone}-${vehicle.id}`; + + const wrapper = document.createElement('div'); + wrapper.className = 'checkbox-wrapper'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = checkboxId; + checkbox.name = checkboxId; + checkbox.title = zone; + + const label = document.createElement('label'); + label.htmlFor = checkboxId; + label.textContent = zone; + + wrapper.appendChild(checkbox); + wrapper.appendChild(label); + formExpansion.appendChild(wrapper); + } + } + + formHidden.style = 'display:block'; +} + +function reset() { + document.getElementById('formHidden').style = 'display:hidden'; + document.getElementById('formExpansion').innerHTML = ''; +} + +async function formSubmit(event) { + event.preventDefault(); + + const form = event.target; + const formData = new FormData(form); + + const submitButton = form.querySelector('button[type="submit"]'); + submitButton.disabled = true; + + const data = {}; + for (const [key, value] of formData.entries()) { + if (data[key]) { + if (!Array.isArray(data[key])) data[key] = [data[key]]; + data[key].push(value); + } else { + data[key] = value; + } + } + + data.utcTimestamp = new Date().toISOString(); + + try { + const res = await fetch("/submit", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(data), + }); + + if (!res.ok) throw new Error("Submission failed"); + console.log("Form Submitted") + showStatus("✅ Form received, thank-you.", type="success"); + } catch (err) { + console.warn("Form Sending failed, saving to localStorage"); + saveReportOffline(data); + showStatus("⚠️ Unable to send form, it will be automatically sent with the next report you submit.", type="warn"); + } + form.reset(); + reset(); + submitButton.disabled = false; +} + +function saveReportOffline(report) { + const key = "offlineReports"; + const reports = JSON.parse(localStorage.getItem(key) || "[]"); + reports.push(report); + localStorage.setItem(key, JSON.stringify(reports)); +} + +async function retryOfflineReports() { + const key = "offlineReports"; + const stored = JSON.parse(localStorage.getItem(key) || "[]"); + + if (stored.length === 0) return; + + console.log(`Attempting to resend ${stored.length} stored report(s)...`); + + const remaining = []; + + for (const report of stored) { + try { + const res = await fetch("/submit", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(report), + }); + + if (!res.ok) throw new Error ("Submission Failed"); + console.log("Resent one stored report"); + } catch (err) { + console.warn("Failed to resend stored report"); + remaining.push(report); + } + } + + if (remaining.length === 0) { + localStorage.removeItem(key); + } else { + localStorage.setItem(key, JSON.stringify(remaining)); + } +} + +function showStatus(message, type) { + const statusDiv = document.getElementById('formStatus'); + statusDiv.textContent = message; + statusDiv.className = `status-${type}`; + statusDiv.style = 'display:flex'; + + setTimeout(() => { + statusDiv.style = 'display:none'; + }, 5000); +} \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..b333cfc --- /dev/null +++ b/static/style.css @@ -0,0 +1,165 @@ +* { + box-sizing: border-box; +} + + +html { + width: 100%; + margin: 0; + padding: 0; +} + +body { + width: 100vw; + padding: 0; + margin: 0; + text-align: center; + color: #f2f3f2; + background-color: #525252; +} + +h1 { + padding-top: 20px; + padding-bottom: 20px; + margin:auto; + width: 90%; +} + +form { + width: 100vw; + padding: 0; + margin: 0; + margin-bottom: 25px; +} + +#unitNumber { + width: 40vw; + font-size: larger; + border-radius: 4px; + text-align: center; + margin: 0; +} + +h2 { + font-size: larger; +} + +h3 { + font-size: large; + margin-bottom: 0; +} + +#formExpansion { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 0.7rem 2rem; +} + +#formExpansion h3 { + flex-basis: 100%; + margin-top: 1.0rem; + margin-bottom: 0rem; + font-weight: bold; +} + +#formExpansion h3:first-of-type { + margin-top: 0rem; +} + +.checkbox-wrapper { + display: flex; + flex-direction: column; + align-items: center; + font-size: 1rem; + cursor: pointer; + width: 5rem; +} + +.checkbox-wrapper input[type="checkbox"] { + width: 1.8rem; + height: 1.8rem; + margin-top: 0rem; + cursor: pointer; +} + +.checkbox-wrapper label { + text-transform: capitalize; + user-select: none; +} + + +#formHidden { + display: none; + width: 100vw; + margin: auto; + padding-top: 10px; +} + +#radio-group { + width: 90vw; + margin:auto; + padding-top: 10px; + padding-bottom: 10px; +} + +#radio-group p { + font-weight: bold; +} + +#radio-group label { + display: flex; + align-items: center; + font-size: 1rem; + padding: 0.25rem 0.75rem; /* top/bottom 0.25rem, left/right 0.75rem */ + margin: 0; + cursor: pointer; +} + +#radio-group input[type="radio"] { + width: 1.5rem; + height: 1.5rem; + margin-right: 0.5rem; +} + +#commentsLabel { + font-weight: bold; +} + +p { + width: 90vw; + margin: auto; +} + +button { + width: 60px; + height: 40px; + border-radius: 12px; +} + +#formStatus { + display: none; + position: fixed; + top: 37.5vh; + left: 13vw; + width: 74vw; + height: 25vh; + text-align: center; + font-weight: bold; + border-radius: 25px; + + justify-content: center; + align-items: center; +} + +.status-success { + background-color: green; + color: whitesmoke; +} + +.status-warn { + background-color: yellow; + color: black; +} + diff --git a/static/units.converted.json b/static/units.converted.json new file mode 100644 index 0000000..7abb8de --- /dev/null +++ b/static/units.converted.json @@ -0,0 +1,1435 @@ +{ + "158745": [ + { + "id": "52745", + "zones": [ + "saloon" + ] + }, + { + "id": "57745", + "zones": [ + "saloon" + ] + } + ], + "158747": [ + { + "id": "52747", + "zones": [ + "saloon" + ] + }, + { + "id": "57747", + "zones": [ + "saloon" + ] + } + ], + "158748": [ + { + "id": "52748", + "zones": [ + "saloon" + ] + }, + { + "id": "57748", + "zones": [ + "saloon" + ] + } + ], + "158749": [ + { + "id": "52749", + "zones": [ + "saloon" + ] + }, + { + "id": "57749", + "zones": [ + "saloon" + ] + } + ], + "158750": [ + { + "id": "52750", + "zones": [ + "saloon" + ] + }, + { + "id": "57750", + "zones": [ + "saloon" + ] + } + ], + "158760": [ + { + "id": "52760", + "zones": [ + "saloon" + ] + }, + { + "id": "57760", + "zones": [ + "saloon" + ] + } + ], + "158762": [ + { + "id": "52762", + "zones": [ + "saloon" + ] + }, + { + "id": "57762", + "zones": [ + "saloon" + ] + } + ], + "158765": [ + { + "id": "52765", + "zones": [ + "saloon" + ] + }, + { + "id": "57765", + "zones": [ + "saloon" + ] + } + ], + "158766": [ + { + "id": "52766", + "zones": [ + "saloon" + ] + }, + { + "id": "57766", + "zones": [ + "saloon" + ] + } + ], + "158767": [ + { + "id": "52767", + "zones": [ + "saloon" + ] + }, + { + "id": "57767", + "zones": [ + "saloon" + ] + } + ], + "158768": [ + { + "id": "52768", + "zones": [ + "saloon" + ] + }, + { + "id": "57768", + "zones": [ + "saloon" + ] + } + ], + "158769": [ + { + "id": "52769", + "zones": [ + "saloon" + ] + }, + { + "id": "57769", + "zones": [ + "saloon" + ] + } + ], + "158771": [ + { + "id": "52771", + "zones": [ + "saloon" + ] + }, + { + "id": "57771", + "zones": [ + "saloon" + ] + } + ], + "158798": [ + { + "id": "52798", + "zones": [ + "saloon" + ] + }, + { + "id": "57798", + "zones": [ + "saloon" + ] + }, + { + "id": "58715", + "zones": [ + "saloon" + ] + } + ], + "158950": [ + { + "id": "52761", + "zones": [ + "saloon" + ] + }, + { + "id": "57751", + "zones": [ + "saloon" + ] + }, + { + "id": "57761", + "zones": [ + "saloon" + ] + } + ], + "158951": [ + { + "id": "52751", + "zones": [ + "saloon" + ] + }, + { + "id": "52764", + "zones": [ + "saloon" + ] + }, + { + "id": "57764", + "zones": [ + "saloon" + ] + } + ], + "158958": [ + { + "id": "52776", + "zones": [ + "saloon" + ] + }, + { + "id": "57746", + "zones": [ + "saloon" + ] + }, + { + "id": "57776", + "zones": [ + "saloon" + ] + } + ], + "158959": [ + { + "id": "52746", + "zones": [ + "saloon" + ] + }, + { + "id": "52778", + "zones": [ + "saloon" + ] + }, + { + "id": "57778", + "zones": [ + "saloon" + ] + } + ], + "165101": [ + { + "id": "58916", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55415", + "zones": [ + "saloon" + ] + }, + { + "id": "58953", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165102": [ + { + "id": "58917", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55416", + "zones": [ + "saloon" + ] + }, + { + "id": "58954", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165103": [ + { + "id": "58918", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55417", + "zones": [ + "saloon" + ] + }, + { + "id": "58955", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165104": [ + { + "id": "58919", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55418", + "zones": [ + "saloon" + ] + }, + { + "id": "58956", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165105": [ + { + "id": "58920", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55419", + "zones": [ + "saloon" + ] + }, + { + "id": "58957", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165106": [ + { + "id": "58921", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55420", + "zones": [ + "saloon" + ] + }, + { + "id": "58958", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165107": [ + { + "id": "58922", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55421", + "zones": [ + "saloon" + ] + }, + { + "id": "58959", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165108": [ + { + "id": "58923", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55422", + "zones": [ + "saloon" + ] + }, + { + "id": "58960", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165109": [ + { + "id": "58924", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55423", + "zones": [ + "saloon" + ] + }, + { + "id": "58961", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165110": [ + { + "id": "58925", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55424", + "zones": [ + "saloon" + ] + }, + { + "id": "58962", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165111": [ + { + "id": "58926", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55425", + "zones": [ + "saloon" + ] + }, + { + "id": "58963", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165112": [ + { + "id": "58927", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55426", + "zones": [ + "saloon" + ] + }, + { + "id": "58964", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165113": [ + { + "id": "58928", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55427", + "zones": [ + "saloon" + ] + }, + { + "id": "58965", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165114": [ + { + "id": "58929", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55428", + "zones": [ + "saloon" + ] + }, + { + "id": "58966", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165116": [ + { + "id": "58931", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55430", + "zones": [ + "saloon" + ] + }, + { + "id": "58968", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165117": [ + { + "id": "58932", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "55431", + "zones": [ + "saloon" + ] + }, + { + "id": "58969", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165118": [ + { + "id": "58879", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58933", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165119": [ + { + "id": "58880", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58934", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165120": [ + { + "id": "58881", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58935", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165121": [ + { + "id": "58882", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58936", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165122": [ + { + "id": "58883", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58937", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165123": [ + { + "id": "58884", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58938", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165124": [ + { + "id": "58885", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58939", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165125": [ + { + "id": "58886", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58940", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165126": [ + { + "id": "58887", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58941", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165127": [ + { + "id": "58888", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58942", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165128": [ + { + "id": "58889", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58943", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165129": [ + { + "id": "58890", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58944", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165130": [ + { + "id": "58891", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58945", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165131": [ + { + "id": "58892", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58946", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165132": [ + { + "id": "58893", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58947", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165133": [ + { + "id": "58894", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58948", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165134": [ + { + "id": "58895", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58949", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165135": [ + { + "id": "58896", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58950", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165136": [ + { + "id": "58897", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58951", + "zones": [ + "saloon", + "cab" + ] + } + ], + "165137": [ + { + "id": "58898", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58952", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166201": [ + { + "id": "58101", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58601", + "zones": [ + "saloon" + ] + }, + { + "id": "58122", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166202": [ + { + "id": "58102", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58602", + "zones": [ + "saloon" + ] + }, + { + "id": "58123", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166203": [ + { + "id": "58103", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58603", + "zones": [ + "saloon" + ] + }, + { + "id": "58124", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166204": [ + { + "id": "58104", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58604", + "zones": [ + "saloon" + ] + }, + { + "id": "58125", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166205": [ + { + "id": "58105", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58605", + "zones": [ + "saloon" + ] + }, + { + "id": "58126", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166206": [ + { + "id": "58106", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58606", + "zones": [ + "saloon" + ] + }, + { + "id": "58127", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166207": [ + { + "id": "58107", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58607", + "zones": [ + "saloon" + ] + }, + { + "id": "58128", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166208": [ + { + "id": "58108", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58608", + "zones": [ + "saloon" + ] + }, + { + "id": "58129", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166209": [ + { + "id": "58109", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58609", + "zones": [ + "saloon" + ] + }, + { + "id": "58130", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166210": [ + { + "id": "58110", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58610", + "zones": [ + "saloon" + ] + }, + { + "id": "58131", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166211": [ + { + "id": "58111", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58611", + "zones": [ + "saloon" + ] + }, + { + "id": "58132", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166212": [ + { + "id": "58112", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58612", + "zones": [ + "saloon" + ] + }, + { + "id": "58133", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166213": [ + { + "id": "58113", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58613", + "zones": [ + "saloon" + ] + }, + { + "id": "58134", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166214": [ + { + "id": "58114", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58614", + "zones": [ + "saloon" + ] + }, + { + "id": "58135", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166215": [ + { + "id": "58115", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58615", + "zones": [ + "saloon" + ] + }, + { + "id": "58136", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166216": [ + { + "id": "58116", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58616", + "zones": [ + "saloon" + ] + }, + { + "id": "58137", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166217": [ + { + "id": "58117", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58617", + "zones": [ + "saloon" + ] + }, + { + "id": "58138", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166218": [ + { + "id": "58118", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58618", + "zones": [ + "saloon" + ] + }, + { + "id": "58139", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166219": [ + { + "id": "58119", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58619", + "zones": [ + "saloon" + ] + }, + { + "id": "58140", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166220": [ + { + "id": "58120", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58620", + "zones": [ + "saloon" + ] + }, + { + "id": "58141", + "zones": [ + "saloon", + "cab" + ] + } + ], + "166221": [ + { + "id": "58121", + "zones": [ + "saloon", + "cab" + ] + }, + { + "id": "58621", + "zones": [ + "saloon" + ] + }, + { + "id": "58142", + "zones": [ + "saloon", + "cab" + ] + } + ], + "175002": [ + { "id": "A", "zones": ["saloon", "cab"] }, + { "id": "B", "zones": ["saloon", "cab"] } + ], + "175007": [ + { "id": "A", "zones": ["saloon", "cab"] }, + { "id": "B", "zones": ["saloon", "cab"] } + ], + "175009": [ + { "id": "A", "zones": ["saloon", "cab"] }, + { "id": "B", "zones": ["saloon", "cab"] } + ], + "175114": [ + { "id": "A", "zones": ["saloon", "cab"] }, + { "id": "B", "zones": ["saloon"] }, + { "id": "C", "zones": ["saloon", "cab"] } + ] +} \ No newline at end of file