From 150715cfbad9f0a51ad31a2f1b272e9cd5ff011e Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Mon, 12 Dec 2022 21:44:14 +0200 Subject: [PATCH 01/19] added main dependencies and domain model --- .gitignore | 1 + package-lock.json | 1111 +++++++++++++++++++++++++++++++++++++++++- package.json | 10 +- prisma/schema.prisma | 87 ++++ 4 files changed, 1183 insertions(+), 26 deletions(-) create mode 100644 prisma/schema.prisma diff --git a/.gitignore b/.gitignore index 4634cc5..df1093e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules *.pem *.done .DS_Store +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3bbde58..f013788 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,11 @@ "name": "node-soa", "version": "0.0.0", "license": "MIT", + "dependencies": { + "@fastify/cors": "^8.2.0", + "@prisma/client": "^4.7.1", + "fastify": "^4.10.2" + }, "devDependencies": { "@types/node": "^18.7.8", "@types/ws": "^8.5.3", @@ -18,7 +23,11 @@ "eslint-plugin-prettier": "^4.2.1", "metatests": "^0.8.2", "prettier": "^2.7.1", + "prisma": "^4.7.1", "typescript": "^4.7.4" + }, + "engines": { + "node": "18" } }, "node_modules/@eslint/eslintrc": { @@ -44,6 +53,63 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz", + "integrity": "sha512-69JnK7Cot+ktn7LD5TikP3b7psBPX55tYpQa8WSumt8r117PCa2zwHnImfBtRWYExreJlI48hr0WZaVrTBGj7w==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/@fastify/cors": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.2.0.tgz", + "integrity": "sha512-qDgwpmg6C4D0D3nh8MTMuRXWyEwPnDZDBODaJv90FP2o9ukbahJByW4FtrM5Bpod5KbTf1oIExBmpItbUTQmHg==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.5" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==" + }, + "node_modules/@fastify/error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.2.0.tgz", + "integrity": "sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.1.0.tgz", + "integrity": "sha512-cTKBV2J9+u6VaKDhX7HepSfPSzw+F+TSd+k0wzifj4rG+4E5PjSFJCk19P8R6tr/72cuzgGd+mbB3jFT6lvAgw==", + "dependencies": { + "fast-json-stringify": "^5.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -121,6 +187,38 @@ "node": ">= 8" } }, + "node_modules/@prisma/client": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.7.1.tgz", + "integrity": "sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines-version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/engines": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.7.1.tgz", + "integrity": "sha512-zWabHosTdLpXXlMefHmnouhXMoTB1+SCbUU3t4FCmdrtIOZcarPKU3Alto7gm/pZ9vHlGOXHCfVZ1G7OIrSbog==", + "devOptional": true, + "hasInstallScript": true + }, + "node_modules/@prisma/engines-version": { + "version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz", + "integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -142,6 +240,22 @@ "@types/node": "*" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "node_modules/acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", @@ -179,6 +293,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -203,6 +353,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -246,12 +401,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.0.tgz", + "integrity": "sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==", + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -262,6 +454,29 @@ "concat-map": "0.0.1" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -353,6 +568,22 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -371,7 +602,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -824,17 +1054,37 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/events-to-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", "integrity": "sha512-inRWzRY7nG+aXZxBzEqYKB3HPgwflZRopAjDCHv0whhRx+MTUr1ei0ICZUypdyE0HRm4L2d5VEcIqLD6yl+BFA==", "dev": true }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.2.0", @@ -848,17 +1098,97 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-json-stringify": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.5.0.tgz", + "integrity": "sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==", + "dependencies": { + "@fastify/deepmerge": "^1.0.0", + "ajv": "^8.10.0", + "ajv-formats": "^2.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-querystring": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.0.0.tgz", + "integrity": "sha512-3LQi62IhQoDlmt4ULCYmh17vRO2EtS7hTSsG4WwoKWgV7GLMKBOecEh+aiavASnLx8I2y89OD33AGLo0ccRhzA==", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", + "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-uri": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", + "integrity": "sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==" + }, + "node_modules/fastify": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.10.2.tgz", + "integrity": "sha512-0T+4zI6N3S8ex0LCZi3H4FasJR4AzWw834fUkPWvV8r6GBJkLmAOfFxH8f5V29Plef24IK0QSQD/tz1Nx+1UOA==", + "dependencies": { + "@fastify/ajv-compiler": "^3.3.1", + "@fastify/error": "^3.0.0", + "@fastify/fast-json-stringify-compiler": "^4.1.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.2.0", + "content-type": "^1.0.4", + "find-my-way": "^7.3.0", + "light-my-request": "^5.6.1", + "pino": "^8.5.0", + "process-warning": "^2.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.5.0", + "semver": "^7.3.7", + "tiny-lru": "^10.0.0" + } + }, + "node_modules/fastify-plugin": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.4.0.tgz", + "integrity": "sha512-ovwFQG2qNy3jcCROiWpr94Hs0le+c7N/3t7m9aVwbFhkxcR/esp2xu25dP8e617HpQdmeDv+gFX4zagdUhDByw==" + }, "node_modules/fastq": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -875,6 +1205,19 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/find-my-way": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.3.1.tgz", + "integrity": "sha512-kGvM08SOkqvheLcuQ8GW9t/H901Qb9rZEbcNWbXopzy4jDRoaJpJoObPSKf4MnQLZ20ZTp7rL5MpF6rf+pqmyg==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -910,6 +1253,14 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1110,6 +1461,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", @@ -1174,6 +1544,14 @@ "node": ">= 0.4" } }, + "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==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -1442,6 +1820,16 @@ "node": ">= 0.8.0" } }, + "node_modules/light-my-request": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.8.0.tgz", + "integrity": "sha512-4BtD5C+VmyTpzlDPCZbsatZMJVgUIciSOwYhJDCbLffPZ35KoDkDj4zubLeHDEb35b4kkPeEv5imbh+RJxK/Pg==", + "dependencies": { + "cookie": "^0.5.0", + "process-warning": "^2.0.0", + "set-cookie-parser": "^2.4.1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1463,6 +1851,17 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/metatests": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/metatests/-/metatests-0.8.2.tgz", @@ -1515,11 +1914,18 @@ "node": ">=8" } }, + "node_modules/mnemonist": { + "version": "0.39.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", + "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", + "dependencies": { + "obliterator": "^2.0.1" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -1580,6 +1986,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1690,6 +2106,41 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/pino": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.7.0.tgz", + "integrity": "sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", + "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -1726,11 +2177,52 @@ "node": ">=6.0.0" } }, + "node_modules/prisma": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.7.1.tgz", + "integrity": "sha512-CCQP+m+1qZOGIZlvnL6T3ZwaU0LAleIHYFPN9tFSzjs/KL6vH9rlYbGOkTuG9Q1s6Ki5D0LJlYlW18Z9EBUpGg==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "4.7.1" + }, + "bin": { + "prisma": "build/index.js", + "prisma2": "build/index.js" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.1.0.tgz", + "integrity": "sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==" + }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -1755,6 +2247,33 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/readable-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", + "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -1793,6 +2312,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -1825,16 +2352,28 @@ "node": ">=4" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1887,12 +2426,52 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dependencies": { + "ret": "~0.2.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.6.0.tgz", + "integrity": "sha512-B9osKohb6L+EZ6Kve3wHKfsAClzOC/iISA2vSuCe5Jx5NAKiwitfxx8ZKYapHXr0sYRj7UZInT7pLb3rp2Yx6A==" + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-cookie-parser": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz", + "integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1928,6 +2507,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sonic-boom": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.1.tgz", + "integrity": "sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2090,6 +2685,22 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thread-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.2.0.tgz", + "integrity": "sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tiny-lru": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-10.0.1.tgz", + "integrity": "sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -2167,7 +2778,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -2247,8 +2857,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "1.10.2", @@ -2377,6 +2986,61 @@ "strip-json-comments": "^3.1.1" } }, + "@fastify/ajv-compiler": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz", + "integrity": "sha512-69JnK7Cot+ktn7LD5TikP3b7psBPX55tYpQa8WSumt8r117PCa2zwHnImfBtRWYExreJlI48hr0WZaVrTBGj7w==", + "requires": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, + "@fastify/cors": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.2.0.tgz", + "integrity": "sha512-qDgwpmg6C4D0D3nh8MTMuRXWyEwPnDZDBODaJv90FP2o9ukbahJByW4FtrM5Bpod5KbTf1oIExBmpItbUTQmHg==", + "requires": { + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.5" + } + }, + "@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==" + }, + "@fastify/error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.2.0.tgz", + "integrity": "sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==" + }, + "@fastify/fast-json-stringify-compiler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.1.0.tgz", + "integrity": "sha512-cTKBV2J9+u6VaKDhX7HepSfPSzw+F+TSd+k0wzifj4rG+4E5PjSFJCk19P8R6tr/72cuzgGd+mbB3jFT6lvAgw==", + "requires": { + "fast-json-stringify": "^5.0.0" + } + }, "@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -2432,6 +3096,25 @@ "fastq": "^1.6.0" } }, + "@prisma/client": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.7.1.tgz", + "integrity": "sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==", + "requires": { + "@prisma/engines-version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + } + }, + "@prisma/engines": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.7.1.tgz", + "integrity": "sha512-zWabHosTdLpXXlMefHmnouhXMoTB1+SCbUU3t4FCmdrtIOZcarPKU3Alto7gm/pZ9vHlGOXHCfVZ1G7OIrSbog==", + "devOptional": true + }, + "@prisma/engines-version": { + "version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz", + "integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==" + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2453,6 +3136,19 @@ "@types/node": "*" } }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, "acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", @@ -2478,6 +3174,32 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2493,6 +3215,11 @@ "color-convert": "^2.0.1" } }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2524,12 +3251,32 @@ "es-shim-unscopables": "^1.0.0" } }, + "atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" + }, + "avvio": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.0.tgz", + "integrity": "sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==", + "requires": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2540,6 +3287,15 @@ "concat-map": "0.0.1" } }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -2610,6 +3366,16 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "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.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2625,7 +3391,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -2963,17 +3728,31 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, "events-to-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", "integrity": "sha512-inRWzRY7nG+aXZxBzEqYKB3HPgwflZRopAjDCHv0whhRx+MTUr1ei0ICZUypdyE0HRm4L2d5VEcIqLD6yl+BFA==", "dev": true }, + "fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-diff": { "version": "1.2.0", @@ -2987,17 +3766,92 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "fast-json-stringify": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.5.0.tgz", + "integrity": "sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==", + "requires": { + "@fastify/deepmerge": "^1.0.0", + "ajv": "^8.10.0", + "ajv-formats": "^2.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "rfdc": "^1.2.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-querystring": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.0.0.tgz", + "integrity": "sha512-3LQi62IhQoDlmt4ULCYmh17vRO2EtS7hTSsG4WwoKWgV7GLMKBOecEh+aiavASnLx8I2y89OD33AGLo0ccRhzA==", + "requires": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "fast-redact": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", + "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==" + }, + "fast-uri": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", + "integrity": "sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==" + }, + "fastify": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.10.2.tgz", + "integrity": "sha512-0T+4zI6N3S8ex0LCZi3H4FasJR4AzWw834fUkPWvV8r6GBJkLmAOfFxH8f5V29Plef24IK0QSQD/tz1Nx+1UOA==", + "requires": { + "@fastify/ajv-compiler": "^3.3.1", + "@fastify/error": "^3.0.0", + "@fastify/fast-json-stringify-compiler": "^4.1.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.2.0", + "content-type": "^1.0.4", + "find-my-way": "^7.3.0", + "light-my-request": "^5.6.1", + "pino": "^8.5.0", + "process-warning": "^2.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.5.0", + "semver": "^7.3.7", + "tiny-lru": "^10.0.0" + } + }, + "fastify-plugin": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.4.0.tgz", + "integrity": "sha512-ovwFQG2qNy3jcCROiWpr94Hs0le+c7N/3t7m9aVwbFhkxcR/esp2xu25dP8e617HpQdmeDv+gFX4zagdUhDByw==" + }, "fastq": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -3011,6 +3865,16 @@ "flat-cache": "^3.0.4" } }, + "find-my-way": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.3.1.tgz", + "integrity": "sha512-kGvM08SOkqvheLcuQ8GW9t/H901Qb9rZEbcNWbXopzy4jDRoaJpJoObPSKf4MnQLZ20ZTp7rL5MpF6rf+pqmyg==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^2.0.0" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3037,6 +3901,11 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3177,6 +4046,11 @@ "has-symbols": "^1.0.2" } }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, "ignore": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", @@ -3226,6 +4100,11 @@ "side-channel": "^1.0.4" } }, + "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==" + }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -3409,6 +4288,16 @@ "type-check": "~0.4.0" } }, + "light-my-request": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.8.0.tgz", + "integrity": "sha512-4BtD5C+VmyTpzlDPCZbsatZMJVgUIciSOwYhJDCbLffPZ35KoDkDj4zubLeHDEb35b4kkPeEv5imbh+RJxK/Pg==", + "requires": { + "cookie": "^0.5.0", + "process-warning": "^2.0.0", + "set-cookie-parser": "^2.4.1" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3424,6 +4313,14 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "metatests": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/metatests/-/metatests-0.8.2.tgz", @@ -3461,11 +4358,18 @@ "yallist": "^4.0.0" } }, + "mnemonist": { + "version": "0.39.5", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", + "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", + "requires": { + "obliterator": "^2.0.1" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "natural-compare": { "version": "1.4.0", @@ -3508,6 +4412,16 @@ "es-abstract": "^1.20.4" } }, + "obliterator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", + "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==" + }, + "on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3588,6 +4502,38 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "pino": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.7.0.tgz", + "integrity": "sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==", + "requires": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + } + }, + "pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "requires": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "pino-std-serializers": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", + "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3609,11 +4555,38 @@ "fast-diff": "^1.1.2" } }, + "prisma": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.7.1.tgz", + "integrity": "sha512-CCQP+m+1qZOGIZlvnL6T3ZwaU0LAleIHYFPN9tFSzjs/KL6vH9rlYbGOkTuG9Q1s6Ki5D0LJlYlW18Z9EBUpGg==", + "devOptional": true, + "requires": { + "@prisma/engines": "4.7.1" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "process-warning": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.1.0.tgz", + "integrity": "sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==" + }, + "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==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "queue-microtask": { "version": "1.2.3", @@ -3621,6 +4594,27 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "readable-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", + "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10" + } + }, + "real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -3644,6 +4638,11 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -3667,11 +4666,20 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==" + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" }, "rimraf": { "version": "3.0.2", @@ -3702,12 +4710,43 @@ "is-regex": "^1.1.4" } }, + "safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "requires": { + "ret": "~0.2.0" + } + }, + "safe-stable-stringify": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", + "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==" + }, + "secure-json-parse": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.6.0.tgz", + "integrity": "sha512-B9osKohb6L+EZ6Kve3wHKfsAClzOC/iISA2vSuCe5Jx5NAKiwitfxx8ZKYapHXr0sYRj7UZInT7pLb3rp2Yx6A==" + }, + "semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "set-cookie-parser": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz", + "integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3734,6 +4773,19 @@ "object-inspect": "^1.9.0" } }, + "sonic-boom": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.2.1.tgz", + "integrity": "sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==", + "requires": { + "atomic-sleep": "^1.0.0" + } + }, + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3853,6 +4905,19 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "thread-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.2.0.tgz", + "integrity": "sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ==", + "requires": { + "real-require": "^0.2.0" + } + }, + "tiny-lru": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-10.0.1.tgz", + "integrity": "sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==" + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -3911,7 +4976,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -3976,8 +5040,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 00b4e08..a8ccddf 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "check": "npx tsc --project .", "start": "node main.js", + "build": "prisma generate", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -22,7 +23,11 @@ "engines": { "node": "18" }, - "dependencies": {}, + "dependencies": { + "@fastify/cors": "^8.2.0", + "@prisma/client": "^4.7.1", + "fastify": "^4.10.2" + }, "devDependencies": { "@types/node": "^18.7.8", "@types/ws": "^8.5.3", @@ -33,6 +38,7 @@ "eslint-plugin-prettier": "^4.2.1", "metatests": "^0.8.2", "prettier": "^2.7.1", + "prisma": "^4.7.1", "typescript": "^4.7.4" } -} +} \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..90b5f7a --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,87 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model User { + id String @id @default(uuid()) + email String @unique + firstName String + lastName String + passwordHash String + createdAt DateTime @default(now()) + udpatedAt DateTime @updatedAt + + employments Employee[] + companies Company[] +} + +model Company { + id String @id @default(uuid()) + name String @unique + domain String @unique + description String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + owner User @relation(fields: [ownerId], references: [id]) + ownerId String + employees Employee[] + projects Project[] + permissions Permissions[] +} + +model Employee { + id String @id @default(uuid()) + type EmployeeType @default(Normal) + phone String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id]) + userId String + company Company @relation(fields: [companyId], references: [id]) + companyId String + permissions Permissions? + projects Project[] +} + +model Permissions { + accessToCMS Boolean + + company Company @relation(fields: [companyId], references: [id]) + companyId String + employee Employee @relation(fields: [employeeId], references: [id]) + employeeId String @unique + + @@id(fields: [companyId, employeeId]) +} + +model Project { + id String @id @default(uuid()) + name String + description String? + startDate DateTime? + endDate DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + company Company @relation(fields: [companyId], references: [id]) + companyId String + members Employee[] + + @@unique([companyId, name]) +} + +enum EmployeeType { + Normal + Cooperation + Intern +} From 54121110451f96d352e931fe2b3d7c1f0be67be2 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Mon, 12 Dec 2022 22:10:08 +0200 Subject: [PATCH 02/19] added pino, added db and logger service --- package-lock.json | 329 ++++++++++++++++++++++++++++++++++++++++- package.json | 6 +- src/services/db.js | 21 +++ src/services/logger.js | 16 ++ 4 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 src/services/db.js create mode 100644 src/services/logger.js diff --git a/package-lock.json b/package-lock.json index f013788..0f37f23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "@fastify/cors": "^8.2.0", "@prisma/client": "^4.7.1", - "fastify": "^4.10.2" + "fastify": "^4.10.2", + "pino": "^8.7.0" }, "devDependencies": { "@types/node": "^18.7.8", @@ -22,6 +23,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", "metatests": "^0.8.2", + "pino-pretty": "^9.1.1", "prettier": "^2.7.1", "prisma": "^4.7.1", "typescript": "^4.7.4" @@ -562,6 +564,12 @@ "color-support": "bin.js" } }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -598,6 +606,15 @@ "node": ">= 8" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -672,6 +689,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", @@ -1076,6 +1102,12 @@ "integrity": "sha512-inRWzRY7nG+aXZxBzEqYKB3HPgwflZRopAjDCHv0whhRx+MTUr1ei0ICZUypdyE0HRm4L2d5VEcIqLD6yl+BFA==", "dev": true }, + "node_modules/fast-copy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.0.tgz", + "integrity": "sha512-4HzS+9pQ5Yxtv13Lhs1Z1unMXamBdn5nA4bEi1abYpDNSpSp7ODYQ1KPMF6nTatfEzgH6/zPvXKU1zvHiUjWlA==", + "dev": true + }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -1153,6 +1185,12 @@ "node": ">=6" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fast-uri": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", @@ -1461,6 +1499,70 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/help-me": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.1.0.tgz", + "integrity": "sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw==", + "dev": true, + "dependencies": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/help-me/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/help-me/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/help-me/node_modules/minimatch": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", + "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/help-me/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1761,6 +1863,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -2136,6 +2247,31 @@ "split2": "^4.0.0" } }, + "node_modules/pino-pretty": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-9.1.1.tgz", + "integrity": "sha512-iJrnjgR4FWQIXZkUF48oNgoRI9BpyMhaEmihonHeCnZ6F50ZHAS4YGfGBT/ZVNsPmd+hzkIPGzjKdY08+/yAXw==", + "dev": true, + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, "node_modules/pino-std-serializers": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", @@ -2219,6 +2355,16 @@ "node": ">= 0.10" } }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -2412,6 +2558,26 @@ "queue-microtask": "^1.2.2" } }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -2523,6 +2689,15 @@ "node": ">= 10.x" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2782,6 +2957,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3360,6 +3541,12 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, + "colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3387,6 +3574,12 @@ "which": "^2.0.1" } }, + "dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3438,6 +3631,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, "es-abstract": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", @@ -3744,6 +3946,12 @@ "integrity": "sha512-inRWzRY7nG+aXZxBzEqYKB3HPgwflZRopAjDCHv0whhRx+MTUr1ei0ICZUypdyE0HRm4L2d5VEcIqLD6yl+BFA==", "dev": true }, + "fast-copy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.0.tgz", + "integrity": "sha512-4HzS+9pQ5Yxtv13Lhs1Z1unMXamBdn5nA4bEi1abYpDNSpSp7ODYQ1KPMF6nTatfEzgH6/zPvXKU1zvHiUjWlA==", + "dev": true + }, "fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -3816,6 +4024,12 @@ "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.1.2.tgz", "integrity": "sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==" }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "fast-uri": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", @@ -4046,6 +4260,60 @@ "has-symbols": "^1.0.2" } }, + "help-me": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.1.0.tgz", + "integrity": "sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw==", + "dev": true, + "requires": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", + "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4242,6 +4510,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true + }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -4529,6 +4803,28 @@ "split2": "^4.0.0" } }, + "pino-pretty": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-9.1.1.tgz", + "integrity": "sha512-iJrnjgR4FWQIXZkUF48oNgoRI9BpyMhaEmihonHeCnZ6F50ZHAS4YGfGBT/ZVNsPmd+hzkIPGzjKdY08+/yAXw==", + "dev": true, + "requires": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, "pino-std-serializers": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", @@ -4583,6 +4879,16 @@ "ipaddr.js": "1.9.1" } }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -4699,6 +5005,12 @@ "queue-microtask": "^1.2.2" } }, + "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==", + "dev": true + }, "safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -4786,6 +5098,15 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4980,6 +5301,12 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index a8ccddf..a32d8c3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "main": "main.js", "scripts": { - "check": "npx tsc --project .", + "check": "tsc --project .", "start": "node main.js", "build": "prisma generate", "test": "echo \"Error: no test specified\" && exit 1" @@ -26,7 +26,8 @@ "dependencies": { "@fastify/cors": "^8.2.0", "@prisma/client": "^4.7.1", - "fastify": "^4.10.2" + "fastify": "^4.10.2", + "pino": "^8.7.0" }, "devDependencies": { "@types/node": "^18.7.8", @@ -37,6 +38,7 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-prettier": "^4.2.1", "metatests": "^0.8.2", + "pino-pretty": "^9.1.1", "prettier": "^2.7.1", "prisma": "^4.7.1", "typescript": "^4.7.4" diff --git a/src/services/db.js b/src/services/db.js new file mode 100644 index 0000000..1175851 --- /dev/null +++ b/src/services/db.js @@ -0,0 +1,21 @@ +'use strict'; + +const { PrismaClient } = require('@prisma/client'); + +module.exports.options = (extendWith = {}) => ({ + log: ['info', 'query'], + errorFormat: 'minimal', + ...extendWith, +}); + +module.exports.init = async ({ logger }, options = {}) => { + const client = new PrismaClient(options); + await client.$connect(); + logger.info('DB connected'); + return client; +}; + +module.exports.teardown = async ({ db, logger }) => { + await db.$disconnect(); + logger.info('DB disconnected'); +}; diff --git a/src/services/logger.js b/src/services/logger.js new file mode 100644 index 0000000..5a61316 --- /dev/null +++ b/src/services/logger.js @@ -0,0 +1,16 @@ +'use strict'; + +const { pino } = require('pino'); + +module.exports.options = (extendWith = {}) => ({ + level: 'debug', + transport: { + target: 'pino-pretty', + options: { + ignore: 'pid,hostname', + }, + }, + ...extendWith, +}); + +module.exports.init = (options) => pino(options); From 3151c61637ff69351ce72295316f08e4f35f69de Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Mon, 12 Dec 2022 23:58:46 +0200 Subject: [PATCH 03/19] added session to domain, created basic lib --- prisma/schema.prisma | 11 +++++++++++ src/lib/crypto.js | 25 +++++++++++++++++++++++++ src/lib/error.js | 8 ++++++++ src/lib/schema.js | 6 ++++++ 4 files changed, 50 insertions(+) create mode 100644 src/lib/crypto.js create mode 100644 src/lib/error.js create mode 100644 src/lib/schema.js diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 90b5f7a..eb17636 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,10 +19,21 @@ model User { createdAt DateTime @default(now()) udpatedAt DateTime @updatedAt + sessions Session[] employments Employee[] companies Company[] } +model Session { + id String @id @default(uuid()) + token String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id]) + userId String +} + model Company { id String @id @default(uuid()) name String @unique diff --git a/src/lib/crypto.js b/src/lib/crypto.js new file mode 100644 index 0000000..5cba507 --- /dev/null +++ b/src/lib/crypto.js @@ -0,0 +1,25 @@ +const crypto = require('node:crypto'); + +const SALT_LENGTH = 15; +const KEY_LENGTH = 64; + +module.exports.hash = (password) => + new Promise((resolve, reject) => { + const salt = crypto.randomBytes(SALT_LENGTH).toString('base64'); + crypto.scrypt(password, salt, KEY_LENGTH, (err, result) => { + if (err) reject(err); + resolve(salt + ':' + result.toString('base64')); + }); + }); + +module.exports.compare = (password, hash) => + new Promise((resolve, reject) => { + const [salt, hashedPassword] = hash.split(':'); + crypto.scrypt(password, salt, KEY_LENGTH, (err, derivedKey) => { + if (err !== null) reject(err); + resolve(derivedKey.toString('hex') === hashedPassword); + }); + }); + +module.exports.random = (length = 36) => + crypto.randomBytes(length).toString('base64'); diff --git a/src/lib/error.js b/src/lib/error.js new file mode 100644 index 0000000..134925a --- /dev/null +++ b/src/lib/error.js @@ -0,0 +1,8 @@ +class AppError extends Error { } + +module.exports.AppError = AppError; + +module.exports.handleError = (error) => + error instanceof AppError + ? [400, error.message, 'warn', undefined] + : [500, 'Internal server error', 'error', error?.stack]; diff --git a/src/lib/schema.js b/src/lib/schema.js new file mode 100644 index 0000000..856b82b --- /dev/null +++ b/src/lib/schema.js @@ -0,0 +1,6 @@ +module.exports.toObject = ({ required = [], properties }) => ({ + type: 'object', + additionalProperties: false, + required, + properties, +}); From 9d594ff4481501a87b15aeb66625b155f9dc22a5 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Mon, 12 Dec 2022 23:59:04 +0200 Subject: [PATCH 04/19] added auth routes --- src/routes/auth.js | 143 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/routes/auth.js diff --git a/src/routes/auth.js b/src/routes/auth.js new file mode 100644 index 0000000..2022d8d --- /dev/null +++ b/src/routes/auth.js @@ -0,0 +1,143 @@ +const crypto = require('../lib/crypto'); +const schema = require('../lib/schema'); +const { AppError, handleError } = require('../lib/error'); + +module.exports = function ({ db, logger }) { + return async (server) => { + server.route({ + method: 'POST', + url: '/sign-up', + schema: { + body: schema.toObject({ + required: ['email', 'password', 'firstName', 'lastName'], + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + firstName: { type: 'string' }, + lastName: { type: 'string' }, + }, + }), + }, + handler: async (req, res) => { + try { + const { email, password, ...rest } = req.body; + + const exists = await db.user.findUnique({ where: { email } }); + if (exists) throw new AppError('Already exists'); + + const passwordHash = await crypto.hash(password); + + const { id: userId } = await db.user.create({ + data: { email, passwordHash, ...rest }, + }); + + const token = crypto.random(); + await db.session.create({ data: { userId, token } }); + + res.code(200).send({ userId, token }); + } catch (error) { + const [status, message, level, stack] = handleError(error); + + logger[level]({ stack }, `auth/sign-up error - ${message}`); + res.code(status).send({ message }); + } + }, + }); + + server.route({ + method: 'POST', + url: '/sign-in', + schema: { + body: schema.toObject({ + required: ['email', 'password'], + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + }, + }), + }, + handler: async (req, res) => { + try { + const { email, password } = req.body; + + const user = await db.user.findUnique({ where: { email } }); + if (!user) throw new AppError('Invalid credentials'); + + const valid = await crypto.compare(password, user.passwordHash); + if (!valid) throw new AppError('Invalid credentials'); + + const { id: userId } = user; + const token = crypto.random(); + await db.session.create({ data: { userId, token } }); + + res.code(200).send({ userId, token }); + } catch (error) { + const [status, message, level, stack] = handleError(error); + + logger[level]({ stack }, `auth/sign-in error - ${message}`); + res.code(status).send({ message }); + } + }, + }); + + server.route({ + method: 'POST', + url: '/refresh', + schema: { + body: schema.toObject({ + required: ['token'], + properties: { token: { type: 'string' } }, + }), + }, + handler: async (req, res) => { + try { + const { token } = req.body; + + const session = await db.session.findUnique({ where: { token } }); + if (!session) throw new AppError('Not found'); + + const newToken = crypto.random(); + await db.session.update({ + where: { id: session.id }, + data: { token: newToken }, + }); + + res.code(200).send({ token: newToken }); + } catch (error) { + const [status, message, level, stack] = handleError(error); + + logger[level]({ stack }, `auth/sign-in error - ${message}`); + res.code(status).send({ message }); + } + }, + }); + + server.route({ + method: 'POST', + url: '/sign-out', + schema: { + body: schema.toObject({ + required: ['token'], + properties: { token: { type: 'string' } }, + }), + }, + handler: async (req, res) => { + try { + const { token } = req.body; + + const exists = await db.session + .delete({ where: { token } }) + .catch(() => false); + if (!exists) throw new AppError('Not found'); + + res.code(204).send(); + } catch (error) { + const [status, message, level, stack] = handleError(error); + + logger[level]({ stack }, `auth/sign-in error - ${message}`); + res.code(status).send({ message }); + } + }, + }); + }; +}; From f39fb6db16e7b258ef7b5c7ed22488a7cc0b0fc2 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 13 Dec 2022 00:23:26 +0200 Subject: [PATCH 05/19] added server --- src/hello.js | 1 - src/routes/index.js | 3 +++ src/server.js | 37 +++++++++++++++++++++++++++++++++++++ src/services/index.js | 4 ++++ 4 files changed, 44 insertions(+), 1 deletion(-) delete mode 100644 src/hello.js create mode 100644 src/routes/index.js create mode 100644 src/server.js create mode 100644 src/services/index.js diff --git a/src/hello.js b/src/hello.js deleted file mode 100644 index f239eb9..0000000 --- a/src/hello.js +++ /dev/null @@ -1 +0,0 @@ -console.log('Hi there'); diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..cbee3eb --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,3 @@ +const auth = require('./auth'); + +module.exports = { auth }; diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..2ed330b --- /dev/null +++ b/src/server.js @@ -0,0 +1,37 @@ +const { fastify } = require('fastify'); +const services = require('./services'); +const routes = require('./routes'); + +module.exports.options = (extendWith = {}) => ({ + host: '0.0.0.0', + port: '9000', + ...extendWith, +}); + +module.exports.deps = async (replaceWith = {}) => { + const logger = + replaceWith.logger || services.logger.init(services.logger.options()); + const db = + replaceWith.db || + (await services.db.init({ logger }, services.db.options())); + return { logger, db }; +}; + +module.exports.init = async ({ logger, db }, options = {}) => { + const server = fastify({ logger, ...options }); + + await server.register(routes.auth({ db, logger }), { prefix: '/api/auth' }); + + return server; +}; + +module.exports.listen = async ({ logger }, server, options) => { + const { host, port } = options; + await server.listen({ host, port }); + logger.info(`Server listens to ${host}:${port}`); +}; + +module.exports.teardown = async ({ logger }, server) => { + await server.close(); + logger.info('Server closed'); +}; diff --git a/src/services/index.js b/src/services/index.js new file mode 100644 index 0000000..6d0a66f --- /dev/null +++ b/src/services/index.js @@ -0,0 +1,4 @@ +const db = require('./db'); +const logger = require('./logger'); + +module.exports = { db, logger }; From 5be272b19c409e804310b2cc80cc42640a74ce64 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 13 Dec 2022 00:41:45 +0200 Subject: [PATCH 06/19] updated main, converted everything to es modules --- main.js | 24 +++++++++++++++++++++++- src/lib/crypto.js | 8 ++++---- src/lib/error.js | 6 ++---- src/lib/schema.js | 2 +- src/routes/auth.js | 10 +++++----- src/routes/index.js | 4 ++-- src/server.js | 18 ++++++++++-------- src/services/db.js | 10 ++++------ src/services/index.js | 6 +++--- src/services/logger.js | 8 +++----- 10 files changed, 57 insertions(+), 39 deletions(-) diff --git a/main.js b/main.js index f239eb9..5ba87fa 100644 --- a/main.js +++ b/main.js @@ -1 +1,23 @@ -console.log('Hi there'); +import * as server from './src/server.js'; + +try { + const deps = await server.deps(); + const options = server.options(); + const httpServer = await server.init(deps); + await server.listen(deps, httpServer, options); + process.on('SIGINT', () => { + deps.logger.info('caught SIGINT'); + server + .teardown(deps, httpServer) + .then(() => { + process.exit(0); + }) + .catch((err) => { + console.error(err); + process.exit(-1); + }); + }); +} catch (error) { + console.error(error); + process.exit(-1); +} diff --git a/src/lib/crypto.js b/src/lib/crypto.js index 5cba507..5e9bb32 100644 --- a/src/lib/crypto.js +++ b/src/lib/crypto.js @@ -1,9 +1,9 @@ -const crypto = require('node:crypto'); +import crypto from 'node:crypto'; const SALT_LENGTH = 15; const KEY_LENGTH = 64; -module.exports.hash = (password) => +export const hash = (password) => new Promise((resolve, reject) => { const salt = crypto.randomBytes(SALT_LENGTH).toString('base64'); crypto.scrypt(password, salt, KEY_LENGTH, (err, result) => { @@ -12,7 +12,7 @@ module.exports.hash = (password) => }); }); -module.exports.compare = (password, hash) => +export const compare = (password, hash) => new Promise((resolve, reject) => { const [salt, hashedPassword] = hash.split(':'); crypto.scrypt(password, salt, KEY_LENGTH, (err, derivedKey) => { @@ -21,5 +21,5 @@ module.exports.compare = (password, hash) => }); }); -module.exports.random = (length = 36) => +export const random = (length = 36) => crypto.randomBytes(length).toString('base64'); diff --git a/src/lib/error.js b/src/lib/error.js index 134925a..13bba81 100644 --- a/src/lib/error.js +++ b/src/lib/error.js @@ -1,8 +1,6 @@ -class AppError extends Error { } +export class AppError extends Error { } -module.exports.AppError = AppError; - -module.exports.handleError = (error) => +export const handleError = (error) => error instanceof AppError ? [400, error.message, 'warn', undefined] : [500, 'Internal server error', 'error', error?.stack]; diff --git a/src/lib/schema.js b/src/lib/schema.js index 856b82b..d30f06e 100644 --- a/src/lib/schema.js +++ b/src/lib/schema.js @@ -1,4 +1,4 @@ -module.exports.toObject = ({ required = [], properties }) => ({ +export const toObject = ({ required = [], properties }) => ({ type: 'object', additionalProperties: false, required, diff --git a/src/routes/auth.js b/src/routes/auth.js index 2022d8d..7af5ad3 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,8 +1,8 @@ -const crypto = require('../lib/crypto'); -const schema = require('../lib/schema'); -const { AppError, handleError } = require('../lib/error'); +import * as crypto from '../lib/crypto.js'; +import * as schema from '../lib/schema.js'; +import { AppError, handleError } from '../lib/error.js'; -module.exports = function ({ db, logger }) { +export default function ({ db, logger }) { return async (server) => { server.route({ method: 'POST', @@ -140,4 +140,4 @@ module.exports = function ({ db, logger }) { }, }); }; -}; +} diff --git a/src/routes/index.js b/src/routes/index.js index cbee3eb..871e52b 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,3 +1,3 @@ -const auth = require('./auth'); +import auth from './auth.js'; -module.exports = { auth }; +export default { auth }; diff --git a/src/server.js b/src/server.js index 2ed330b..d5fdeec 100644 --- a/src/server.js +++ b/src/server.js @@ -1,14 +1,15 @@ -const { fastify } = require('fastify'); -const services = require('./services'); -const routes = require('./routes'); +import { fastify } from 'fastify'; +import cors from '@fastify/cors'; +import services from './services/index.js'; +import routes from './routes/index.js'; -module.exports.options = (extendWith = {}) => ({ +export const options = (extendWith = {}) => ({ host: '0.0.0.0', port: '9000', ...extendWith, }); -module.exports.deps = async (replaceWith = {}) => { +export const deps = async (replaceWith = {}) => { const logger = replaceWith.logger || services.logger.init(services.logger.options()); const db = @@ -17,21 +18,22 @@ module.exports.deps = async (replaceWith = {}) => { return { logger, db }; }; -module.exports.init = async ({ logger, db }, options = {}) => { +export const init = async ({ logger, db }, options = {}) => { const server = fastify({ logger, ...options }); + await server.register(cors); await server.register(routes.auth({ db, logger }), { prefix: '/api/auth' }); return server; }; -module.exports.listen = async ({ logger }, server, options) => { +export const listen = async ({ logger }, server, options) => { const { host, port } = options; await server.listen({ host, port }); logger.info(`Server listens to ${host}:${port}`); }; -module.exports.teardown = async ({ logger }, server) => { +export const teardown = async ({ logger }, server) => { await server.close(); logger.info('Server closed'); }; diff --git a/src/services/db.js b/src/services/db.js index 1175851..78baa81 100644 --- a/src/services/db.js +++ b/src/services/db.js @@ -1,21 +1,19 @@ -'use strict'; +import { PrismaClient } from '@prisma/client'; -const { PrismaClient } = require('@prisma/client'); - -module.exports.options = (extendWith = {}) => ({ +export const options = (extendWith = {}) => ({ log: ['info', 'query'], errorFormat: 'minimal', ...extendWith, }); -module.exports.init = async ({ logger }, options = {}) => { +export const init = async ({ logger }, options = {}) => { const client = new PrismaClient(options); await client.$connect(); logger.info('DB connected'); return client; }; -module.exports.teardown = async ({ db, logger }) => { +export const teardown = async ({ db, logger }) => { await db.$disconnect(); logger.info('DB disconnected'); }; diff --git a/src/services/index.js b/src/services/index.js index 6d0a66f..922b079 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -1,4 +1,4 @@ -const db = require('./db'); -const logger = require('./logger'); +import * as db from './db.js'; +import * as logger from './logger.js'; -module.exports = { db, logger }; +export default { db, logger }; diff --git a/src/services/logger.js b/src/services/logger.js index 5a61316..31ad6d2 100644 --- a/src/services/logger.js +++ b/src/services/logger.js @@ -1,8 +1,6 @@ -'use strict'; +import { pino } from 'pino'; -const { pino } = require('pino'); - -module.exports.options = (extendWith = {}) => ({ +export const options = (extendWith = {}) => ({ level: 'debug', transport: { target: 'pino-pretty', @@ -13,4 +11,4 @@ module.exports.options = (extendWith = {}) => ({ ...extendWith, }); -module.exports.init = (options) => pino(options); +export const init = (options) => pino(options); From 67e8a94ebb7dc4660053b5413f096efbe7d07907 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Mon, 19 Dec 2022 16:17:54 +0200 Subject: [PATCH 07/19] updated prisma schema to have money transfer domain models --- .gitignore | 3 +- prisma/schema.prisma | 90 ++++++++++++++++---------------------------- 2 files changed, 35 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index df1093e..27a64c2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules *.pem *.done .DS_Store -.env \ No newline at end of file +.env +prisma/json-schema.json \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index eb17636..185c025 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -19,9 +19,8 @@ model User { createdAt DateTime @default(now()) udpatedAt DateTime @updatedAt - sessions Session[] - employments Employee[] - companies Company[] + sessions Session[] + accounts Account[] } model Session { @@ -34,65 +33,42 @@ model Session { userId String } -model Company { - id String @id @default(uuid()) - name String @unique - domain String @unique - description String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - owner User @relation(fields: [ownerId], references: [id]) - ownerId String - employees Employee[] - projects Project[] - permissions Permissions[] -} - -model Employee { - id String @id @default(uuid()) - type EmployeeType @default(Normal) - phone String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt +model Account { + id String @id @default(uuid()) + balance Float + currency Currency + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) - userId String - company Company @relation(fields: [companyId], references: [id]) - companyId String - permissions Permissions? - projects Project[] + user User @relation(fields: [userId], references: [id]) + userId String + transactionsIn Transaction[] @relation(name: "receiver") + transactionsOut Transaction[] @relation(name: "sender") } -model Permissions { - accessToCMS Boolean - - company Company @relation(fields: [companyId], references: [id]) - companyId String - employee Employee @relation(fields: [employeeId], references: [id]) - employeeId String @unique - - @@id(fields: [companyId, employeeId]) +model Transaction { + id String @id @default(uuid()) + amount Float + currency Currency + state TransactionState @default(INITIAL) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + from Account @relation(name: "sender", fields: [fromId], references: [id]) + fromId String + to Account @relation(name: "receiver", fields: [toId], references: [id]) + toId String } -model Project { - id String @id @default(uuid()) - name String - description String? - startDate DateTime? - endDate DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - company Company @relation(fields: [companyId], references: [id]) - companyId String - members Employee[] - - @@unique([companyId, name]) +enum TransactionState { + INITIAL + DEBITED + COMPLETED + FAILED } -enum EmployeeType { - Normal - Cooperation - Intern +enum Currency { + USD + EUR + UAH } From 7425f7b3dc17277245fc1f6868f91bc658742b43 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Wed, 21 Dec 2022 12:13:11 +0200 Subject: [PATCH 08/19] updated project structure, separated different type of services, added Money Transfer domain models --- global.d.ts | 20 +++ main.js | 22 +-- package-lock.json | 132 ++++++++++++++ package.json | 3 +- src/app.js | 26 +++ src/config.js | 15 ++ src/lib/crypto.d.ts | 3 + src/lib/error.d.ts | 3 + src/lib/error.js | 2 +- src/lib/schema.js | 6 - src/routes/auth.js | 221 +++++++++--------------- src/routes/index.js | 19 +- src/server.js | 39 ----- src/services/application/auth.d.ts | 17 ++ src/services/application/auth.js | 55 ++++++ src/services/application/index.d.ts | 9 + src/services/application/index.js | 21 +++ src/services/application/server.d.ts | 33 ++++ src/services/application/server.js | 42 +++++ src/services/index.js | 4 - src/services/infrastructure/db.d.ts | 7 + src/services/{ => infrastructure}/db.js | 6 - src/services/infrastructure/index.d.ts | 8 + src/services/infrastructure/index.js | 15 ++ src/services/infrastructure/logger.d.ts | 5 + src/services/infrastructure/logger.js | 39 +++++ src/services/logger.js | 14 -- 27 files changed, 561 insertions(+), 225 deletions(-) create mode 100644 global.d.ts create mode 100644 src/app.js create mode 100644 src/config.js create mode 100644 src/lib/crypto.d.ts create mode 100644 src/lib/error.d.ts delete mode 100644 src/lib/schema.js delete mode 100644 src/server.js create mode 100644 src/services/application/auth.d.ts create mode 100644 src/services/application/auth.js create mode 100644 src/services/application/index.d.ts create mode 100644 src/services/application/index.js create mode 100644 src/services/application/server.d.ts create mode 100644 src/services/application/server.js delete mode 100644 src/services/index.js create mode 100644 src/services/infrastructure/db.d.ts rename src/services/{ => infrastructure}/db.js (73%) create mode 100644 src/services/infrastructure/index.d.ts create mode 100644 src/services/infrastructure/index.js create mode 100644 src/services/infrastructure/logger.d.ts create mode 100644 src/services/infrastructure/logger.js delete mode 100644 src/services/logger.js diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 0000000..ed380c6 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,20 @@ +import type { + ApplicationOptions, + ApplicationServices, +} from './src/services/application'; +import type { + InfrastructureOptions, + InfrastructureServices, +} from './src/services/infrastructure' + +declare global { + type Config = { + application: ApplicationOptions; + infrastructure: InfrastructureOptions; + }; + type Services = { + application: ApplicationServices; + infrastructure: InfrastructureServices; + } + type Router = (deps: Services) => Promise; +} \ No newline at end of file diff --git a/main.js b/main.js index 5ba87fa..9cfc293 100644 --- a/main.js +++ b/main.js @@ -1,21 +1,13 @@ -import * as server from './src/server.js'; +import * as app from './src/app.js'; +let stopping = false; try { - const deps = await server.deps(); - const options = server.options(); - const httpServer = await server.init(deps); - await server.listen(deps, httpServer, options); + const teardown = await app.init(); process.on('SIGINT', () => { - deps.logger.info('caught SIGINT'); - server - .teardown(deps, httpServer) - .then(() => { - process.exit(0); - }) - .catch((err) => { - console.error(err); - process.exit(-1); - }); + if (!stopping) { + stopping = true; + teardown().then(() => process.exit(0)); + } }); } catch (error) { console.error(error); diff --git a/package-lock.json b/package-lock.json index 0f37f23..80173f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "pino": "^8.7.0" }, "devDependencies": { + "@fastify/type-provider-json-schema-to-ts": "^2.2.1", "@types/node": "^18.7.8", "@types/ws": "^8.5.3", "eslint": "^8.22.0", @@ -32,6 +33,19 @@ "node": "18" } }, + "node_modules/@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -112,6 +126,16 @@ "fast-json-stringify": "^5.0.0" } }, + "node_modules/@fastify/type-provider-json-schema-to-ts": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-2.2.1.tgz", + "integrity": "sha512-FJKwd6RsHHJ6yEwTQZRqCfSt3+ormyn6j7r746gdCBHxJPXeQ8hVZg0dTlgQGbjjal5tqzEleBmh/zM56fEKUw==", + "dev": true, + "peerDependencies": { + "fastify": "^4.0.0", + "json-schema-to-ts": "^2.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -221,6 +245,13 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz", "integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==" }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true, + "peer": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1894,6 +1925,22 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-schema-to-ts": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.6.2.tgz", + "integrity": "sha512-RrcvhZUcTAtfMVSvHIq3h/tELToha68V/1kGeQ2ggBv/4Bv31Zjbqis+b+Hiwibj6GO5WLA9PE4X93C8VTJ1TA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@types/json-schema": "^7.0.9", + "ts-algebra": "^1.1.1", + "ts-toolbelt": "^9.6.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2420,6 +2467,13 @@ "node": ">= 12.13.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "peer": true + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -2876,6 +2930,23 @@ "node": ">=6" } }, + "node_modules/ts-algebra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.1.1.tgz", + "integrity": "sha512-W43a3/BN0Tp4SgRNERQF/QPVuY1rnHkgCr/fISLY0Ycu05P0NWPYRuViU8JFn+pFZuY6/zp9TgET1fxMzppR/Q==", + "dev": true, + "peer": true, + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "dev": true, + "peer": true + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -3150,6 +3221,16 @@ } }, "dependencies": { + "@babel/runtime": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", + "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "dev": true, + "peer": true, + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -3222,6 +3303,13 @@ "fast-json-stringify": "^5.0.0" } }, + "@fastify/type-provider-json-schema-to-ts": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-2.2.1.tgz", + "integrity": "sha512-FJKwd6RsHHJ6yEwTQZRqCfSt3+ormyn6j7r746gdCBHxJPXeQ8hVZg0dTlgQGbjjal5tqzEleBmh/zM56fEKUw==", + "dev": true, + "requires": {} + }, "@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -3296,6 +3384,13 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz", "integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==" }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true, + "peer": true + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -4531,6 +4626,19 @@ "argparse": "^2.0.1" } }, + "json-schema-to-ts": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.6.2.tgz", + "integrity": "sha512-RrcvhZUcTAtfMVSvHIq3h/tELToha68V/1kGeQ2ggBv/4Bv31Zjbqis+b+Hiwibj6GO5WLA9PE4X93C8VTJ1TA==", + "dev": true, + "peer": true, + "requires": { + "@babel/runtime": "^7.18.3", + "@types/json-schema": "^7.0.9", + "ts-algebra": "^1.1.1", + "ts-toolbelt": "^9.6.0" + } + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4921,6 +5029,13 @@ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "peer": true + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -5239,6 +5354,23 @@ "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-10.0.1.tgz", "integrity": "sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==" }, + "ts-algebra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.1.1.tgz", + "integrity": "sha512-W43a3/BN0Tp4SgRNERQF/QPVuY1rnHkgCr/fISLY0Ycu05P0NWPYRuViU8JFn+pFZuY6/zp9TgET1fxMzppR/Q==", + "dev": true, + "peer": true, + "requires": { + "ts-toolbelt": "^9.6.0" + } + }, + "ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "dev": true, + "peer": true + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", diff --git a/package.json b/package.json index a32d8c3..7e642d5 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "pino": "^8.7.0" }, "devDependencies": { + "@fastify/type-provider-json-schema-to-ts": "^2.2.1", "@types/node": "^18.7.8", "@types/ws": "^8.5.3", "eslint": "^8.22.0", @@ -43,4 +44,4 @@ "prisma": "^4.7.1", "typescript": "^4.7.4" } -} \ No newline at end of file +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..6bb7167 --- /dev/null +++ b/src/app.js @@ -0,0 +1,26 @@ +import { config } from './config.js'; +import { + init as initAppServices, + teardown as teardownAppServices, +} from './services/application/index.js'; +import { + init as initInfraServices, + teardown as teardownInfraServices, +} from './services/infrastructure/index.js'; +import { routes } from './routes/index.js'; + +export const init = async () => { + const infrastructure = await initInfraServices(config.infrastructure); + const application = await initAppServices( + { infrastructure, routes }, + config.application, + ); + + const services = { application, infrastructure }; + + return async () => { + services.infrastructure.logger.info('Stopping application'); + await teardownAppServices(services); + await teardownInfraServices(infrastructure); + }; +}; diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..b073572 --- /dev/null +++ b/src/config.js @@ -0,0 +1,15 @@ +const env = process.env.NODE_ENV || 'development'; + +/** @type Config */ +export const config = { + application: { + server: { + host: process.env.HOST || '0.0.0.0', + port: Number(process.env.PORT) || 9000, + }, + }, + infrastructure: { + db: { errorFormat: 'minimal' }, + logger: { env }, + }, +}; diff --git a/src/lib/crypto.d.ts b/src/lib/crypto.d.ts new file mode 100644 index 0000000..786dcc4 --- /dev/null +++ b/src/lib/crypto.d.ts @@ -0,0 +1,3 @@ +export function hash(password: string): Promise; +export function compare(password: string, hash: string): Promise; +export function random(length?: number): string; \ No newline at end of file diff --git a/src/lib/error.d.ts b/src/lib/error.d.ts new file mode 100644 index 0000000..dbe5a77 --- /dev/null +++ b/src/lib/error.d.ts @@ -0,0 +1,3 @@ +export class AppError extends Error { } + +export function handleError(error: unknown): [number, string, 'warn' | 'error', string | undefined]; \ No newline at end of file diff --git a/src/lib/error.js b/src/lib/error.js index 13bba81..077be0a 100644 --- a/src/lib/error.js +++ b/src/lib/error.js @@ -1,4 +1,4 @@ -export class AppError extends Error { } +export class AppError extends Error {} export const handleError = (error) => error instanceof AppError diff --git a/src/lib/schema.js b/src/lib/schema.js deleted file mode 100644 index d30f06e..0000000 --- a/src/lib/schema.js +++ /dev/null @@ -1,6 +0,0 @@ -export const toObject = ({ required = [], properties }) => ({ - type: 'object', - additionalProperties: false, - required, - properties, -}); diff --git a/src/routes/auth.js b/src/routes/auth.js index 7af5ad3..972f9f3 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -1,143 +1,90 @@ -import * as crypto from '../lib/crypto.js'; -import * as schema from '../lib/schema.js'; -import { AppError, handleError } from '../lib/error.js'; - -export default function ({ db, logger }) { - return async (server) => { - server.route({ - method: 'POST', - url: '/sign-up', - schema: { - body: schema.toObject({ - required: ['email', 'password', 'firstName', 'lastName'], - properties: { - email: { type: 'string' }, - password: { type: 'string' }, - firstName: { type: 'string' }, - lastName: { type: 'string' }, - }, - }), - }, - handler: async (req, res) => { - try { - const { email, password, ...rest } = req.body; - - const exists = await db.user.findUnique({ where: { email } }); - if (exists) throw new AppError('Already exists'); - - const passwordHash = await crypto.hash(password); - - const { id: userId } = await db.user.create({ - data: { email, passwordHash, ...rest }, - }); - - const token = crypto.random(); - await db.session.create({ data: { userId, token } }); - - res.code(200).send({ userId, token }); - } catch (error) { - const [status, message, level, stack] = handleError(error); - - logger[level]({ stack }, `auth/sign-up error - ${message}`); - res.code(status).send({ message }); - } - }, - }); - - server.route({ - method: 'POST', - url: '/sign-in', - schema: { - body: schema.toObject({ - required: ['email', 'password'], - properties: { - email: { type: 'string' }, - password: { type: 'string' }, - }, - }), - }, - handler: async (req, res) => { - try { - const { email, password } = req.body; - - const user = await db.user.findUnique({ where: { email } }); - if (!user) throw new AppError('Invalid credentials'); - - const valid = await crypto.compare(password, user.passwordHash); - if (!valid) throw new AppError('Invalid credentials'); - - const { id: userId } = user; - const token = crypto.random(); - await db.session.create({ data: { userId, token } }); - - res.code(200).send({ userId, token }); - } catch (error) { - const [status, message, level, stack] = handleError(error); - - logger[level]({ stack }, `auth/sign-in error - ${message}`); - res.code(status).send({ message }); - } +/** @type function(Services): Promise */ +export const auth = async ({ application: { server, auth } }) => { + server.route({ + method: 'POST', + url: '/sign-up', + schema: /** @type {const} */ ({ + body: { + type: 'object', + additionalProperties: false, + required: ['email', 'password', 'firstName', 'lastName'], + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + firstName: { type: 'string' }, + lastName: { type: 'string' }, + }, }, - }); - - server.route({ - method: 'POST', - url: '/refresh', - schema: { - body: schema.toObject({ - required: ['token'], - properties: { token: { type: 'string' } }, - }), + }), + handler: async (req, res) => { + const data = req.body; + + const { userId, token } = await auth.signUp(data); + + res.code(200).send({ userId, token }); + }, + }); + + server.route({ + method: 'POST', + url: '/sign-in', + schema: /** @type {const} */ ({ + body: { + type: 'object', + additionalProperties: false, + required: ['email', 'password'], + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + }, }, - handler: async (req, res) => { - try { - const { token } = req.body; - - const session = await db.session.findUnique({ where: { token } }); - if (!session) throw new AppError('Not found'); - - const newToken = crypto.random(); - await db.session.update({ - where: { id: session.id }, - data: { token: newToken }, - }); - - res.code(200).send({ token: newToken }); - } catch (error) { - const [status, message, level, stack] = handleError(error); - - logger[level]({ stack }, `auth/sign-in error - ${message}`); - res.code(status).send({ message }); - } + }), + handler: async (req, res) => { + const { email, password } = req.body; + + const { userId, token } = await auth.signIn({ email, password }); + + res.code(200).send({ userId, token }); + }, + }); + + server.route({ + method: 'POST', + url: '/refresh', + schema: /** @type {const} */ ({ + body: { + type: 'object', + additionalProperties: false, + required: ['token'], + properties: { token: { type: 'string' } }, }, - }); - - server.route({ - method: 'POST', - url: '/sign-out', - schema: { - body: schema.toObject({ - required: ['token'], - properties: { token: { type: 'string' } }, - }), + }), + handler: async (req, res) => { + const { token } = req.body; + + const { token: newToken } = await auth.refresh({ token }); + + res.code(200).send({ token: newToken }); + }, + }); + + server.route({ + method: 'POST', + url: '/sign-out', + schema: /** @type {const} */ ({ + body: { + type: 'object', + additionalProperties: false, + required: ['token'], + properties: { token: { type: 'string' } }, }, - handler: async (req, res) => { - try { - const { token } = req.body; - - const exists = await db.session - .delete({ where: { token } }) - .catch(() => false); - if (!exists) throw new AppError('Not found'); + }), + handler: async (req, res) => { + const { token } = req.body; - res.code(204).send(); - } catch (error) { - const [status, message, level, stack] = handleError(error); + await auth.signOut({ token }); - logger[level]({ stack }, `auth/sign-in error - ${message}`); - res.code(status).send({ message }); - } - }, - }); - }; -} + res.code(204).send(); + }, + }); +}; diff --git a/src/routes/index.js b/src/routes/index.js index 871e52b..4ce05ac 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,3 +1,18 @@ -import auth from './auth.js'; +import { handleError } from '../lib/error.js'; +import { auth } from './auth.js'; -export default { auth }; +/** @type Router */ +export const routes = async (services) => { + const { server } = services.application; + const { logger } = services.infrastructure; + + server.setErrorHandler(server.errorHandler); + server.setErrorHandler((error, req, res) => { + const [status, message, level, stack] = handleError(error); + + logger[level]({ stack }, `${req.routerPath} error - ${message}`); + res.code(status).send({ message }); + }); + + await server.register(() => auth(services), { prefix: '/auth' }); +}; diff --git a/src/server.js b/src/server.js deleted file mode 100644 index d5fdeec..0000000 --- a/src/server.js +++ /dev/null @@ -1,39 +0,0 @@ -import { fastify } from 'fastify'; -import cors from '@fastify/cors'; -import services from './services/index.js'; -import routes from './routes/index.js'; - -export const options = (extendWith = {}) => ({ - host: '0.0.0.0', - port: '9000', - ...extendWith, -}); - -export const deps = async (replaceWith = {}) => { - const logger = - replaceWith.logger || services.logger.init(services.logger.options()); - const db = - replaceWith.db || - (await services.db.init({ logger }, services.db.options())); - return { logger, db }; -}; - -export const init = async ({ logger, db }, options = {}) => { - const server = fastify({ logger, ...options }); - - await server.register(cors); - await server.register(routes.auth({ db, logger }), { prefix: '/api/auth' }); - - return server; -}; - -export const listen = async ({ logger }, server, options) => { - const { host, port } = options; - await server.listen({ host, port }); - logger.info(`Server listens to ${host}:${port}`); -}; - -export const teardown = async ({ logger }, server) => { - await server.close(); - logger.info('Server closed'); -}; diff --git a/src/services/application/auth.d.ts b/src/services/application/auth.d.ts new file mode 100644 index 0000000..a160db6 --- /dev/null +++ b/src/services/application/auth.d.ts @@ -0,0 +1,17 @@ +export type Auth = { + signUp(params: { + email: string; + password: string; + firstName: string; + lastName: string; + }): Promise<{ userId: string; token: string; }>; + signIn(params: { + email: string; + password: string; + }): Promise<{ userId: string; token: string; }>; + signOut(params: { token: string; }): Promise; + refresh(params: { token: string; }): Promise<{ token: string }>; +} + +export type Deps = { infrastructure: Services['infrastructure'] }; +export function init(deps: Deps): Promise; \ No newline at end of file diff --git a/src/services/application/auth.js b/src/services/application/auth.js new file mode 100644 index 0000000..5021523 --- /dev/null +++ b/src/services/application/auth.js @@ -0,0 +1,55 @@ +import * as crypto from '../../lib/crypto.js'; +import { AppError } from '../../lib/error.js'; + +/** @type function(Pick): Services['application']['auth'] */ +export const init = ({ db }) => ({ + signUp: async ({ email, password, ...rest }) => { + const exists = await db.user.findUnique({ where: { email } }); + if (exists) throw new AppError('Already exists'); + + const passwordHash = await crypto.hash(password); + + const { id: userId } = await db.user.create({ + data: { email, passwordHash, ...rest }, + }); + + const token = crypto.random(); + await db.session.create({ data: { userId, token } }); + + return { userId, token }; + }, + + signIn: async ({ email, password }) => { + const user = await db.user.findUnique({ where: { email } }); + if (!user) throw new AppError('Invalid credentials'); + + const valid = await crypto.compare(password, user.passwordHash); + if (!valid) throw new AppError('Invalid credentials'); + + const { id: userId } = user; + const token = crypto.random(); + await db.session.create({ data: { userId, token } }); + + return { userId, token }; + }, + + signOut: async ({ token }) => { + const exists = await db.session + .delete({ where: { token } }) + .catch(() => false); + if (!exists) throw new AppError('Not found'); + }, + + refresh: async ({ token }) => { + const session = await db.session.findUnique({ where: { token } }); + if (!session) throw new AppError('Not found'); + + const newToken = crypto.random(); + await db.session.update({ + where: { id: session.id }, + data: { token: newToken }, + }); + + return { token: newToken }; + }, +}); diff --git a/src/services/application/index.d.ts b/src/services/application/index.d.ts new file mode 100644 index 0000000..4d6cbc6 --- /dev/null +++ b/src/services/application/index.d.ts @@ -0,0 +1,9 @@ +import type { Server, Options as ServerOptions } from './server'; +import type { Auth } from './auth'; + +export type ApplicationServices = { server: Server, auth: Auth }; +export type ApplicationOptions = { server: ServerOptions }; + +type Deps = { infrastructure: Services['infrastructure'], routes: Router }; +export function init(deps: Deps, options: ApplicationOptions): Promise; +export function teardown(deps: Services): Promise; \ No newline at end of file diff --git a/src/services/application/index.js b/src/services/application/index.js new file mode 100644 index 0000000..16c0f00 --- /dev/null +++ b/src/services/application/index.js @@ -0,0 +1,21 @@ +import { init as initAuth } from './auth.js'; +import { init as initServer, teardown as teardownServer } from './server.js'; + +/** @type function({ infrastructure: Services['infrastructure'], routes: Router }, Config['application']): Promise */ +export const init = async ({ infrastructure, routes }, options) => { + const auth = await initAuth({ infrastructure }); + const server = await initServer( + { infrastructure, application: { auth }, routes }, + options.server, + ); + + return { auth, server }; +}; + +/** @type function(Services): Promise */ +export const teardown = async ({ + infrastructure: { logger }, + application: { server }, +}) => { + await teardownServer({ server, logger }); +}; diff --git a/src/services/application/server.d.ts b/src/services/application/server.d.ts new file mode 100644 index 0000000..740b925 --- /dev/null +++ b/src/services/application/server.d.ts @@ -0,0 +1,33 @@ +import type { + FastifyInstance, + FastifyBaseLogger, + RawReplyDefaultExpression, + RawRequestDefaultExpression, + RawServerDefault, + FastifyServerOptions, +} from "fastify"; +import type { FastifyCorsOptions } from '@fastify/cors' +import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' + +export type Server = FastifyInstance< + RawServerDefault, + RawRequestDefaultExpression, + RawReplyDefaultExpression, + FastifyBaseLogger, + JsonSchemaToTsProvider +>; + +export type InitDeps = { + infrastructure: Services['infrastructure'], + application: Omit, + routes: Router +}; +export type TeardownDeps = { server: Server, logger: Services['infrastructure']['logger'] }; +export type Options = { + host: string; + port: number; + instance?: FastifyServerOptions; + cors?: FastifyCorsOptions; +}; +export function init(deps: InitDeps, options: Options): Promise; +export function teardown(deps: TeardownDeps): Promise; \ No newline at end of file diff --git a/src/services/application/server.js b/src/services/application/server.js new file mode 100644 index 0000000..5e39df6 --- /dev/null +++ b/src/services/application/server.js @@ -0,0 +1,42 @@ +import { fastify } from 'fastify'; +import cors from '@fastify/cors'; + +/** @type function( + * { + * infrastructure: Services['infrastructure'], + * application: Omit, + * routes: Router + * }, + * Config['application']['server'] + * ): Promise */ +export const init = async ( + { infrastructure, application, routes }, + options, +) => { + const { logger } = infrastructure; + /** @type Services['application']['server'] */ + const server = fastify({ logger, ...options.instance }); + + await server.register(cors, options.cors); + + await server.register( + async (/** @type Services['application']['server'] */ server) => { + const services = { + infrastructure, + application: { ...application, server }, + }; + return routes(services); + }, + { prefix: '/api' }, + ); + + const { host, port } = options; + await server.listen({ host, port }); + + return server; +}; + +export const teardown = async ({ logger, server }) => { + await server.close(); + logger.info('Server closed'); +}; diff --git a/src/services/index.js b/src/services/index.js deleted file mode 100644 index 922b079..0000000 --- a/src/services/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import * as db from './db.js'; -import * as logger from './logger.js'; - -export default { db, logger }; diff --git a/src/services/infrastructure/db.d.ts b/src/services/infrastructure/db.d.ts new file mode 100644 index 0000000..8ab2677 --- /dev/null +++ b/src/services/infrastructure/db.d.ts @@ -0,0 +1,7 @@ +import type { PrismaClient, Prisma } from '@prisma/client'; +import type { Logger } from './logger'; + +export type DB = PrismaClient; +export type Options = Prisma.PrismaClientOptions; +export function init(deps: { logger: Logger }, options: Options): Promise; +export function teardown(deps: { db: DB, logger: Logger }): Promise; \ No newline at end of file diff --git a/src/services/db.js b/src/services/infrastructure/db.js similarity index 73% rename from src/services/db.js rename to src/services/infrastructure/db.js index 78baa81..8b2fb97 100644 --- a/src/services/db.js +++ b/src/services/infrastructure/db.js @@ -1,11 +1,5 @@ import { PrismaClient } from '@prisma/client'; -export const options = (extendWith = {}) => ({ - log: ['info', 'query'], - errorFormat: 'minimal', - ...extendWith, -}); - export const init = async ({ logger }, options = {}) => { const client = new PrismaClient(options); await client.$connect(); diff --git a/src/services/infrastructure/index.d.ts b/src/services/infrastructure/index.d.ts new file mode 100644 index 0000000..9a64610 --- /dev/null +++ b/src/services/infrastructure/index.d.ts @@ -0,0 +1,8 @@ +import type { Logger, Options as LoggerOptions } from './logger'; +import type { DB, Options as DBOptions } from './db'; + +export type InfrastructureServices = { logger: Logger, db: DB }; +export type InfrastructureOptions = { db: DBOptions, logger: LoggerOptions } + +export function init(options: InfrastructureOptions): InfrastructureServices; +export function teardown(deps: InfrastructureServices): Promise; \ No newline at end of file diff --git a/src/services/infrastructure/index.js b/src/services/infrastructure/index.js new file mode 100644 index 0000000..194b396 --- /dev/null +++ b/src/services/infrastructure/index.js @@ -0,0 +1,15 @@ +import { init as initLogger } from './logger.js'; +import { init as initDB, teardown as teardownDB } from './db.js'; + +/** @type function(Config['infrastructure']): Promise */ +export const init = async (options) => { + const logger = await initLogger(options.logger); + const db = await initDB({ logger }, options.db); + + return { logger, db }; +}; + +/** @type function(Services['infrastructure']): Promise */ +export const teardown = async ({ db, logger }) => { + await teardownDB({ db, logger }); +}; diff --git a/src/services/infrastructure/logger.d.ts b/src/services/infrastructure/logger.d.ts new file mode 100644 index 0000000..abe26f4 --- /dev/null +++ b/src/services/infrastructure/logger.d.ts @@ -0,0 +1,5 @@ +import { BaseLogger } from 'pino'; + +export type Logger = Pick; +export type Options = { env: string }; +export function init(options: Options): Logger; diff --git a/src/services/infrastructure/logger.js b/src/services/infrastructure/logger.js new file mode 100644 index 0000000..e30a2ec --- /dev/null +++ b/src/services/infrastructure/logger.js @@ -0,0 +1,39 @@ +import { pino } from 'pino'; + +const PinoLevelToSeverityLookup = { + trace: 'DEBUG', + debug: 'DEBUG', + info: 'INFO', + warn: 'WARNING', + error: 'ERROR', + fatal: 'CRITICAL', +}; + +const options = { + development: { + level: 'trace', + transport: { + target: 'pino-pretty', + options: { + ignore: 'pid,hostname', + }, + }, + }, + production: { + messageKey: 'message', + formatters: { + level(label, number) { + return { + severity: + PinoLevelToSeverityLookup[label] || PinoLevelToSeverityLookup.info, + level: number, + }; + }, + }, + }, + test: { + level: 'silent', + }, +}; + +export const init = ({ env }) => pino(options[env]); diff --git a/src/services/logger.js b/src/services/logger.js deleted file mode 100644 index 31ad6d2..0000000 --- a/src/services/logger.js +++ /dev/null @@ -1,14 +0,0 @@ -import { pino } from 'pino'; - -export const options = (extendWith = {}) => ({ - level: 'debug', - transport: { - target: 'pino-pretty', - options: { - ignore: 'pid,hostname', - }, - }, - ...extendWith, -}); - -export const init = (options) => pino(options); From d13a92f196926bffe2a2fbc889e8a86c007747dd Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Thu, 22 Dec 2022 14:57:02 +0200 Subject: [PATCH 09/19] decoupled router from services, restructured app --- global.d.ts | 20 - package-lock.json | 581 +++++++++++++++++- package.json | 4 + src/api/http/auth.js | 85 +++ src/api/http/router.js | 3 + src/api/router.js | 1 + src/api/types.d.ts | 13 + src/app.js | 33 +- src/config.js | 28 +- src/infra/bus.js | 32 + src/{services/infrastructure => infra}/db.js | 0 src/infra/infra.js | 19 + .../infrastructure => infra}/logger.js | 0 src/infra/types.d.ts | 28 + src/routes/auth.js | 90 --- src/routes/index.js | 18 - src/server/plugins/auth.js | 0 src/server/plugins/cors.js | 7 + src/server/plugins/swagger.js | 33 + src/server/server.js | 83 +++ src/server/types.d.ts | 23 + src/services/application/index.d.ts | 9 - src/services/application/index.js | 21 - src/services/application/server.d.ts | 33 - src/services/application/server.js | 42 -- src/services/{application => auth}/auth.d.ts | 7 +- src/services/{application => auth}/auth.js | 7 +- src/services/infrastructure/db.d.ts | 7 - src/services/infrastructure/index.d.ts | 8 - src/services/infrastructure/index.js | 15 - src/services/infrastructure/logger.d.ts | 5 - src/services/services.js | 13 + 32 files changed, 944 insertions(+), 324 deletions(-) delete mode 100644 global.d.ts create mode 100644 src/api/http/auth.js create mode 100644 src/api/http/router.js create mode 100644 src/api/router.js create mode 100644 src/api/types.d.ts create mode 100644 src/infra/bus.js rename src/{services/infrastructure => infra}/db.js (100%) create mode 100644 src/infra/infra.js rename src/{services/infrastructure => infra}/logger.js (100%) create mode 100644 src/infra/types.d.ts delete mode 100644 src/routes/auth.js delete mode 100644 src/routes/index.js create mode 100644 src/server/plugins/auth.js create mode 100644 src/server/plugins/cors.js create mode 100644 src/server/plugins/swagger.js create mode 100644 src/server/server.js create mode 100644 src/server/types.d.ts delete mode 100644 src/services/application/index.d.ts delete mode 100644 src/services/application/index.js delete mode 100644 src/services/application/server.d.ts delete mode 100644 src/services/application/server.js rename src/services/{application => auth}/auth.d.ts (74%) rename src/services/{application => auth}/auth.js (91%) delete mode 100644 src/services/infrastructure/db.d.ts delete mode 100644 src/services/infrastructure/index.d.ts delete mode 100644 src/services/infrastructure/index.js delete mode 100644 src/services/infrastructure/logger.d.ts create mode 100644 src/services/services.js diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index ed380c6..0000000 --- a/global.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { - ApplicationOptions, - ApplicationServices, -} from './src/services/application'; -import type { - InfrastructureOptions, - InfrastructureServices, -} from './src/services/infrastructure' - -declare global { - type Config = { - application: ApplicationOptions; - infrastructure: InfrastructureOptions; - }; - type Services = { - application: ApplicationServices; - infrastructure: InfrastructureServices; - } - type Router = (deps: Services) => Promise; -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 80173f1..7e2a5fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,13 @@ "version": "0.0.0", "license": "MIT", "dependencies": { + "@fastify/auth": "^4.2.0", "@fastify/cors": "^8.2.0", + "@fastify/swagger": "^8.2.1", + "@fastify/swagger-ui": "^1.3.0", "@prisma/client": "^4.7.1", "fastify": "^4.10.2", + "fastify-plugin": "^4.4.0", "pino": "^8.7.0" }, "devDependencies": { @@ -69,6 +73,14 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@fastify/ajv-compiler": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz", @@ -99,6 +111,15 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "node_modules/@fastify/auth": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/auth/-/auth-4.2.0.tgz", + "integrity": "sha512-wcovETbvNHsNQv1fzbjyAeG+ViNYMI0/gTmWUP77hQgbkwPJpYn1iBLAkMmHFKrS6Ap/l9QFZChwV9lJvwRTCA==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "reusify": "^1.0.4" + } + }, "node_modules/@fastify/cors": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.2.0.tgz", @@ -126,6 +147,97 @@ "fast-json-stringify": "^5.0.0" } }, + "node_modules/@fastify/static": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.6.0.tgz", + "integrity": "sha512-UiYSN2dUmDZ48M40xdIwY1dPwSSYD7c+wtoIQP8y7wyxCwcUtf1YT5/Q4n1uJsBF1fySvuo9njQZKlHeiKy4HQ==", + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "glob": "^8.0.1", + "p-limit": "^3.1.0", + "readable-stream": "^4.0.0", + "send": "^0.18.0" + } + }, + "node_modules/@fastify/static/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@fastify/static/node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@fastify/static/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@fastify/swagger": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.2.1.tgz", + "integrity": "sha512-nYP/3ncrI5YmaGiJf6m+CLdFrdlWSsASHBPqP9uN9/oFFwDJwdUtq0ylmvObxzqWNVt9zT50iT/uvIndVEsvbg==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "json-schema-resolver": "^2.0.0", + "openapi-types": "^12.0.0", + "rfdc": "^1.3.0", + "yaml": "^2.1.1" + } + }, + "node_modules/@fastify/swagger-ui": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-1.3.0.tgz", + "integrity": "sha512-Q6vvIyTd1gj0h0IoDAAUX3SBBiL1pybXP0FmFbD4yLMcACIZ7xm8oHCf5lMc3rNC69KhbywuSst3iHgp23x8SA==", + "dependencies": { + "@fastify/static": "^6.0.0", + "fastify-plugin": "^4.0.0", + "openapi-types": "^12.0.2", + "rfdc": "^1.3.0", + "yaml": "^2.1.3" + } + }, + "node_modules/@fastify/swagger-ui/node_modules/yaml": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", + "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@fastify/swagger/node_modules/yaml": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", + "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==", + "engines": { + "node": ">= 14" + } + }, "node_modules/@fastify/type-provider-json-schema-to-ts": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-2.2.1.tgz", @@ -455,8 +567,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -607,6 +718,17 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -693,6 +815,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -714,12 +853,25 @@ "node": ">=6.0.0" } }, + "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==" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -793,6 +945,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1111,6 +1268,14 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -1330,11 +1495,18 @@ "node": ">= 0.6" } }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/function-bind": { "version": "1.1.1", @@ -1594,6 +1766,21 @@ "node": ">= 6" } }, + "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==", + "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/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1651,7 +1838,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1660,8 +1846,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.3", @@ -1925,6 +2110,22 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-schema-resolver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz", + "integrity": "sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==", + "dependencies": { + "debug": "^4.1.1", + "rfdc": "^1.1.4", + "uri-js": "^4.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/Eomm/json-schema-resolver?sponsor=1" + } + }, "node_modules/json-schema-to-ts": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.6.2.tgz", @@ -2039,6 +2240,17 @@ "node": ">=12.0.0" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2154,15 +2366,30 @@ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" }, + "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==", + "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==", - "dev": true, "dependencies": { "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", + "integrity": "sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==" + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -2184,7 +2411,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -2445,6 +2671,14 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/readable-stream": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", @@ -2616,7 +2850,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -2681,6 +2914,47 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/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==" + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -2692,6 +2966,11 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz", "integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==" }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2743,6 +3022,14 @@ "node": ">= 10.x" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2930,6 +3217,14 @@ "node": ">=6" } }, + "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==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-algebra": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.1.1.tgz", @@ -3097,8 +3392,7 @@ "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==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/y18n": { "version": "4.0.3", @@ -3211,7 +3505,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "engines": { "node": ">=10" }, @@ -3248,6 +3541,11 @@ "strip-json-comments": "^3.1.1" } }, + "@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==" + }, "@fastify/ajv-compiler": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz", @@ -3276,6 +3574,15 @@ } } }, + "@fastify/auth": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/auth/-/auth-4.2.0.tgz", + "integrity": "sha512-wcovETbvNHsNQv1fzbjyAeG+ViNYMI0/gTmWUP77hQgbkwPJpYn1iBLAkMmHFKrS6Ap/l9QFZChwV9lJvwRTCA==", + "requires": { + "fastify-plugin": "^4.0.0", + "reusify": "^1.0.4" + } + }, "@fastify/cors": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.2.0.tgz", @@ -3303,6 +3610,88 @@ "fast-json-stringify": "^5.0.0" } }, + "@fastify/static": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.6.0.tgz", + "integrity": "sha512-UiYSN2dUmDZ48M40xdIwY1dPwSSYD7c+wtoIQP8y7wyxCwcUtf1YT5/Q4n1uJsBF1fySvuo9njQZKlHeiKy4HQ==", + "requires": { + "@fastify/accept-negotiator": "^1.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "glob": "^8.0.1", + "p-limit": "^3.1.0", + "readable-stream": "^4.0.0", + "send": "^0.18.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@fastify/swagger": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.2.1.tgz", + "integrity": "sha512-nYP/3ncrI5YmaGiJf6m+CLdFrdlWSsASHBPqP9uN9/oFFwDJwdUtq0ylmvObxzqWNVt9zT50iT/uvIndVEsvbg==", + "requires": { + "fastify-plugin": "^4.0.0", + "json-schema-resolver": "^2.0.0", + "openapi-types": "^12.0.0", + "rfdc": "^1.3.0", + "yaml": "^2.1.1" + }, + "dependencies": { + "yaml": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", + "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==" + } + } + }, + "@fastify/swagger-ui": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/swagger-ui/-/swagger-ui-1.3.0.tgz", + "integrity": "sha512-Q6vvIyTd1gj0h0IoDAAUX3SBBiL1pybXP0FmFbD4yLMcACIZ7xm8oHCf5lMc3rNC69KhbywuSst3iHgp23x8SA==", + "requires": { + "@fastify/static": "^6.0.0", + "fastify-plugin": "^4.0.0", + "openapi-types": "^12.0.2", + "rfdc": "^1.3.0", + "yaml": "^2.1.3" + }, + "dependencies": { + "yaml": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", + "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==" + } + } + }, "@fastify/type-provider-json-schema-to-ts": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-2.2.1.tgz", @@ -3545,8 +3934,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -3648,6 +4036,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -3705,6 +4101,16 @@ "object-keys": "^1.1.1" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3720,12 +4126,22 @@ "esutils": "^2.0.2" } }, + "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==" + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -3787,6 +4203,11 @@ "is-symbol": "^1.0.2" } }, + "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==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4025,6 +4446,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -4215,11 +4641,15 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "function-bind": { "version": "1.1.1", @@ -4409,6 +4839,18 @@ } } }, + "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==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4440,7 +4882,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -4449,8 +4890,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { "version": "1.0.3", @@ -4626,6 +5066,16 @@ "argparse": "^2.0.1" } }, + "json-schema-resolver": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-resolver/-/json-schema-resolver-2.0.0.tgz", + "integrity": "sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==", + "requires": { + "debug": "^4.1.1", + "rfdc": "^1.1.4", + "uri-js": "^4.2.2" + } + }, "json-schema-to-ts": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.6.2.tgz", @@ -4716,6 +5166,11 @@ "yargs": "^15.4.1" } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4804,15 +5259,27 @@ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } }, + "openapi-types": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", + "integrity": "sha512-XpeCy01X6L5EpP+6Hc3jWN7rMZJ+/k1lwki/kTmWzbVhdPie3jd5O2ZtedEx8Yp58icJ0osVldLMrTB/zslQXA==" + }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -4831,7 +5298,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "requires": { "yocto-queue": "^0.1.0" } @@ -5013,6 +5479,11 @@ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, + "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==" + }, "readable-stream": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", @@ -5123,8 +5594,7 @@ "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==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-regex-test": { "version": "1.0.0", @@ -5163,6 +5633,48 @@ "lru-cache": "^6.0.0" } }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "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" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -5174,6 +5686,11 @@ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz", "integrity": "sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==" }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5213,6 +5730,11 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5354,6 +5876,11 @@ "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-10.0.1.tgz", "integrity": "sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==" }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, "ts-algebra": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.1.1.tgz", @@ -5487,8 +6014,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "y18n": { "version": "4.0.3", @@ -5578,8 +6104,7 @@ "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } } diff --git a/package.json b/package.json index 7e642d5..3ec5d34 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,13 @@ "node": "18" }, "dependencies": { + "@fastify/auth": "^4.2.0", "@fastify/cors": "^8.2.0", + "@fastify/swagger": "^8.2.1", + "@fastify/swagger-ui": "^1.3.0", "@prisma/client": "^4.7.1", "fastify": "^4.10.2", + "fastify-plugin": "^4.4.0", "pino": "^8.7.0" }, "devDependencies": { diff --git a/src/api/http/auth.js b/src/api/http/auth.js new file mode 100644 index 0000000..6f1d3ad --- /dev/null +++ b/src/api/http/auth.js @@ -0,0 +1,85 @@ +/** @typedef {import('../types').HttpRoute} HttpRoute */ + +/** @type HttpRoute */ +const signUp = { + method: 'POST', + url: '/sign-up', + schema: { + source: 'body', + input: { + required: ['email', 'password', 'firstName', 'lastName'], + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + firstName: { type: 'string' }, + lastName: { type: 'string' }, + }, + }, + output: { + required: ['userId', 'token'], + properties: { + userId: { type: 'string' }, + token: { type: 'string' }, + }, + }, + }, + command: 'auth.signUp', +}; + +/** @type HttpRoute */ +const signIn = { + method: 'POST', + url: '/sign-in', + schema: { + source: 'body', + input: { + required: ['email', 'password'], + properties: { + email: { type: 'string' }, + password: { type: 'string' }, + }, + }, + output: { + required: ['userId', 'token'], + properties: { + userId: { type: 'string' }, + token: { type: 'string' }, + }, + }, + }, + command: 'auth.signIn', +}; + +/** @type HttpRoute */ +const signOut = { + method: 'POST', + url: '/sign-out', + schema: { + source: 'body', + input: { + required: ['token'], + properties: { token: { type: 'string' } }, + }, + }, + command: 'auth.signOut', +}; + +/** @type HttpRoute */ +const refresh = { + method: 'POST', + url: '/refresh', + schema: { + source: 'body', + input: { + required: ['token'], + properties: { token: { type: 'string' } }, + }, + output: { + required: ['token'], + properties: { token: { type: 'string' } }, + }, + }, + command: 'auth.refresh', +}; + +export const auth = [signUp, signIn, signOut, refresh]; diff --git a/src/api/http/router.js b/src/api/http/router.js new file mode 100644 index 0000000..e288919 --- /dev/null +++ b/src/api/http/router.js @@ -0,0 +1,3 @@ +import { auth } from './auth.js'; + +export const http = { auth }; diff --git a/src/api/router.js b/src/api/router.js new file mode 100644 index 0000000..7bc1cb6 --- /dev/null +++ b/src/api/router.js @@ -0,0 +1 @@ +export { http } from './http/router.js'; diff --git a/src/api/types.d.ts b/src/api/types.d.ts new file mode 100644 index 0000000..3ac89fe --- /dev/null +++ b/src/api/types.d.ts @@ -0,0 +1,13 @@ +type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; +type HttpDataSource = 'query' | 'body'; +type ValidationSchema = { required?: string[]; properties: object; } +type HttpSchema = { source?: HttpDataSource, input?: ValidationSchema, output?: ValidationSchema }; + +export type HttpRoute = { + method: HttpMethod; + url: string; + schema: HttpSchema; + command: string; +} + +export type HttpRouter = Record; \ No newline at end of file diff --git a/src/app.js b/src/app.js index 6bb7167..f7c1a84 100644 --- a/src/app.js +++ b/src/app.js @@ -1,26 +1,23 @@ import { config } from './config.js'; -import { - init as initAppServices, - teardown as teardownAppServices, -} from './services/application/index.js'; -import { - init as initInfraServices, - teardown as teardownInfraServices, -} from './services/infrastructure/index.js'; -import { routes } from './routes/index.js'; +import * as server from './server/server.js'; +import * as infra from './infra/infra.js'; +import * as services from './services/services.js'; +import * as router from './api/router.js'; export const init = async () => { - const infrastructure = await initInfraServices(config.infrastructure); - const application = await initAppServices( - { infrastructure, routes }, - config.application, - ); + const infrastructure = await infra.init(config.infra); + + await services.init(infrastructure); - const services = { application, infrastructure }; + const serviceInstance = await server.init( + router.http, + infrastructure, + config.server, + ); return async () => { - services.infrastructure.logger.info('Stopping application'); - await teardownAppServices(services); - await teardownInfraServices(infrastructure); + infrastructure.logger.info('Stopping application'); + await serviceInstance.close(); + await infra.teardown(infrastructure); }; }; diff --git a/src/config.js b/src/config.js index b073572..00b7d02 100644 --- a/src/config.js +++ b/src/config.js @@ -1,14 +1,32 @@ +/** @typedef {import('./server/types').ServerConfig} Server */ +/** @typedef {import('./infra/types').InfraConfig} Infra */ +/** @typedef Config + * @property server {Server} + * @property infra {Infra} + */ + const env = process.env.NODE_ENV || 'development'; /** @type Config */ export const config = { - application: { - server: { - host: process.env.HOST || '0.0.0.0', - port: Number(process.env.PORT) || 9000, + server: { + host: process.env.HOST || '0.0.0.0', + port: Number(process.env.PORT) || 9000, + instance: {}, + cors: { + origin: '*', + methods: '*', + allowedHeaders: '*', + credentials: true, + }, + swagger: { + title: 'Test', + version: '1.0.0', + routePrefix: '/docs', + serverUrl: process.env.SERVER_URL || 'http://localhost:9000', }, }, - infrastructure: { + infra: { db: { errorFormat: 'minimal' }, logger: { env }, }, diff --git a/src/infra/bus.js b/src/infra/bus.js new file mode 100644 index 0000000..d10d461 --- /dev/null +++ b/src/infra/bus.js @@ -0,0 +1,32 @@ +/** @typedef {import('./types').Bus} IBus */ +import { EventEmitter } from 'node:events'; + +/** @implements IBus */ +export class Bus { + #ee; + #services; + + constructor() { + this.#ee = new EventEmitter(); + this.#services = new Map(); + } + + command(name, payload) { + const [serviceName, methodName] = name.split('.'); + const service = this.#services.get(serviceName); + return service[methodName](payload); + } + + registerService(name, service) { + this.#services.set(name, service); + } + + subscribe(event, handler) { + this.#ee.on(event, handler); + return true; + } + + publish(event, data) { + return this.#ee.emit(event, data); + } +} diff --git a/src/services/infrastructure/db.js b/src/infra/db.js similarity index 100% rename from src/services/infrastructure/db.js rename to src/infra/db.js diff --git a/src/infra/infra.js b/src/infra/infra.js new file mode 100644 index 0000000..fd15066 --- /dev/null +++ b/src/infra/infra.js @@ -0,0 +1,19 @@ +/** @typedef {import('./types').Infra} Infra */ +/** @typedef {import('./types').InfraConfig} Config */ +import { init as initLogger } from './logger.js'; +import { init as initDB, teardown as teardownDB } from './db.js'; +import { Bus } from './bus.js'; + +/** @type function(Config): Promise */ +export const init = async (config) => { + const bus = new Bus(); + const logger = await initLogger(config.logger); + const db = await initDB({ logger }, config.db); + + return { bus, logger, db }; +}; + +/** @type function(Infra): Promise */ +export const teardown = async ({ db, logger }) => { + await teardownDB({ db, logger }); +}; diff --git a/src/services/infrastructure/logger.js b/src/infra/logger.js similarity index 100% rename from src/services/infrastructure/logger.js rename to src/infra/logger.js diff --git a/src/infra/types.d.ts b/src/infra/types.d.ts new file mode 100644 index 0000000..edd785f --- /dev/null +++ b/src/infra/types.d.ts @@ -0,0 +1,28 @@ +import type { PrismaClient, Prisma } from '@prisma/client'; +import { BaseLogger } from 'pino'; + +interface EventHandler { + (event: object): any; +} + +export type Logger = Pick; +export type DB = PrismaClient; +export interface Bus { + command(commandName: string, payload: object): Promise; + registerService(name: string, service: object): void; + + subscribe(eventName: string, handler: EventHandler): boolean; + publish(eventName: string, event: object): boolean; +} +export type Infra = { + bus: Bus; + logger: Logger; + db: DB; +}; + +export type LoggerConfig = { env: string }; +export type DBConfig = Prisma.PrismaClientOptions; +export type InfraConfig = { + logger: LoggerConfig; + db: DBConfig; +} diff --git a/src/routes/auth.js b/src/routes/auth.js deleted file mode 100644 index 972f9f3..0000000 --- a/src/routes/auth.js +++ /dev/null @@ -1,90 +0,0 @@ -/** @type function(Services): Promise */ -export const auth = async ({ application: { server, auth } }) => { - server.route({ - method: 'POST', - url: '/sign-up', - schema: /** @type {const} */ ({ - body: { - type: 'object', - additionalProperties: false, - required: ['email', 'password', 'firstName', 'lastName'], - properties: { - email: { type: 'string' }, - password: { type: 'string' }, - firstName: { type: 'string' }, - lastName: { type: 'string' }, - }, - }, - }), - handler: async (req, res) => { - const data = req.body; - - const { userId, token } = await auth.signUp(data); - - res.code(200).send({ userId, token }); - }, - }); - - server.route({ - method: 'POST', - url: '/sign-in', - schema: /** @type {const} */ ({ - body: { - type: 'object', - additionalProperties: false, - required: ['email', 'password'], - properties: { - email: { type: 'string' }, - password: { type: 'string' }, - }, - }, - }), - handler: async (req, res) => { - const { email, password } = req.body; - - const { userId, token } = await auth.signIn({ email, password }); - - res.code(200).send({ userId, token }); - }, - }); - - server.route({ - method: 'POST', - url: '/refresh', - schema: /** @type {const} */ ({ - body: { - type: 'object', - additionalProperties: false, - required: ['token'], - properties: { token: { type: 'string' } }, - }, - }), - handler: async (req, res) => { - const { token } = req.body; - - const { token: newToken } = await auth.refresh({ token }); - - res.code(200).send({ token: newToken }); - }, - }); - - server.route({ - method: 'POST', - url: '/sign-out', - schema: /** @type {const} */ ({ - body: { - type: 'object', - additionalProperties: false, - required: ['token'], - properties: { token: { type: 'string' } }, - }, - }), - handler: async (req, res) => { - const { token } = req.body; - - await auth.signOut({ token }); - - res.code(204).send(); - }, - }); -}; diff --git a/src/routes/index.js b/src/routes/index.js deleted file mode 100644 index 4ce05ac..0000000 --- a/src/routes/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import { handleError } from '../lib/error.js'; -import { auth } from './auth.js'; - -/** @type Router */ -export const routes = async (services) => { - const { server } = services.application; - const { logger } = services.infrastructure; - - server.setErrorHandler(server.errorHandler); - server.setErrorHandler((error, req, res) => { - const [status, message, level, stack] = handleError(error); - - logger[level]({ stack }, `${req.routerPath} error - ${message}`); - res.code(status).send({ message }); - }); - - await server.register(() => auth(services), { prefix: '/auth' }); -}; diff --git a/src/server/plugins/auth.js b/src/server/plugins/auth.js new file mode 100644 index 0000000..e69de29 diff --git a/src/server/plugins/cors.js b/src/server/plugins/cors.js new file mode 100644 index 0000000..defa596 --- /dev/null +++ b/src/server/plugins/cors.js @@ -0,0 +1,7 @@ +import fp from 'fastify-plugin'; +import plugin from '@fastify/cors'; + +export const cors = (options) => + fp(async (fastify) => { + await fastify.register(plugin, options); + }); diff --git a/src/server/plugins/swagger.js b/src/server/plugins/swagger.js new file mode 100644 index 0000000..fcfded3 --- /dev/null +++ b/src/server/plugins/swagger.js @@ -0,0 +1,33 @@ +import fp from 'fastify-plugin'; +import plugin from '@fastify/swagger'; +import pluginUi from '@fastify/swagger-ui'; + +export const swagger = (options) => + fp(async (fastify) => { + const { title, routePrefix, serverUrl, version } = options; + await fastify.register(plugin, { + openapi: { + info: { + title, + description: `API docs for ${title} project`, + version, + }, + externalDocs: { + url: 'https://swagger.io', + description: 'Find more info here', + }, + servers: [{ url: serverUrl }], + components: { + securitySchemes: { + token: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + }, + }, + }); + + await fastify.register(pluginUi, { routePrefix }); + }); diff --git a/src/server/server.js b/src/server/server.js new file mode 100644 index 0000000..68e9033 --- /dev/null +++ b/src/server/server.js @@ -0,0 +1,83 @@ +/** @typedef {import('./types').init} init */ +import { fastify } from 'fastify'; +import { cors } from './plugins/cors.js'; +import { swagger } from './plugins/swagger.js'; +import { handleError } from '../lib/error.js'; + +const objectProps = { type: 'object', additionalProperties: false }; +const errorResponse = { + ...objectProps, + required: ['message'], + properties: { message: { type: 'string' } }, +}; + +const generateSchema = (prefix, definition) => { + const schema = { tags: [prefix.toUpperCase()] }; + if (definition.input) { + schema[definition.source] = { ...objectProps, ...definition.input }; + } + + if (definition.output) { + schema.response = { + 200: { ...objectProps, ...definition.output }, + 400: { description: 'Client error', ...errorResponse }, + //401: { description: 'Auth error', errorResponse }, + }; + } + + return schema; +}; + +/** @type init */ +export const init = async (router, infra, config) => { + const { db, logger, bus } = infra; + const { host, port, instance } = config; + + const server = fastify({ logger, ...instance }); + + await server.register(cors(config.cors)); + await server.register(swagger(config.swagger)); + + server.setErrorHandler(server.errorHandler); + server.setErrorHandler((error, req, res) => { + const [status, message, level, stack] = handleError(error); + + logger[level]({ stack }, `${req.routerPath} error - ${message}`); + res.code(status).send({ message }); + }); + + server.route({ + method: 'GET', + url: '/healthcheck', + handler: async (_req, res) => { + const dbHealthy = await db.$queryRaw`SELECT 1`.catch(() => false); + + const allGood = dbHealthy; + + return res.code(allGood ? 200 : 503).send(); + }, + }); + + for (const [prefix, routes] of Object.entries(router)) { + for (const route of routes) { + const { method, url, schema: schemaDefinition, command } = route; + const schema = generateSchema(prefix, schemaDefinition); + server.route({ + method, + url: `/${prefix}${url}`, + schema, + handler: async (req, res) => { + /** @type object */ + const payload = schema.source ? req[schema.source] : {}; + const result = await bus.command(command, payload); + const [code, data] = schema.output ? [200, result] : [204, null]; + res.code(code).send(data); + }, + }); + } + } + + await server.listen({ host, port }); + + return server; +}; diff --git a/src/server/types.d.ts b/src/server/types.d.ts new file mode 100644 index 0000000..3792426 --- /dev/null +++ b/src/server/types.d.ts @@ -0,0 +1,23 @@ +import type { FastifyInstance, FastifyServerOptions } from 'fastify'; +import type { FastifyCorsOptions } from '@fastify/cors' +import type { HttpRouter } from '../api/types'; +import type { Infra } from '../infra/types'; + +export type ServerConfig = { + host: string; + port: number; + instance: FastifyServerOptions; + cors: FastifyCorsOptions; + swagger: { + title: string; + version: string; + routePrefix: string; + serverUrl: string; + } +}; + +export function init( + router: HttpRouter, + infra: Infra, + config: ServerConfig +): Promise; \ No newline at end of file diff --git a/src/services/application/index.d.ts b/src/services/application/index.d.ts deleted file mode 100644 index 4d6cbc6..0000000 --- a/src/services/application/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Server, Options as ServerOptions } from './server'; -import type { Auth } from './auth'; - -export type ApplicationServices = { server: Server, auth: Auth }; -export type ApplicationOptions = { server: ServerOptions }; - -type Deps = { infrastructure: Services['infrastructure'], routes: Router }; -export function init(deps: Deps, options: ApplicationOptions): Promise; -export function teardown(deps: Services): Promise; \ No newline at end of file diff --git a/src/services/application/index.js b/src/services/application/index.js deleted file mode 100644 index 16c0f00..0000000 --- a/src/services/application/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import { init as initAuth } from './auth.js'; -import { init as initServer, teardown as teardownServer } from './server.js'; - -/** @type function({ infrastructure: Services['infrastructure'], routes: Router }, Config['application']): Promise */ -export const init = async ({ infrastructure, routes }, options) => { - const auth = await initAuth({ infrastructure }); - const server = await initServer( - { infrastructure, application: { auth }, routes }, - options.server, - ); - - return { auth, server }; -}; - -/** @type function(Services): Promise */ -export const teardown = async ({ - infrastructure: { logger }, - application: { server }, -}) => { - await teardownServer({ server, logger }); -}; diff --git a/src/services/application/server.d.ts b/src/services/application/server.d.ts deleted file mode 100644 index 740b925..0000000 --- a/src/services/application/server.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { - FastifyInstance, - FastifyBaseLogger, - RawReplyDefaultExpression, - RawRequestDefaultExpression, - RawServerDefault, - FastifyServerOptions, -} from "fastify"; -import type { FastifyCorsOptions } from '@fastify/cors' -import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts' - -export type Server = FastifyInstance< - RawServerDefault, - RawRequestDefaultExpression, - RawReplyDefaultExpression, - FastifyBaseLogger, - JsonSchemaToTsProvider ->; - -export type InitDeps = { - infrastructure: Services['infrastructure'], - application: Omit, - routes: Router -}; -export type TeardownDeps = { server: Server, logger: Services['infrastructure']['logger'] }; -export type Options = { - host: string; - port: number; - instance?: FastifyServerOptions; - cors?: FastifyCorsOptions; -}; -export function init(deps: InitDeps, options: Options): Promise; -export function teardown(deps: TeardownDeps): Promise; \ No newline at end of file diff --git a/src/services/application/server.js b/src/services/application/server.js deleted file mode 100644 index 5e39df6..0000000 --- a/src/services/application/server.js +++ /dev/null @@ -1,42 +0,0 @@ -import { fastify } from 'fastify'; -import cors from '@fastify/cors'; - -/** @type function( - * { - * infrastructure: Services['infrastructure'], - * application: Omit, - * routes: Router - * }, - * Config['application']['server'] - * ): Promise */ -export const init = async ( - { infrastructure, application, routes }, - options, -) => { - const { logger } = infrastructure; - /** @type Services['application']['server'] */ - const server = fastify({ logger, ...options.instance }); - - await server.register(cors, options.cors); - - await server.register( - async (/** @type Services['application']['server'] */ server) => { - const services = { - infrastructure, - application: { ...application, server }, - }; - return routes(services); - }, - { prefix: '/api' }, - ); - - const { host, port } = options; - await server.listen({ host, port }); - - return server; -}; - -export const teardown = async ({ logger, server }) => { - await server.close(); - logger.info('Server closed'); -}; diff --git a/src/services/application/auth.d.ts b/src/services/auth/auth.d.ts similarity index 74% rename from src/services/application/auth.d.ts rename to src/services/auth/auth.d.ts index a160db6..a0d40cb 100644 --- a/src/services/application/auth.d.ts +++ b/src/services/auth/auth.d.ts @@ -1,4 +1,6 @@ -export type Auth = { +import type { Infra } from '../../infra/types'; + +interface Auth { signUp(params: { email: string; password: string; @@ -13,5 +15,4 @@ export type Auth = { refresh(params: { token: string; }): Promise<{ token: string }>; } -export type Deps = { infrastructure: Services['infrastructure'] }; -export function init(deps: Deps): Promise; \ No newline at end of file +export function init(infra: Infra): Promise; diff --git a/src/services/application/auth.js b/src/services/auth/auth.js similarity index 91% rename from src/services/application/auth.js rename to src/services/auth/auth.js index 5021523..1edcb97 100644 --- a/src/services/application/auth.js +++ b/src/services/auth/auth.js @@ -1,8 +1,9 @@ +/** @typedef {import('./auth').init} init */ import * as crypto from '../../lib/crypto.js'; import { AppError } from '../../lib/error.js'; -/** @type function(Pick): Services['application']['auth'] */ -export const init = ({ db }) => ({ +/** @type init */ +export const init = async ({ db, bus }) => ({ signUp: async ({ email, password, ...rest }) => { const exists = await db.user.findUnique({ where: { email } }); if (exists) throw new AppError('Already exists'); @@ -16,6 +17,8 @@ export const init = ({ db }) => ({ const token = crypto.random(); await db.session.create({ data: { userId, token } }); + bus.publish('signUp', { email }); + return { userId, token }; }, diff --git a/src/services/infrastructure/db.d.ts b/src/services/infrastructure/db.d.ts deleted file mode 100644 index 8ab2677..0000000 --- a/src/services/infrastructure/db.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { PrismaClient, Prisma } from '@prisma/client'; -import type { Logger } from './logger'; - -export type DB = PrismaClient; -export type Options = Prisma.PrismaClientOptions; -export function init(deps: { logger: Logger }, options: Options): Promise; -export function teardown(deps: { db: DB, logger: Logger }): Promise; \ No newline at end of file diff --git a/src/services/infrastructure/index.d.ts b/src/services/infrastructure/index.d.ts deleted file mode 100644 index 9a64610..0000000 --- a/src/services/infrastructure/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Logger, Options as LoggerOptions } from './logger'; -import type { DB, Options as DBOptions } from './db'; - -export type InfrastructureServices = { logger: Logger, db: DB }; -export type InfrastructureOptions = { db: DBOptions, logger: LoggerOptions } - -export function init(options: InfrastructureOptions): InfrastructureServices; -export function teardown(deps: InfrastructureServices): Promise; \ No newline at end of file diff --git a/src/services/infrastructure/index.js b/src/services/infrastructure/index.js deleted file mode 100644 index 194b396..0000000 --- a/src/services/infrastructure/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import { init as initLogger } from './logger.js'; -import { init as initDB, teardown as teardownDB } from './db.js'; - -/** @type function(Config['infrastructure']): Promise */ -export const init = async (options) => { - const logger = await initLogger(options.logger); - const db = await initDB({ logger }, options.db); - - return { logger, db }; -}; - -/** @type function(Services['infrastructure']): Promise */ -export const teardown = async ({ db, logger }) => { - await teardownDB({ db, logger }); -}; diff --git a/src/services/infrastructure/logger.d.ts b/src/services/infrastructure/logger.d.ts deleted file mode 100644 index abe26f4..0000000 --- a/src/services/infrastructure/logger.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { BaseLogger } from 'pino'; - -export type Logger = Pick; -export type Options = { env: string }; -export function init(options: Options): Logger; diff --git a/src/services/services.js b/src/services/services.js new file mode 100644 index 0000000..211c725 --- /dev/null +++ b/src/services/services.js @@ -0,0 +1,13 @@ +/** @typedef {import('../infra/types').Infra} Infra */ +import * as auth from './auth/auth.js'; + +const services = { auth }; + +/** @type function(Infra): Promise */ +export const init = async (infra) => { + const { bus } = infra; + for (const [serviceName, service] of Object.entries(services)) { + const instance = await service.init(infra); + bus.registerService(serviceName, instance); + } +}; From cf5ba11812fe4b574eb6eb7ea360e260b266ce20 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Mon, 26 Dec 2022 17:41:38 +0200 Subject: [PATCH 10/19] added basic money transfer api and logic --- prisma/schema.prisma | 15 +++++++-------- src/api/http/router.js | 3 ++- src/api/http/wallet.js | 25 +++++++++++++++++++++++++ src/services/auth/auth.d.ts | 2 +- src/services/auth/auth.js | 2 +- src/services/wallet/wallet.d.ts | 11 +++++++++++ src/services/wallet/wallet.js | 19 +++++++++++++++++++ 7 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 src/api/http/wallet.js create mode 100644 src/services/wallet/wallet.d.ts create mode 100644 src/services/wallet/wallet.js diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 185c025..d7c9b30 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -36,7 +36,6 @@ model Session { model Account { id String @id @default(uuid()) balance Float - currency Currency createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -48,8 +47,8 @@ model Account { model Transaction { id String @id @default(uuid()) + type TransactionType amount Float - currency Currency state TransactionState @default(INITIAL) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -60,15 +59,15 @@ model Transaction { toId String } +enum TransactionType { + TRANSFER + WITHDRAW + DEPOSIT +} + enum TransactionState { INITIAL DEBITED COMPLETED FAILED } - -enum Currency { - USD - EUR - UAH -} diff --git a/src/api/http/router.js b/src/api/http/router.js index e288919..bdfef99 100644 --- a/src/api/http/router.js +++ b/src/api/http/router.js @@ -1,3 +1,4 @@ import { auth } from './auth.js'; +import { wallet } from './wallet'; -export const http = { auth }; +export const http = { auth, wallet }; diff --git a/src/api/http/wallet.js b/src/api/http/wallet.js new file mode 100644 index 0000000..2bc9138 --- /dev/null +++ b/src/api/http/wallet.js @@ -0,0 +1,25 @@ +/** @typedef {import('../types').HttpRoute} HttpRoute */ + +/** @type HttpRoute */ +const transfer = { + method: 'POST', + url: '/transfer', + schema: { + source: 'body', + input: { + required: ['from', 'to', 'amount'], + properties: { + from: { type: 'string' }, + to: { type: 'string' }, + amount: { type: 'number' }, + }, + }, + output: { + required: ['transactionId'], + properties: { transactionId: { type: 'string' } }, + }, + }, + command: 'wallet.transfert', +}; + +export const wallet = [transfer]; diff --git a/src/services/auth/auth.d.ts b/src/services/auth/auth.d.ts index a0d40cb..54a5339 100644 --- a/src/services/auth/auth.d.ts +++ b/src/services/auth/auth.d.ts @@ -15,4 +15,4 @@ interface Auth { refresh(params: { token: string; }): Promise<{ token: string }>; } -export function init(infra: Infra): Promise; +export function init(infra: Infra): Auth; diff --git a/src/services/auth/auth.js b/src/services/auth/auth.js index 1edcb97..57a885e 100644 --- a/src/services/auth/auth.js +++ b/src/services/auth/auth.js @@ -3,7 +3,7 @@ import * as crypto from '../../lib/crypto.js'; import { AppError } from '../../lib/error.js'; /** @type init */ -export const init = async ({ db, bus }) => ({ +export const init = ({ db, bus }) => ({ signUp: async ({ email, password, ...rest }) => { const exists = await db.user.findUnique({ where: { email } }); if (exists) throw new AppError('Already exists'); diff --git a/src/services/wallet/wallet.d.ts b/src/services/wallet/wallet.d.ts new file mode 100644 index 0000000..74a1625 --- /dev/null +++ b/src/services/wallet/wallet.d.ts @@ -0,0 +1,11 @@ +import type { Infra } from '../../infra/types'; + +interface Wallet { + transfer(params: { + fromAccount: string; + toAccount: string; + amount: number; + }): Promise<{ transactionId: string; }>; +} + +export function init(infra: Infra): Wallet; diff --git a/src/services/wallet/wallet.js b/src/services/wallet/wallet.js new file mode 100644 index 0000000..1e42ab2 --- /dev/null +++ b/src/services/wallet/wallet.js @@ -0,0 +1,19 @@ +/** @typedef {import('./wallet').init} init */ + +/** @type init */ +export const init = ({ db, bus }) => ({ + transfer: async ({ fromAccount, toAccount, amount }) => { + const transaction = await db.transaction.create({ + data: { + fromId: fromAccount, + toId: toAccount, + amount, + type: 'TRANSFER', + }, + }); + + bus.publish('wallet.transfer', transaction); + + return { transactionId: transaction.id }; + }, +}); From 1c679a65339407671f8db77b6d33da779da19db9 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Thu, 29 Dec 2022 13:20:23 +0200 Subject: [PATCH 11/19] updated domain model and added account tests --- .eslintrc.json | 3 +- package.json | 2 +- prisma/schema.prisma | 70 +++++++++++++++----------- src/api/http/{wallet.js => account.js} | 4 +- src/api/http/router.js | 4 +- src/services/account/account.d.ts | 8 +++ src/services/account/account.js | 22 ++++++++ src/services/account/account.test.js | 50 ++++++++++++++++++ src/services/wallet/wallet.d.ts | 11 ---- src/services/wallet/wallet.js | 19 ------- 10 files changed, 128 insertions(+), 65 deletions(-) rename src/api/http/{wallet.js => account.js} (87%) create mode 100644 src/services/account/account.d.ts create mode 100644 src/services/account/account.js create mode 100644 src/services/account/account.test.js delete mode 100644 src/services/wallet/wallet.d.ts delete mode 100644 src/services/wallet/wallet.js diff --git a/.eslintrc.json b/.eslintrc.json index fb1cad0..ff12ca2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,8 @@ "node": true }, "rules": { - "strict": "off" + "strict": "off", + "no-use-before-define": "off" }, "parserOptions": { "sourceType": "module", diff --git a/package.json b/package.json index 3ec5d34..b1313ec 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "check": "tsc --project .", "start": "node main.js", "build": "prisma generate", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node --test" }, "repository": { "type": "git", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d7c9b30..c8ee962 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,40 +34,52 @@ model Session { } model Account { - id String @id @default(uuid()) - balance Float - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(uuid()) + type AccountType + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - user User @relation(fields: [userId], references: [id]) - userId String - transactionsIn Transaction[] @relation(name: "receiver") - transactionsOut Transaction[] @relation(name: "sender") + owner User @relation(fields: [userId], references: [id]) + userId String + statements AccountStatement[] + debits Transaction[] @relation(name: "debit") + credit Transaction[] @relation(name: "credit") } -model Transaction { - id String @id @default(uuid()) - type TransactionType - amount Float - state TransactionState @default(INITIAL) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - from Account @relation(name: "sender", fields: [fromId], references: [id]) - fromId String - to Account @relation(name: "receiver", fields: [toId], references: [id]) - toId String +enum AccountType { + internal + external } -enum TransactionType { - TRANSFER - WITHDRAW - DEPOSIT +model AccountStatement { + date DateTime @db.Date + balance Float + credit Float + debit Float + + account Account @relation(fields: [accountId], references: [id]) + accountId String + + @@id([accountId, date]) } -enum TransactionState { - INITIAL - DEBITED - COMPLETED - FAILED +model Ledger { + id String @id @default(uuid()) + date DateTime? @unique @db.Date + balance Float + + transactions Transaction[] +} + +model Transaction { + id String @id @default(uuid()) + date DateTime @default(now()) + amount Float + + ledger Ledger @relation(fields: [ledgerId], references: [id]) + ledgerId String + to Account @relation(name: "debit", fields: [toId], references: [id]) + toId String + from Account @relation(name: "credit", fields: [fromId], references: [id]) + fromId String } diff --git a/src/api/http/wallet.js b/src/api/http/account.js similarity index 87% rename from src/api/http/wallet.js rename to src/api/http/account.js index 2bc9138..3e4ec9f 100644 --- a/src/api/http/wallet.js +++ b/src/api/http/account.js @@ -19,7 +19,7 @@ const transfer = { properties: { transactionId: { type: 'string' } }, }, }, - command: 'wallet.transfert', + command: 'account.transfert', }; -export const wallet = [transfer]; +export const account = [transfer]; diff --git a/src/api/http/router.js b/src/api/http/router.js index bdfef99..5751437 100644 --- a/src/api/http/router.js +++ b/src/api/http/router.js @@ -1,4 +1,4 @@ import { auth } from './auth.js'; -import { wallet } from './wallet'; +import { account } from './account.js'; -export const http = { auth, wallet }; +export const http = { auth, account }; diff --git a/src/services/account/account.d.ts b/src/services/account/account.d.ts new file mode 100644 index 0000000..89f350c --- /dev/null +++ b/src/services/account/account.d.ts @@ -0,0 +1,8 @@ +import type { Infra } from '../../infra/types'; +import type { Transaction } from '@prisma/client'; + +interface Wallet { + transfer(params: Pick): Promise<{ transactionId: string; }>; +} + +export function init(infra: Infra): Wallet; diff --git a/src/services/account/account.js b/src/services/account/account.js new file mode 100644 index 0000000..2decd8c --- /dev/null +++ b/src/services/account/account.js @@ -0,0 +1,22 @@ +/** @typedef {import('./account').init} init */ + +/** @type init */ +export const init = ({ db, bus }) => ({ + transfer: async ({ fromId, toId, amount }) => { + const ledger = await db.ledger.findFirst({ where: { date: null } }); + if (!ledger) throw new Error('Missing current ledger'); + + const transaction = await db.transaction.create({ + data: { + fromId, + toId, + amount, + ledgerId: ledger.id, + }, + }); + + bus.publish('account.transfer', transaction); + + return { transactionId: transaction.id }; + }, +}); diff --git a/src/services/account/account.test.js b/src/services/account/account.test.js new file mode 100644 index 0000000..6f7f29a --- /dev/null +++ b/src/services/account/account.test.js @@ -0,0 +1,50 @@ +// @ts-nocheck +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import * as account from './account.js'; + +describe('Account service', () => { + it('transfers the money', async () => { + const db = getDB(); + const bus = getBus(); + const service = account.init({ db, bus }); + + const params = { fromId: 'from', toId: 'to', amount: 10 }; + const result = await service.transfer(params); + + assert.deepEqual(result, { transactionId }); + assert.equal(db.ledger.invokedWith.date, null); + assert.deepEqual(db.transaction.invokedWith, { ...params, ledgerId }); + assert.equal(bus.events.length, 1); + assert.deepEqual(bus.events[0], { + name: 'account.transfer', + data: { id: transactionId }, + }); + }); + + const getBus = () => ({ + events: [], + publish(name, data) { + this.events.push({ name, data }); + }, + }); + + const ledgerId = '1'; + const transactionId = '1'; + const getDB = () => ({ + ledger: { + invokedWith: null, + findFirst({ where }) { + this.invokedWith = where; + return { id: ledgerId }; + }, + }, + transaction: { + invokedWith: null, + create({ data }) { + this.invokedWith = data; + return { id: transactionId }; + }, + }, + }); +}); diff --git a/src/services/wallet/wallet.d.ts b/src/services/wallet/wallet.d.ts deleted file mode 100644 index 74a1625..0000000 --- a/src/services/wallet/wallet.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Infra } from '../../infra/types'; - -interface Wallet { - transfer(params: { - fromAccount: string; - toAccount: string; - amount: number; - }): Promise<{ transactionId: string; }>; -} - -export function init(infra: Infra): Wallet; diff --git a/src/services/wallet/wallet.js b/src/services/wallet/wallet.js deleted file mode 100644 index 1e42ab2..0000000 --- a/src/services/wallet/wallet.js +++ /dev/null @@ -1,19 +0,0 @@ -/** @typedef {import('./wallet').init} init */ - -/** @type init */ -export const init = ({ db, bus }) => ({ - transfer: async ({ fromAccount, toAccount, amount }) => { - const transaction = await db.transaction.create({ - data: { - fromId: fromAccount, - toId: toAccount, - amount, - type: 'TRANSFER', - }, - }); - - bus.publish('wallet.transfer', transaction); - - return { transactionId: transaction.id }; - }, -}); From 2a6994ea4e624e2f56f4712a7fc525c4f254a780 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Thu, 29 Dec 2022 14:41:27 +0200 Subject: [PATCH 12/19] added auth/sign-up tests --- src/services/account/account.js | 3 +- src/services/account/account.test.js | 12 +++--- src/services/auth/auth.js | 2 +- src/services/auth/auth.test.js | 60 ++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 src/services/auth/auth.test.js diff --git a/src/services/account/account.js b/src/services/account/account.js index 2decd8c..a249e44 100644 --- a/src/services/account/account.js +++ b/src/services/account/account.js @@ -1,10 +1,11 @@ /** @typedef {import('./account').init} init */ +import { AppError } from '../../lib/error.js'; /** @type init */ export const init = ({ db, bus }) => ({ transfer: async ({ fromId, toId, amount }) => { const ledger = await db.ledger.findFirst({ where: { date: null } }); - if (!ledger) throw new Error('Missing current ledger'); + if (!ledger) throw new AppError('Missing current ledger'); const transaction = await db.transaction.create({ data: { diff --git a/src/services/account/account.test.js b/src/services/account/account.test.js index 6f7f29a..951ed75 100644 --- a/src/services/account/account.test.js +++ b/src/services/account/account.test.js @@ -3,9 +3,9 @@ import { describe, it } from 'node:test'; import assert from 'node:assert'; import * as account from './account.js'; -describe('Account service', () => { - it('transfers the money', async () => { - const db = getDB(); +describe('account/transfer', () => { + it('works', async () => { + const db = getDb(); const bus = getBus(); const service = account.init({ db, bus }); @@ -31,19 +31,19 @@ describe('Account service', () => { const ledgerId = '1'; const transactionId = '1'; - const getDB = () => ({ + const getDb = () => ({ ledger: { invokedWith: null, findFirst({ where }) { this.invokedWith = where; - return { id: ledgerId }; + return Promise.resolve({ id: ledgerId }); }, }, transaction: { invokedWith: null, create({ data }) { this.invokedWith = data; - return { id: transactionId }; + return Promise.resolve({ id: transactionId }); }, }, }); diff --git a/src/services/auth/auth.js b/src/services/auth/auth.js index 57a885e..aa68b49 100644 --- a/src/services/auth/auth.js +++ b/src/services/auth/auth.js @@ -17,7 +17,7 @@ export const init = ({ db, bus }) => ({ const token = crypto.random(); await db.session.create({ data: { userId, token } }); - bus.publish('signUp', { email }); + bus.publish('auth.signUp', { email }); return { userId, token }; }, diff --git a/src/services/auth/auth.test.js b/src/services/auth/auth.test.js new file mode 100644 index 0000000..dc05dd5 --- /dev/null +++ b/src/services/auth/auth.test.js @@ -0,0 +1,60 @@ +// @ts-nocheck +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import * as auth from './auth.js'; + +describe('auth/sign-up', () => { + it('works', async () => { + const bus = getBus(); + const db = getDb(); + const service = auth.init({ db, bus }); + + const params = { + email: 'test@mail.com', + password: 'test', + firstName: 'max', + lastName: 'prudnik', + }; + + const result = await service.signUp(params); + + assert.equal(result.userId, userId); + assert.ok(result.token); + + assert.equal(bus.events.length, 1); + assert.deepEqual(bus.events[0], { + name: 'auth.signUp', + data: { email: params.email }, + }); + }); + + const getBus = () => ({ + events: [], + publish(name, data) { + this.events.push({ name, data }); + }, + }); + + const userId = '1'; + const getDb = () => ({ + user: { + findInvokedWith: null, + createInvokedWith: null, + findUnique({ where }) { + this.findInvokedWith = where; + return Promise.resolve(null); + }, + create({ data }) { + this.createInvokedWith = data; + return Promise.resolve({ id: userId }); + }, + }, + session: { + createInvokedWith: null, + create({ data }) { + this.createInvokedWith = data; + return Promise.resolve(); + }, + }, + }); +}); From ab2ef1236bd1accb02bc30e840a5a9ad26eba87f Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 10 Jan 2023 12:41:48 +0200 Subject: [PATCH 13/19] fixed typo --- src/app.js | 4 +-- src/server/types.d.ts | 3 +- src/services/auth/auth.test.js | 62 ++++++++++++++++++---------------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/app.js b/src/app.js index f7c1a84..08d3ca6 100644 --- a/src/app.js +++ b/src/app.js @@ -9,7 +9,7 @@ export const init = async () => { await services.init(infrastructure); - const serviceInstance = await server.init( + const serverInstance = await server.init( router.http, infrastructure, config.server, @@ -17,7 +17,7 @@ export const init = async () => { return async () => { infrastructure.logger.info('Stopping application'); - await serviceInstance.close(); + await serverInstance.close(); await infra.teardown(infrastructure); }; }; diff --git a/src/server/types.d.ts b/src/server/types.d.ts index 3792426..0f3aa1d 100644 --- a/src/server/types.d.ts +++ b/src/server/types.d.ts @@ -20,4 +20,5 @@ export function init( router: HttpRouter, infra: Infra, config: ServerConfig -): Promise; \ No newline at end of file +): Promise; + diff --git a/src/services/auth/auth.test.js b/src/services/auth/auth.test.js index dc05dd5..38bbf46 100644 --- a/src/services/auth/auth.test.js +++ b/src/services/auth/auth.test.js @@ -3,41 +3,18 @@ import { describe, it } from 'node:test'; import assert from 'node:assert'; import * as auth from './auth.js'; -describe('auth/sign-up', () => { - it('works', async () => { - const bus = getBus(); - const db = getDb(); - const service = auth.init({ db, bus }); - - const params = { - email: 'test@mail.com', - password: 'test', - firstName: 'max', - lastName: 'prudnik', - }; - - const result = await service.signUp(params); - - assert.equal(result.userId, userId); - assert.ok(result.token); - - assert.equal(bus.events.length, 1); - assert.deepEqual(bus.events[0], { - name: 'auth.signUp', - data: { email: params.email }, - }); - }); - - const getBus = () => ({ +const mocks = { + getBus: () => ({ events: [], publish(name, data) { this.events.push({ name, data }); }, - }); + }), - const userId = '1'; - const getDb = () => ({ + getDb: () => ({ user: { + currentId: 0, + users: new Map(), findInvokedWith: null, createInvokedWith: null, findUnique({ where }) { @@ -46,6 +23,8 @@ describe('auth/sign-up', () => { }, create({ data }) { this.createInvokedWith = data; + const userId = String(this.currentId++); + this.users.set(userId, data); return Promise.resolve({ id: userId }); }, }, @@ -56,5 +35,30 @@ describe('auth/sign-up', () => { return Promise.resolve(); }, }, + }), +}; + +describe('auth/sign-up', () => { + it('works', async () => { + const bus = mocks.getBus(); + const db = mocks.getDb(); + const service = auth.init({ db, bus }); + + const params = { + email: 'test@mail.com', + password: 'test', + firstName: 'max', + lastName: 'prudnik', + }; + + const result = await service.signUp(params); + + assert.ok(result.token); + + assert.equal(bus.events.length, 1); + assert.deepEqual(bus.events[0], { + name: 'auth.signUp', + data: { email: params.email }, + }); }); }); From ad7664813b5839bda2e31fceef289fa93661eff2 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 10 Jan 2023 15:50:51 +0200 Subject: [PATCH 14/19] updated prisma, refactored domain model, added account/ deposit, withdraw and transfer operations --- package-lock.json | 1039 ++++++++++++++++++----------- package.json | 6 +- prisma/schema.prisma | 87 ++- src/services/account/account.d.ts | 23 +- src/services/account/account.js | 126 +++- 5 files changed, 831 insertions(+), 450 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e2a5fc..d927664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@fastify/cors": "^8.2.0", "@fastify/swagger": "^8.2.1", "@fastify/swagger-ui": "^1.3.0", - "@prisma/client": "^4.7.1", + "@prisma/client": "^4.8.1", "fastify": "^4.10.2", "fastify-plugin": "^4.4.0", "pino": "^8.7.0" @@ -30,7 +30,7 @@ "metatests": "^0.8.2", "pino-pretty": "^9.1.1", "prettier": "^2.7.1", - "prisma": "^4.7.1", + "prisma": "^4.8.1", "typescript": "^4.7.4" }, "engines": { @@ -38,9 +38,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "dev": true, "peer": true, "dependencies": { @@ -51,15 +51,15 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -82,9 +82,9 @@ } }, "node_modules/@fastify/ajv-compiler": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz", - "integrity": "sha512-69JnK7Cot+ktn7LD5TikP3b7psBPX55tYpQa8WSumt8r117PCa2zwHnImfBtRWYExreJlI48hr0WZaVrTBGj7w==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", + "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", "dependencies": { "ajv": "^8.11.0", "ajv-formats": "^2.1.1", @@ -92,9 +92,9 @@ } }, "node_modules/@fastify/ajv-compiler/node_modules/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -140,9 +140,9 @@ "integrity": "sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==" }, "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.1.0.tgz", - "integrity": "sha512-cTKBV2J9+u6VaKDhX7HepSfPSzw+F+TSd+k0wzifj4rG+4E5PjSFJCk19P8R6tr/72cuzgGd+mbB3jFT6lvAgw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.2.0.tgz", + "integrity": "sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==", "dependencies": { "fast-json-stringify": "^5.0.0" } @@ -161,43 +161,6 @@ "send": "^0.18.0" } }, - "node_modules/@fastify/static/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@fastify/static/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@fastify/static/node_modules/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@fastify/swagger": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-8.2.1.tgz", @@ -222,22 +185,6 @@ "yaml": "^2.1.3" } }, - "node_modules/@fastify/swagger-ui/node_modules/yaml": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", - "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@fastify/swagger/node_modules/yaml": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", - "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==", - "engines": { - "node": ">= 14" - } - }, "node_modules/@fastify/type-provider-json-schema-to-ts": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@fastify/type-provider-json-schema-to-ts/-/type-provider-json-schema-to-ts-2.2.1.tgz", @@ -249,9 +196,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -326,12 +273,12 @@ } }, "node_modules/@prisma/client": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.7.1.tgz", - "integrity": "sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.8.1.tgz", + "integrity": "sha512-d4xhZhETmeXK/yZ7K0KcVOzEfI5YKGGEr4F5SBV04/MU4ncN/HcE28sy3e4Yt8UFW0ZuImKFQJE+9rWt9WbGSQ==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + "@prisma/engines-version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe" }, "engines": { "node": ">=14.17" @@ -346,16 +293,16 @@ } }, "node_modules/@prisma/engines": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.7.1.tgz", - "integrity": "sha512-zWabHosTdLpXXlMefHmnouhXMoTB1+SCbUU3t4FCmdrtIOZcarPKU3Alto7gm/pZ9vHlGOXHCfVZ1G7OIrSbog==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.8.1.tgz", + "integrity": "sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw==", "devOptional": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz", - "integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==" + "version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe.tgz", + "integrity": "sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw==" }, "node_modules/@types/json-schema": { "version": "7.0.11", @@ -371,15 +318,15 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", - "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", "dev": true, "dependencies": { "@types/node": "*" @@ -455,9 +402,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -554,6 +501,18 @@ "node": ">=8.0.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/avvio": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.0.tgz", @@ -882,35 +841,43 @@ } }, "node_modules/es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.0.tgz", + "integrity": "sha512-GUGtW7eXQay0c+PRq0sGIKSdaBorfVqsCMhGHo4elP7YVqZu9nCZS4UkK4gv71gOWNMra/PaSKD3ao1oWExO0g==", "dev": true, "dependencies": { "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.0", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.0", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", "object-inspect": "^1.12.2", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" }, "engines": { "node": ">= 0.4" @@ -919,6 +886,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-shim-unscopables": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", @@ -963,13 +944,13 @@ } }, "node_modules/eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -988,7 +969,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -1029,9 +1010,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -1340,9 +1321,9 @@ } }, "node_modules/fast-json-stringify/node_modules/ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -1366,9 +1347,9 @@ "dev": true }, "node_modules/fast-querystring": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.0.0.tgz", - "integrity": "sha512-3LQi62IhQoDlmt4ULCYmh17vRO2EtS7hTSsG4WwoKWgV7GLMKBOecEh+aiavASnLx8I2y89OD33AGLo0ccRhzA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.0.tgz", + "integrity": "sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==", "dependencies": { "fast-decode-uri-component": "^1.0.1" } @@ -1393,9 +1374,9 @@ "integrity": "sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==" }, "node_modules/fastify": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.10.2.tgz", - "integrity": "sha512-0T+4zI6N3S8ex0LCZi3H4FasJR4AzWw834fUkPWvV8r6GBJkLmAOfFxH8f5V29Plef24IK0QSQD/tz1Nx+1UOA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.11.0.tgz", + "integrity": "sha512-JteZ8pjEqd+6n+azQnQfSJV8MUMxAmxbvC2Dx/Mybj039Lf/u3kda9Kq84uy/huCpqCzZoyHIZS5JFGF3wLztw==", "dependencies": { "@fastify/ajv-compiler": "^3.3.1", "@fastify/error": "^3.0.0", @@ -1420,9 +1401,9 @@ "integrity": "sha512-ovwFQG2qNy3jcCROiWpr94Hs0le+c7N/3t7m9aVwbFhkxcR/esp2xu25dP8e617HpQdmeDv+gFX4zagdUhDByw==" }, "node_modules/fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dependencies": { "reusify": "^1.0.4" } @@ -1440,9 +1421,9 @@ } }, "node_modules/find-my-way": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.3.1.tgz", - "integrity": "sha512-kGvM08SOkqvheLcuQ8GW9t/H901Qb9rZEbcNWbXopzy4jDRoaJpJoObPSKf4MnQLZ20ZTp7rL5MpF6rf+pqmyg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.4.0.tgz", + "integrity": "sha512-JFT7eURLU5FumlZ3VBGnveId82cZz7UR7OUu+THQJOwdQXxmS/g8v0KLoFhv97HreycOrmAbqjXD/4VG2j0uMQ==", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", @@ -1487,6 +1468,15 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1581,20 +1571,18 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1612,10 +1600,29 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1627,6 +1634,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -1675,6 +1709,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -1703,55 +1749,15 @@ } }, "node_modules/help-me": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.1.0.tgz", - "integrity": "sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", "dev": true, "dependencies": { "glob": "^8.0.0", "readable-stream": "^3.6.0" } }, - "node_modules/help-me/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/help-me/node_modules/glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/help-me/node_modules/minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/help-me/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -1801,9 +1807,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -1849,12 +1855,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" }, @@ -1870,6 +1876,20 @@ "node": ">= 0.10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -2061,6 +2081,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -2155,9 +2194,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -2240,6 +2279,15 @@ "node": ">=12.0.0" } }, + "node_modules/metatests/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -2491,9 +2539,9 @@ "dev": true }, "node_modules/pino": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.7.0.tgz", - "integrity": "sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.8.0.tgz", + "integrity": "sha512-cF8iGYeu2ODg2gIwgAHcPrtR63ILJz3f7gkogaHC/TXVVXxZgInmNYiIpDYEwgEkxZti2Se6P2W2DxlBIZe6eQ==", "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", @@ -2546,9 +2594,9 @@ } }, "node_modules/pino-std-serializers": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", - "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz", + "integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -2560,9 +2608,9 @@ } }, "node_modules/prettier": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", - "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", + "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -2587,13 +2635,13 @@ } }, "node_modules/prisma": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.7.1.tgz", - "integrity": "sha512-CCQP+m+1qZOGIZlvnL6T3ZwaU0LAleIHYFPN9tFSzjs/KL6vH9rlYbGOkTuG9Q1s6Ki5D0LJlYlW18Z9EBUpGg==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.8.1.tgz", + "integrity": "sha512-ZMLnSjwulIeYfaU1O6/LF6PEJzxN5par5weykxMykS9Z6ara/j76JH3Yo2AH3bgJbPN4Z6NeCK9s5fDkzf33cg==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "4.7.1" + "@prisma/engines": "4.8.1" }, "bin": { "prisma": "build/index.js", @@ -2680,9 +2728,9 @@ } }, "node_modules/readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -2823,6 +2871,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2888,9 +2956,9 @@ } }, "node_modules/safe-stable-stringify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", - "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", + "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==", "engines": { "node": ">=10" } @@ -3169,6 +3237,26 @@ "node": ">=8" } }, + "node_modules/tap-mocha-reporter/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tap-parser": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-11.0.2.tgz", @@ -3195,6 +3283,15 @@ "yaml": "^1.10.2" } }, + "node_modules/tap-yaml/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3278,10 +3375,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3366,6 +3477,26 @@ "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -3406,12 +3537,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==", "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { @@ -3515,9 +3645,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "dev": true, "peer": true, "requires": { @@ -3525,15 +3655,15 @@ } }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -3547,9 +3677,9 @@ "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==" }, "@fastify/ajv-compiler": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.4.0.tgz", - "integrity": "sha512-69JnK7Cot+ktn7LD5TikP3b7psBPX55tYpQa8WSumt8r117PCa2zwHnImfBtRWYExreJlI48hr0WZaVrTBGj7w==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", + "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", "requires": { "ajv": "^8.11.0", "ajv-formats": "^2.1.1", @@ -3557,9 +3687,9 @@ }, "dependencies": { "ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3603,9 +3733,9 @@ "integrity": "sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==" }, "@fastify/fast-json-stringify-compiler": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.1.0.tgz", - "integrity": "sha512-cTKBV2J9+u6VaKDhX7HepSfPSzw+F+TSd+k0wzifj4rG+4E5PjSFJCk19P8R6tr/72cuzgGd+mbB3jFT6lvAgw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.2.0.tgz", + "integrity": "sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==", "requires": { "fast-json-stringify": "^5.0.0" } @@ -3622,36 +3752,6 @@ "p-limit": "^3.1.0", "readable-stream": "^4.0.0", "send": "^0.18.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", - "requires": { - "brace-expansion": "^2.0.1" - } - } } }, "@fastify/swagger": { @@ -3664,13 +3764,6 @@ "openapi-types": "^12.0.0", "rfdc": "^1.3.0", "yaml": "^2.1.1" - }, - "dependencies": { - "yaml": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", - "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==" - } } }, "@fastify/swagger-ui": { @@ -3683,13 +3776,6 @@ "openapi-types": "^12.0.2", "rfdc": "^1.3.0", "yaml": "^2.1.3" - }, - "dependencies": { - "yaml": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.0.tgz", - "integrity": "sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==" - } } }, "@fastify/type-provider-json-schema-to-ts": { @@ -3700,9 +3786,9 @@ "requires": {} }, "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -3755,23 +3841,23 @@ } }, "@prisma/client": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.7.1.tgz", - "integrity": "sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.8.1.tgz", + "integrity": "sha512-d4xhZhETmeXK/yZ7K0KcVOzEfI5YKGGEr4F5SBV04/MU4ncN/HcE28sy3e4Yt8UFW0ZuImKFQJE+9rWt9WbGSQ==", "requires": { - "@prisma/engines-version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + "@prisma/engines-version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe" } }, "@prisma/engines": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.7.1.tgz", - "integrity": "sha512-zWabHosTdLpXXlMefHmnouhXMoTB1+SCbUU3t4FCmdrtIOZcarPKU3Alto7gm/pZ9vHlGOXHCfVZ1G7OIrSbog==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.8.1.tgz", + "integrity": "sha512-93tctjNXcIS+i/e552IO6tqw17sX8liivv8WX9lDMCpEEe3ci+nT9F+1oHtAafqruXLepKF80i/D20Mm+ESlOw==", "devOptional": true }, "@prisma/engines-version": { - "version": "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz", - "integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==" + "version": "4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.8.0-61.d6e67a83f971b175a593ccc12e15c4a757f93ffe.tgz", + "integrity": "sha512-MHSOSexomRMom8QN4t7bu87wPPD+pa+hW9+71JnVcF3DqyyO/ycCLhRL1we3EojRpZxKvuyGho2REQsMCvxcJw==" }, "@types/json-schema": { "version": "7.0.11", @@ -3787,15 +3873,15 @@ "dev": true }, "@types/node": { - "version": "18.11.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", - "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "version": "18.11.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", + "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==", "dev": true }, "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", "dev": true, "requires": { "@types/node": "*" @@ -3848,9 +3934,9 @@ }, "dependencies": { "ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3921,6 +4007,12 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, "avvio": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.0.tgz", @@ -4152,35 +4244,54 @@ } }, "es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.0.tgz", + "integrity": "sha512-GUGtW7eXQay0c+PRq0sGIKSdaBorfVqsCMhGHo4elP7YVqZu9nCZS4UkK4gv71gOWNMra/PaSKD3ao1oWExO0g==", "dev": true, "requires": { "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.0", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.3", "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has": "^1.0.3", "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.0", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", "object-inspect": "^1.12.2", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" } }, "es-shim-unscopables": { @@ -4215,13 +4326,13 @@ "dev": true }, "eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -4240,7 +4351,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -4269,9 +4380,9 @@ "requires": {} }, "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz", + "integrity": "sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==", "dev": true, "requires": {} }, @@ -4509,9 +4620,9 @@ }, "dependencies": { "ajv": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz", - "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -4533,9 +4644,9 @@ "dev": true }, "fast-querystring": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.0.0.tgz", - "integrity": "sha512-3LQi62IhQoDlmt4ULCYmh17vRO2EtS7hTSsG4WwoKWgV7GLMKBOecEh+aiavASnLx8I2y89OD33AGLo0ccRhzA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.0.tgz", + "integrity": "sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==", "requires": { "fast-decode-uri-component": "^1.0.1" } @@ -4557,9 +4668,9 @@ "integrity": "sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==" }, "fastify": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.10.2.tgz", - "integrity": "sha512-0T+4zI6N3S8ex0LCZi3H4FasJR4AzWw834fUkPWvV8r6GBJkLmAOfFxH8f5V29Plef24IK0QSQD/tz1Nx+1UOA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.11.0.tgz", + "integrity": "sha512-JteZ8pjEqd+6n+azQnQfSJV8MUMxAmxbvC2Dx/Mybj039Lf/u3kda9Kq84uy/huCpqCzZoyHIZS5JFGF3wLztw==", "requires": { "@fastify/ajv-compiler": "^3.3.1", "@fastify/error": "^3.0.0", @@ -4584,9 +4695,9 @@ "integrity": "sha512-ovwFQG2qNy3jcCROiWpr94Hs0le+c7N/3t7m9aVwbFhkxcR/esp2xu25dP8e617HpQdmeDv+gFX4zagdUhDByw==" }, "fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "requires": { "reusify": "^1.0.4" } @@ -4601,9 +4712,9 @@ } }, "find-my-way": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.3.1.tgz", - "integrity": "sha512-kGvM08SOkqvheLcuQ8GW9t/H901Qb9rZEbcNWbXopzy4jDRoaJpJoObPSKf4MnQLZ20ZTp7rL5MpF6rf+pqmyg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.4.0.tgz", + "integrity": "sha512-JFT7eURLU5FumlZ3VBGnveId82cZz7UR7OUu+THQJOwdQXxmS/g8v0KLoFhv97HreycOrmAbqjXD/4VG2j0uMQ==", "requires": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", @@ -4636,6 +4747,15 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4703,17 +4823,33 @@ } }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "glob-parent": { @@ -4726,14 +4862,32 @@ } }, "globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -4770,6 +4924,12 @@ "get-intrinsic": "^1.1.1" } }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -4786,46 +4946,15 @@ } }, "help-me": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.1.0.tgz", - "integrity": "sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", "dev": true, "requires": { "glob": "^8.0.0", "readable-stream": "^3.6.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", - "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz", - "integrity": "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -4857,9 +4986,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "import-fresh": { @@ -4893,12 +5022,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", "dev": true, "requires": { - "get-intrinsic": "^1.1.0", + "get-intrinsic": "^1.1.3", "has": "^1.0.3", "side-channel": "^1.0.4" } @@ -4908,6 +5037,17 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -5030,6 +5170,19 @@ "has-symbols": "^1.0.2" } }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -5102,9 +5255,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -5164,6 +5317,14 @@ "tap-yaml": "^1.0.0", "yaml": "^1.10.2", "yargs": "^15.4.1" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } } }, "mime": { @@ -5351,9 +5512,9 @@ "dev": true }, "pino": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.7.0.tgz", - "integrity": "sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.8.0.tgz", + "integrity": "sha512-cF8iGYeu2ODg2gIwgAHcPrtR63ILJz3f7gkogaHC/TXVVXxZgInmNYiIpDYEwgEkxZti2Se6P2W2DxlBIZe6eQ==", "requires": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", @@ -5400,9 +5561,9 @@ } }, "pino-std-serializers": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz", - "integrity": "sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz", + "integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==" }, "prelude-ls": { "version": "1.2.1", @@ -5411,9 +5572,9 @@ "dev": true }, "prettier": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.0.tgz", - "integrity": "sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz", + "integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==", "dev": true }, "prettier-linter-helpers": { @@ -5426,12 +5587,12 @@ } }, "prisma": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.7.1.tgz", - "integrity": "sha512-CCQP+m+1qZOGIZlvnL6T3ZwaU0LAleIHYFPN9tFSzjs/KL6vH9rlYbGOkTuG9Q1s6Ki5D0LJlYlW18Z9EBUpGg==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.8.1.tgz", + "integrity": "sha512-ZMLnSjwulIeYfaU1O6/LF6PEJzxN5par5weykxMykS9Z6ara/j76JH3Yo2AH3bgJbPN4Z6NeCK9s5fDkzf33cg==", "devOptional": true, "requires": { - "@prisma/engines": "4.7.1" + "@prisma/engines": "4.8.1" } }, "process": { @@ -5485,9 +5646,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "readable-stream": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.2.0.tgz", - "integrity": "sha512-gJrBHsaI3lgBoGMW/jHZsQ/o/TIWiu5ENCJG1BB7fuCKzpFM8GaS2UoBVt9NO+oI+3FcrBNbUkl3ilDe09aY4A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.3.0.tgz", + "integrity": "sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==", "requires": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -5580,6 +5741,22 @@ "dev": true, "requires": { "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } }, "run-parallel": { @@ -5616,9 +5793,9 @@ } }, "safe-stable-stringify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", - "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==" + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz", + "integrity": "sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==" }, "secure-json-parse": { "version": "2.6.0", @@ -5834,6 +6011,20 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } } } }, @@ -5855,6 +6046,14 @@ "dev": true, "requires": { "yaml": "^1.10.2" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } } }, "text-table": { @@ -5925,10 +6124,21 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, "typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", + "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", "dev": true }, "unbox-primitive": { @@ -5994,6 +6204,20 @@ "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -6028,10 +6252,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz", + "integrity": "sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==" }, "yargs": { "version": "15.4.1", diff --git a/package.json b/package.json index b1313ec..e00c8c4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "main": "main.js", "scripts": { - "check": "tsc --project .", + "check": "eslint . && tsc --project .", "start": "node main.js", "build": "prisma generate", "test": "node --test" @@ -28,7 +28,7 @@ "@fastify/cors": "^8.2.0", "@fastify/swagger": "^8.2.1", "@fastify/swagger-ui": "^1.3.0", - "@prisma/client": "^4.7.1", + "@prisma/client": "^4.8.1", "fastify": "^4.10.2", "fastify-plugin": "^4.4.0", "pino": "^8.7.0" @@ -45,7 +45,7 @@ "metatests": "^0.8.2", "pino-pretty": "^9.1.1", "prettier": "^2.7.1", - "prisma": "^4.7.1", + "prisma": "^4.8.1", "typescript": "^4.7.4" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c8ee962..2902cd2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -34,21 +34,14 @@ model Session { } model Account { - id String @id @default(uuid()) - type AccountType - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - owner User @relation(fields: [userId], references: [id]) - userId String - statements AccountStatement[] - debits Transaction[] @relation(name: "debit") - credit Transaction[] @relation(name: "credit") -} + id String @id @default(uuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt -enum AccountType { - internal - external + owner User @relation(fields: [userId], references: [id]) + userId String + statements AccountStatement[] + transactions AccountTransaction[] } model AccountStatement { @@ -64,22 +57,66 @@ model AccountStatement { } model Ledger { - id String @id @default(uuid()) - date DateTime? @unique @db.Date + id String @id @default(uuid()) + type LedgerType + name String @unique + + statements LedgerStatement[] + credits LedgerTransaction[] @relation(name: "credit") + debits LedgerTransaction[] @relation(name: "debit") + transactions AccountTransaction[] +} + +enum LedgerType { + asset + liability + revenue + expense + gain + loss +} + +model LedgerStatement { + date DateTime @default(now()) @db.Date balance Float - transactions Transaction[] + ledger Ledger @relation(fields: [ledgerId], references: [id]) + ledgerId String + + @@id([ledgerId, date]) } -model Transaction { +model LedgerTransaction { id String @id @default(uuid()) - date DateTime @default(now()) amount Float + date DateTime @default(now()) - ledger Ledger @relation(fields: [ledgerId], references: [id]) - ledgerId String - to Account @relation(name: "debit", fields: [toId], references: [id]) - toId String - from Account @relation(name: "credit", fields: [fromId], references: [id]) - fromId String + from Ledger @relation(name: "credit", fields: [fromId], references: [id]) + fromId String + to Ledger @relation(name: "debit", fields: [toId], references: [id]) + toId String +} + +model AccountTransaction { + id String @id @default(uuid()) + date DateTime @default(now()) + amount Float + typeInternal AccountTransactionTypeInternal + typeExternal AccountTransactionTypeExternal + + ledger Ledger @relation(fields: [ledgerId], references: [id]) + ledgerId String + account Account @relation(fields: [accountId], references: [id]) + accountId String +} + +enum AccountTransactionTypeInternal { + credit + debit +} + +enum AccountTransactionTypeExternal { + deposit + withdrawal + bankFee } diff --git a/src/services/account/account.d.ts b/src/services/account/account.d.ts index 89f350c..b53f71b 100644 --- a/src/services/account/account.d.ts +++ b/src/services/account/account.d.ts @@ -1,8 +1,23 @@ import type { Infra } from '../../infra/types'; -import type { Transaction } from '@prisma/client'; +import type { + Prisma, + Account as AccountModel, + AccountTransaction as AccountTransactionModel, +} from '@prisma/client'; -interface Wallet { - transfer(params: Pick): Promise<{ transactionId: string; }>; +interface Account { + deposit(accountId: AccountModel['id'], amount: AccountTransactionModel['amount']): Promise; + withdraw(accountId: AccountModel['id'], amount: AccountTransactionModel['amount']): Promise; + transfer( + fromId: AccountModel['id'], + toId: AccountModel['id'], + amount: AccountTransactionModel['amount'], + ): Promise; } -export function init(infra: Infra): Wallet; +export function getBalance( + db: Infra['db'] | Prisma.TransactionClient, + accountId: AccountModel['id'], +): Promise + +export function init(infra: Infra): Account; diff --git a/src/services/account/account.js b/src/services/account/account.js index a249e44..a98f8c5 100644 --- a/src/services/account/account.js +++ b/src/services/account/account.js @@ -1,23 +1,129 @@ -/** @typedef {import('./account').init} init */ +/** @typedef {import('./account')} AccountService */ import { AppError } from '../../lib/error.js'; -/** @type init */ +/** @type AccountService['init'] */ export const init = ({ db, bus }) => ({ - transfer: async ({ fromId, toId, amount }) => { - const ledger = await db.ledger.findFirst({ where: { date: null } }); - if (!ledger) throw new AppError('Missing current ledger'); + deposit: async (accountId, amount) => { + const ledger = await db.ledger.findUnique({ where: { name: 'HouseCash' } }); + if (!ledger) throw new AppError('Transaction failed'); - const transaction = await db.transaction.create({ + await db.accountTransaction.create({ data: { - fromId, - toId, + accountId, amount, ledgerId: ledger.id, + typeInternal: 'debit', + typeExternal: 'deposit', }, }); + bus.publish('account.deposit', { accountId, amount }); + }, + withdraw: async (accountId, amount) => { + const ledger = await db.ledger.findUnique({ where: { name: 'HouseCash' } }); + if (!ledger) throw new AppError('Transaction failed'); + + await db.$transaction(async (tx) => { + const balance = await getBalance(tx, accountId); + if (amount > balance) throw new AppError('Insufficient funds'); + + await tx.accountTransaction.create({ + data: { + accountId, + amount, + ledgerId: ledger.id, + typeInternal: 'credit', + typeExternal: 'withdrawal', + }, + }); + }); + bus.publish('account.withdraw', { accountId, amount }); + }, + transfer: async (fromId, toId, amount) => { + const reserveLedger = await db.ledger.findUnique({ + where: { name: 'HouseReserve' }, + }); + const cashLedger = await db.ledger.findUnique({ + where: { name: 'HouseCash' }, + }); + if (!reserveLedger || !cashLedger) throw new AppError('Transaction failed'); + + await db.$transaction(async (tx) => { + const balance = await getBalance(tx, fromId); + if (amount > balance) throw new AppError('Insufficient funds'); + + await tx.accountTransaction.create({ + data: { + accountId: fromId, + amount, + ledgerId: reserveLedger.id, + typeInternal: 'credit', + typeExternal: 'withdrawal', + }, + }); + }); + bus.publish('account.transfer', { + fromId, + toId, + amount, + state: 'initial', + }); - bus.publish('account.transfer', transaction); + await db.ledgerTransaction.create({ + data: { + fromId: reserveLedger.id, + toId: cashLedger.id, + amount, + }, + }); + bus.publish('account.transfer', { + fromId, + toId, + amount, + state: 'partial', + }); - return { transactionId: transaction.id }; + await db.accountTransaction.create({ + data: { + accountId: toId, + amount, + ledgerId: cashLedger.id, + typeInternal: 'debit', + typeExternal: 'deposit', + }, + }); + bus.publish('account.transfer', { + fromId, + toId, + amount, + state: 'completed', + }); }, }); + +/** @type AccountService['getBalance'] */ +const getBalance = async (db, accountId) => { + const [statement] = await db.accountStatement.findMany({ + where: { accountId }, + select: { balance: true, date: true }, + orderBy: { date: 'desc' }, + take: 1, + }); + + const filter = { accountId, date: { gt: new Date(0) } }; + if (statement) filter.date.gt = statement.date; + + const { _sum: debitSum } = await db.accountTransaction.aggregate({ + _sum: { amount: true }, + where: { ...filter, typeInternal: 'debit' }, + }); + const { _sum: creditSum } = await db.accountTransaction.aggregate({ + _sum: { amount: true }, + where: { ...filter, typeInternal: 'credit' }, + }); + const debit = debitSum.amount ?? 0; + const credit = creditSum.amount ?? 0; + const balance = statement + ? statement.balance + (credit - debit) + : credit - debit; + return balance; +}; From 9bac37a9830259fd9c8e339e77adea95cca4e817 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 10 Jan 2023 21:44:58 +0200 Subject: [PATCH 15/19] added tests for account service --- prisma/schema.prisma | 2 - src/services/account/account.d.ts | 2 + src/services/account/account.js | 6 +- src/services/account/account.test.js | 153 ++++++++++++++++++++------- 4 files changed, 119 insertions(+), 44 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2902cd2..8199e61 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,8 +47,6 @@ model Account { model AccountStatement { date DateTime @db.Date balance Float - credit Float - debit Float account Account @relation(fields: [accountId], references: [id]) accountId String diff --git a/src/services/account/account.d.ts b/src/services/account/account.d.ts index b53f71b..71ecdb8 100644 --- a/src/services/account/account.d.ts +++ b/src/services/account/account.d.ts @@ -13,6 +13,8 @@ interface Account { toId: AccountModel['id'], amount: AccountTransactionModel['amount'], ): Promise; + getBalance(accountId: AccountModel['id']): Promise; + getTransactions(accountId: AccountModel['id']): Promise; } export function getBalance( diff --git a/src/services/account/account.js b/src/services/account/account.js index a98f8c5..a50114d 100644 --- a/src/services/account/account.js +++ b/src/services/account/account.js @@ -98,6 +98,8 @@ export const init = ({ db, bus }) => ({ state: 'completed', }); }, + getBalance: (accountId) => getBalance(db, accountId), + getTransactions: (accountId) => db.accountTransaction.findMany({ where: { accountId } }), }); /** @type AccountService['getBalance'] */ @@ -123,7 +125,7 @@ const getBalance = async (db, accountId) => { const debit = debitSum.amount ?? 0; const credit = creditSum.amount ?? 0; const balance = statement - ? statement.balance + (credit - debit) - : credit - debit; + ? statement.balance + (debit - credit) + : debit - credit; return balance; }; diff --git a/src/services/account/account.test.js b/src/services/account/account.test.js index 951ed75..011dc5c 100644 --- a/src/services/account/account.test.js +++ b/src/services/account/account.test.js @@ -1,50 +1,123 @@ -// @ts-nocheck -import { describe, it } from 'node:test'; +/** @typedef {import('./account').Account} Account */ +/** @typedef {import('../../infra/types').Infra} Infra */ +import { describe, it, before, after, afterEach } from 'node:test'; import assert from 'node:assert'; +import * as infra from '../../infra/infra.js'; import * as account from './account.js'; -describe('account/transfer', () => { - it('works', async () => { - const db = getDb(); - const bus = getBus(); - const service = account.init({ db, bus }); - - const params = { fromId: 'from', toId: 'to', amount: 10 }; - const result = await service.transfer(params); - - assert.deepEqual(result, { transactionId }); - assert.equal(db.ledger.invokedWith.date, null); - assert.deepEqual(db.transaction.invokedWith, { ...params, ledgerId }); - assert.equal(bus.events.length, 1); - assert.deepEqual(bus.events[0], { - name: 'account.transfer', - data: { id: transactionId }, +describe('account', () => { + /** @type Account */ + let service; + /** @type Infra */ + let infrastructure; + const account1 = { id: 'account1', initialBalance: 1000 }; + const account2 = { id: 'account2', initialBalance: 1000 }; + + before(async () => { + infrastructure = await infra.init({ logger: { env: 'test' }, db: {} }); + service = account.init(infrastructure); + const { db } = infrastructure; + + const user = await db.user.create({ + data: { + email: 'test@email.com', + firstName: 'test', + lastName: 'test', + passwordHash: 'test', + }, + }); + await db.account.createMany({ + data: [ + { id: account1.id, userId: user.id }, + { id: account2.id, userId: user.id }, + ], + }); + await db.accountStatement.createMany({ + data: [ + { + accountId: account1.id, + balance: account1.initialBalance, + date: new Date(), + }, + { + accountId: account2.id, + balance: account2.initialBalance, + date: new Date(), + }, + ], + }); + await db.ledger.createMany({ + data: [ + { name: 'HouseCash', type: 'liability' }, + { name: 'HouseReserve', type: 'liability' }, + ], }); }); - const getBus = () => ({ - events: [], - publish(name, data) { - this.events.push({ name, data }); - }, + afterEach(async () => { + const { db } = infrastructure; + await db.accountTransaction.deleteMany({}); + await db.ledgerTransaction.deleteMany({}); }); - const ledgerId = '1'; - const transactionId = '1'; - const getDb = () => ({ - ledger: { - invokedWith: null, - findFirst({ where }) { - this.invokedWith = where; - return Promise.resolve({ id: ledgerId }); - }, - }, - transaction: { - invokedWith: null, - create({ data }) { - this.invokedWith = data; - return Promise.resolve({ id: transactionId }); - }, - }, + after(async () => { + const { db } = infrastructure; + await db.accountTransaction.deleteMany({}); + await db.ledgerTransaction.deleteMany({}); + await db.accountStatement.deleteMany({}); + await db.account.deleteMany({}).catch(console.log); + await db.ledger.deleteMany({}); + await db.user.deleteMany({}); + await infra.teardown(infrastructure); + }); + + it('deposits', async () => { + const amount = 50; + await service.deposit(account1.id, amount); + + const currentBalance = await service.getBalance(account1.id); + assert.equal(currentBalance, account1.initialBalance + amount); + const transactions = await service.getTransactions(account1.id); + assert.equal(transactions.length, 1); + const tx = transactions[0]; + assert.equal(tx.amount, amount); + assert.equal(tx.accountId, account1.id); + assert.equal(tx.typeExternal, 'deposit'); + }); + + it('withdraws', async () => { + const amount = 100; + await service.withdraw(account1.id, amount); + + const currentBalance = await service.getBalance(account1.id); + assert.equal(currentBalance, account1.initialBalance - amount); + const transactions = await service.getTransactions(account1.id); + assert.equal(transactions.length, 1); + const tx = transactions[0]; + assert.equal(tx.amount, amount); + assert.equal(tx.accountId, account1.id); + assert.equal(tx.typeExternal, 'withdrawal'); + }); + + it('transfers', async () => { + const amount = 200; + await service.transfer(account1.id, account2.id, amount); + + const currentBalance1 = await service.getBalance(account1.id); + const currentBalance2 = await service.getBalance(account2.id); + assert.equal(currentBalance1, account1.initialBalance - amount); + assert.equal(currentBalance2, account2.initialBalance + amount); + const transactions1 = await service.getTransactions(account1.id); + assert.equal(transactions1.length, 1); + const tx1 = transactions1[0]; + assert.equal(tx1.amount, amount); + assert.equal(tx1.accountId, account1.id); + assert.equal(tx1.typeExternal, 'withdrawal'); + const transactions2 = await service.getTransactions(account2.id); + assert.equal(transactions2.length, 1); + const tx2 = transactions2[0]; + assert.equal(tx2.amount, amount); + assert.equal(tx2.accountId, account2.id); + assert.equal(tx2.typeExternal, 'deposit'); }); }); From 3e4c3b067d1c5da3a89abd2137cca18bcc03aec5 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 10 Jan 2023 22:51:26 +0200 Subject: [PATCH 16/19] added ws infra and notification service --- src/infra/types.d.ts | 14 ++++++-- src/infra/ws.js | 21 ++++++++++++ src/services/notification/notification.d.ts | 5 +++ src/services/notification/notification.js | 36 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/infra/ws.js create mode 100644 src/services/notification/notification.d.ts create mode 100644 src/services/notification/notification.js diff --git a/src/infra/types.d.ts b/src/infra/types.d.ts index edd785f..7b6e5c0 100644 --- a/src/infra/types.d.ts +++ b/src/infra/types.d.ts @@ -1,8 +1,9 @@ import type { PrismaClient, Prisma } from '@prisma/client'; import { BaseLogger } from 'pino'; +type Event = Record; interface EventHandler { - (event: object): any; + (event: Event): any; } export type Logger = Pick; @@ -12,12 +13,21 @@ export interface Bus { registerService(name: string, service: object): void; subscribe(eventName: string, handler: EventHandler): boolean; - publish(eventName: string, event: object): boolean; + publish(eventName: string, event: Event): boolean; +} + +type WSMessage = Record; +export interface WS { + has(id: string): boolean; + add(id: string, socket: any): void; + remove(id: string): void; + send(id: string, data: WSMessage): void; } export type Infra = { bus: Bus; logger: Logger; db: DB; + ws: WS; }; export type LoggerConfig = { env: string }; diff --git a/src/infra/ws.js b/src/infra/ws.js new file mode 100644 index 0000000..617934b --- /dev/null +++ b/src/infra/ws.js @@ -0,0 +1,21 @@ +/** @typedef {import('./types').Infra['ws']} WS */ + +/** @type function(): WS */ +export const init = () => { + const users = new Map(); + + return { + has: (id) => users.has(id), + add: (id, socket) => { + users.set(id, socket); + }, + remove: (id) => { + users.delete(id); + }, + send: (id, message) => { + const data = JSON.stringify(message); + users.get(id).send(data); + }, + }; +}; + diff --git a/src/services/notification/notification.d.ts b/src/services/notification/notification.d.ts new file mode 100644 index 0000000..647affc --- /dev/null +++ b/src/services/notification/notification.d.ts @@ -0,0 +1,5 @@ +import type { Infra } from '../../infra/types'; + +interface Notification {} + +export function init(infra: Infra): Notification; diff --git a/src/services/notification/notification.js b/src/services/notification/notification.js new file mode 100644 index 0000000..e917530 --- /dev/null +++ b/src/services/notification/notification.js @@ -0,0 +1,36 @@ +/** @typedef {import('./notification')} Notification */ +import { AppError } from '../../lib/error'; + +/** @type Notification['init'] */ +export const init = ({ bus, db, ws }) => { + bus.subscribe('account.transfer', async ({ fromId, toId, amount, state }) => { + if (state !== 'completed') return; + + const sender = await db.account.findUnique({ + where: { id: fromId }, + include: { owner: true }, + }); + if (!sender) throw new AppError('Sender not found'); + const receiver = await db.account.findUnique({ + where: { id: toId }, + include: { owner: true }, + }); + if (!receiver) throw new AppError('Receiver not found'); + + if (ws.has(sender.owner.id)) { + const fullName = `${receiver.owner.firstName} ${receiver.owner.lastName}`; + ws.send(sender.owner.id, { + message: `Successfully sent ${amount}$ to ${fullName}`, + }); + } + + if (ws.has(receiver.owner.id)) { + const fullName = `${sender.owner.firstName} ${sender.owner.lastName}`; + ws.send(receiver.owner.id, { + message: `Successfully received ${amount}$ from ${fullName}`, + }); + } + }); + + return {}; +}; From 856fe1770b6a2a5c3370622a4cc107160c5b46de Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Tue, 10 Jan 2023 23:18:36 +0200 Subject: [PATCH 17/19] added websocket plugin, connected ws to ws state infra --- package-lock.json | 47 ++++++++++++++++++++++++++++++++- package.json | 3 ++- src/infra/infra.js | 6 +++-- src/infra/ws.js | 1 - src/server/plugins/websocket.js | 7 +++++ src/server/server.js | 20 +++++++++++++- src/services/account/account.js | 3 ++- 7 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 src/server/plugins/websocket.js diff --git a/package-lock.json b/package-lock.json index d927664..4e064f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fastify/cors": "^8.2.0", "@fastify/swagger": "^8.2.1", "@fastify/swagger-ui": "^1.3.0", + "@fastify/websocket": "^7.1.2", "@prisma/client": "^4.8.1", "fastify": "^4.10.2", "fastify-plugin": "^4.4.0", @@ -21,7 +22,7 @@ "devDependencies": { "@fastify/type-provider-json-schema-to-ts": "^2.2.1", "@types/node": "^18.7.8", - "@types/ws": "^8.5.3", + "@types/ws": "^8.5.4", "eslint": "^8.22.0", "eslint-config-metarhia": "^8.1.0", "eslint-config-prettier": "^8.5.0", @@ -195,6 +196,15 @@ "json-schema-to-ts": "^2.0.0" } }, + "node_modules/@fastify/websocket": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-7.1.2.tgz", + "integrity": "sha512-OBaPR3KVkDmhJpCy+RtM8CwwV67ieRRbvWtpHqRNoMt+AnHLBiNmrTwHs3/DKjmnI1cqdGTaQ+NhKtDtkk1l/Q==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "ws": "^8.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -3525,6 +3535,26 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -3785,6 +3815,15 @@ "dev": true, "requires": {} }, + "@fastify/websocket": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-7.1.2.tgz", + "integrity": "sha512-OBaPR3KVkDmhJpCy+RtM8CwwV67ieRRbvWtpHqRNoMt+AnHLBiNmrTwHs3/DKjmnI1cqdGTaQ+NhKtDtkk1l/Q==", + "requires": { + "fastify-plugin": "^4.0.0", + "ws": "^8.0.0" + } + }, "@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -6240,6 +6279,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "requires": {} + }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", diff --git a/package.json b/package.json index e00c8c4..2f559cb 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@fastify/cors": "^8.2.0", "@fastify/swagger": "^8.2.1", "@fastify/swagger-ui": "^1.3.0", + "@fastify/websocket": "^7.1.2", "@prisma/client": "^4.8.1", "fastify": "^4.10.2", "fastify-plugin": "^4.4.0", @@ -36,7 +37,7 @@ "devDependencies": { "@fastify/type-provider-json-schema-to-ts": "^2.2.1", "@types/node": "^18.7.8", - "@types/ws": "^8.5.3", + "@types/ws": "^8.5.4", "eslint": "^8.22.0", "eslint-config-metarhia": "^8.1.0", "eslint-config-prettier": "^8.5.0", diff --git a/src/infra/infra.js b/src/infra/infra.js index fd15066..5c8c5ef 100644 --- a/src/infra/infra.js +++ b/src/infra/infra.js @@ -2,15 +2,17 @@ /** @typedef {import('./types').InfraConfig} Config */ import { init as initLogger } from './logger.js'; import { init as initDB, teardown as teardownDB } from './db.js'; +import { init as initWS } from './ws.js'; import { Bus } from './bus.js'; /** @type function(Config): Promise */ export const init = async (config) => { const bus = new Bus(); - const logger = await initLogger(config.logger); + const logger = initLogger(config.logger); const db = await initDB({ logger }, config.db); + const ws = initWS(); - return { bus, logger, db }; + return { bus, logger, db, ws }; }; /** @type function(Infra): Promise */ diff --git a/src/infra/ws.js b/src/infra/ws.js index 617934b..db63f6f 100644 --- a/src/infra/ws.js +++ b/src/infra/ws.js @@ -18,4 +18,3 @@ export const init = () => { }, }; }; - diff --git a/src/server/plugins/websocket.js b/src/server/plugins/websocket.js new file mode 100644 index 0000000..f9afec0 --- /dev/null +++ b/src/server/plugins/websocket.js @@ -0,0 +1,7 @@ +import fp from 'fastify-plugin'; +import plugin from '@fastify/websocket'; + +export const websocket = (options) => + fp(async (fastify) => { + await fastify.register(plugin, { options }); + }); diff --git a/src/server/server.js b/src/server/server.js index 68e9033..820b452 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -2,6 +2,7 @@ import { fastify } from 'fastify'; import { cors } from './plugins/cors.js'; import { swagger } from './plugins/swagger.js'; +import { websocket } from './plugins/websocket.js'; import { handleError } from '../lib/error.js'; const objectProps = { type: 'object', additionalProperties: false }; @@ -30,13 +31,14 @@ const generateSchema = (prefix, definition) => { /** @type init */ export const init = async (router, infra, config) => { - const { db, logger, bus } = infra; + const { db, logger, bus, ws } = infra; const { host, port, instance } = config; const server = fastify({ logger, ...instance }); await server.register(cors(config.cors)); await server.register(swagger(config.swagger)); + await server.register(websocket({})); server.setErrorHandler(server.errorHandler); server.setErrorHandler((error, req, res) => { @@ -77,6 +79,22 @@ export const init = async (router, infra, config) => { } } + server.route({ + method: 'GET', + url: '/ws', + websocket: true, + handler: async () => 'WS', + wsHandler: (connection, req) => { + const { id: userId } = req.user; + connection.socket.on('open', () => { + ws.add(userId, connection.socket); + }); + connection.socket.on('close', () => { + ws.remove(userId); + }); + }, + }); + await server.listen({ host, port }); return server; diff --git a/src/services/account/account.js b/src/services/account/account.js index a50114d..9f6b45e 100644 --- a/src/services/account/account.js +++ b/src/services/account/account.js @@ -99,7 +99,8 @@ export const init = ({ db, bus }) => ({ }); }, getBalance: (accountId) => getBalance(db, accountId), - getTransactions: (accountId) => db.accountTransaction.findMany({ where: { accountId } }), + getTransactions: (accountId) => + db.accountTransaction.findMany({ where: { accountId } }), }); /** @type AccountService['getBalance'] */ From 143e524fe6ec25b005ae55c3a0ea000137733601 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Wed, 11 Jan 2023 11:50:27 +0200 Subject: [PATCH 18/19] added auth to http and ws connections, added verify to auth service, updated bus to support session --- src/api/types.d.ts | 3 ++- src/infra/bus.js | 4 +-- src/infra/types.d.ts | 3 ++- src/server/plugins/auth.js | 7 +++++ src/server/server.js | 52 ++++++++++++++++++++++++++++++++----- src/server/types.d.ts | 13 +++++++++- src/services/auth/auth.d.ts | 2 ++ src/services/auth/auth.js | 7 +++++ src/types.d.ts | 3 +++ 9 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 src/types.d.ts diff --git a/src/api/types.d.ts b/src/api/types.d.ts index 3ac89fe..45dca8a 100644 --- a/src/api/types.d.ts +++ b/src/api/types.d.ts @@ -6,8 +6,9 @@ type HttpSchema = { source?: HttpDataSource, input?: ValidationSchema, output?: export type HttpRoute = { method: HttpMethod; url: string; + auth?: object; schema: HttpSchema; command: string; } -export type HttpRouter = Record; \ No newline at end of file +export type HttpRouter = Record; diff --git a/src/infra/bus.js b/src/infra/bus.js index d10d461..4e16bf1 100644 --- a/src/infra/bus.js +++ b/src/infra/bus.js @@ -11,10 +11,10 @@ export class Bus { this.#services = new Map(); } - command(name, payload) { + command(name, payload, session) { const [serviceName, methodName] = name.split('.'); const service = this.#services.get(serviceName); - return service[methodName](payload); + return service[methodName](payload, session); } registerService(name, service) { diff --git a/src/infra/types.d.ts b/src/infra/types.d.ts index 7b6e5c0..bf4f652 100644 --- a/src/infra/types.d.ts +++ b/src/infra/types.d.ts @@ -1,5 +1,6 @@ import type { PrismaClient, Prisma } from '@prisma/client'; import { BaseLogger } from 'pino'; +import { Session } from '../types'; type Event = Record; interface EventHandler { @@ -9,7 +10,7 @@ interface EventHandler { export type Logger = Pick; export type DB = PrismaClient; export interface Bus { - command(commandName: string, payload: object): Promise; + command(commandName: string, payload: object, session?: Session): Promise; registerService(name: string, service: object): void; subscribe(eventName: string, handler: EventHandler): boolean; diff --git a/src/server/plugins/auth.js b/src/server/plugins/auth.js index e69de29..f15e706 100644 --- a/src/server/plugins/auth.js +++ b/src/server/plugins/auth.js @@ -0,0 +1,7 @@ +import fp from 'fastify-plugin'; +import plugin from '@fastify/auth'; + +export const auth = (options) => + fp(async (fastify) => { + await fastify.register(plugin, options); + }); diff --git a/src/server/server.js b/src/server/server.js index 820b452..77374d2 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -1,5 +1,7 @@ /** @typedef {import('./types').init} init */ +/** @typedef {import('../types').Session} Session */ import { fastify } from 'fastify'; +import { auth } from './plugins/auth.js'; import { cors } from './plugins/cors.js'; import { swagger } from './plugins/swagger.js'; import { websocket } from './plugins/websocket.js'; @@ -22,7 +24,7 @@ const generateSchema = (prefix, definition) => { schema.response = { 200: { ...objectProps, ...definition.output }, 400: { description: 'Client error', ...errorResponse }, - //401: { description: 'Auth error', errorResponse }, + 401: { description: 'Auth error', ...errorResponse }, }; } @@ -39,6 +41,7 @@ export const init = async (router, infra, config) => { await server.register(cors(config.cors)); await server.register(swagger(config.swagger)); await server.register(websocket({})); + await server.register(auth({})); server.setErrorHandler(server.errorHandler); server.setErrorHandler((error, req, res) => { @@ -62,20 +65,47 @@ export const init = async (router, infra, config) => { for (const [prefix, routes] of Object.entries(router)) { for (const route of routes) { - const { method, url, schema: schemaDefinition, command } = route; + const { + method, + url, + schema: schemaDefinition, + auth: authDefinition, + command, + } = route; + const schema = generateSchema(prefix, schemaDefinition); - server.route({ + + const routeDefinition = { method, url: `/${prefix}${url}`, schema, handler: async (req, res) => { /** @type object */ - const payload = schema.source ? req[schema.source] : {}; - const result = await bus.command(command, payload); + const payload = schemaDefinition.source + ? req[schemaDefinition.source] + : {}; + const session = authDefinition ? req.session : undefined; + const result = await bus.command(command, payload, session); const [code, data] = schema.output ? [200, result] : [204, null]; res.code(code).send(data); }, - }); + }; + + if (authDefinition) { + routeDefinition.onRequest = server.auth([ + async (req) => { + const token = server.getAuthToken(req); + /** @type Session */ + const session = await bus.command('auth.verify', { + token, + definition: authDefinition, + }); + req.session = session; + }, + ]); + } + + server.route(routeDefinition); } } @@ -83,9 +113,17 @@ export const init = async (router, infra, config) => { method: 'GET', url: '/ws', websocket: true, + onRequest: server.auth([ + async (req) => { + const token = server.getAuthToken(req); + /** @type Session */ + const session = await bus.command('auth.verify', { token }); + req.session = session; + }, + ]), handler: async () => 'WS', wsHandler: (connection, req) => { - const { id: userId } = req.user; + const { userId } = req.session; connection.socket.on('open', () => { ws.add(userId, connection.socket); }); diff --git a/src/server/types.d.ts b/src/server/types.d.ts index 0f3aa1d..f934016 100644 --- a/src/server/types.d.ts +++ b/src/server/types.d.ts @@ -1,7 +1,8 @@ -import type { FastifyInstance, FastifyServerOptions } from 'fastify'; +import type { FastifyInstance, FastifyServerOptions } from 'fastify'; import type { FastifyCorsOptions } from '@fastify/cors' import type { HttpRouter } from '../api/types'; import type { Infra } from '../infra/types'; +import type { Session } from '../types'; export type ServerConfig = { host: string; @@ -22,3 +23,13 @@ export function init( config: ServerConfig ): Promise; +declare module 'fastify' { + interface FastifyInstance { + getAuthToken: (req: FastifyRequest) => string; + } + + interface FastifyRequest { + session: Session; + } +} + diff --git a/src/services/auth/auth.d.ts b/src/services/auth/auth.d.ts index 54a5339..5cb4660 100644 --- a/src/services/auth/auth.d.ts +++ b/src/services/auth/auth.d.ts @@ -1,4 +1,5 @@ import type { Infra } from '../../infra/types'; +import type { Session } from '../../types'; interface Auth { signUp(params: { @@ -13,6 +14,7 @@ interface Auth { }): Promise<{ userId: string; token: string; }>; signOut(params: { token: string; }): Promise; refresh(params: { token: string; }): Promise<{ token: string }>; + verify(params: { token: string; }): Promise; } export function init(infra: Infra): Auth; diff --git a/src/services/auth/auth.js b/src/services/auth/auth.js index aa68b49..2312d8e 100644 --- a/src/services/auth/auth.js +++ b/src/services/auth/auth.js @@ -55,4 +55,11 @@ export const init = ({ db, bus }) => ({ return { token: newToken }; }, + + verify: async ({ token }) => { + const session = await db.session.findUnique({ where: { token } }); + if (!session) throw new AppError('Not found'); + + return { userId: session.userId }; + }, }); diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..562a8d1 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,3 @@ +export interface Session { + userId: string; +} From a402f071fc7c31304e519df0cc91fba0cc30efa9 Mon Sep 17 00:00:00 2001 From: maksymprudnik Date: Wed, 11 Jan 2023 13:01:58 +0200 Subject: [PATCH 19/19] updated account service to mathc bus contract --- src/services/account/account.d.ts | 22 +++++++++++++----- src/services/account/account.js | 10 ++++---- src/services/account/account.test.js | 34 +++++++++++++++++++--------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/services/account/account.d.ts b/src/services/account/account.d.ts index 71ecdb8..6d9b532 100644 --- a/src/services/account/account.d.ts +++ b/src/services/account/account.d.ts @@ -6,15 +6,25 @@ import type { } from '@prisma/client'; interface Account { - deposit(accountId: AccountModel['id'], amount: AccountTransactionModel['amount']): Promise; - withdraw(accountId: AccountModel['id'], amount: AccountTransactionModel['amount']): Promise; - transfer( + deposit(params: { + accountId: AccountModel['id'], + amount: AccountTransactionModel['amount'], + }): Promise; + withdraw(params: { + accountId: AccountModel['id'], + amount: AccountTransactionModel['amount'], + }): Promise; + transfer(params: { fromId: AccountModel['id'], toId: AccountModel['id'], amount: AccountTransactionModel['amount'], - ): Promise; - getBalance(accountId: AccountModel['id']): Promise; - getTransactions(accountId: AccountModel['id']): Promise; + }): Promise; + getBalance(params: { + accountId: AccountModel['id'], + }): Promise; + getTransactions(params: { + accountId: AccountModel['id'], + }): Promise; } export function getBalance( diff --git a/src/services/account/account.js b/src/services/account/account.js index 9f6b45e..2aa55d5 100644 --- a/src/services/account/account.js +++ b/src/services/account/account.js @@ -3,7 +3,7 @@ import { AppError } from '../../lib/error.js'; /** @type AccountService['init'] */ export const init = ({ db, bus }) => ({ - deposit: async (accountId, amount) => { + deposit: async ({ accountId, amount }) => { const ledger = await db.ledger.findUnique({ where: { name: 'HouseCash' } }); if (!ledger) throw new AppError('Transaction failed'); @@ -18,7 +18,7 @@ export const init = ({ db, bus }) => ({ }); bus.publish('account.deposit', { accountId, amount }); }, - withdraw: async (accountId, amount) => { + withdraw: async ({ accountId, amount }) => { const ledger = await db.ledger.findUnique({ where: { name: 'HouseCash' } }); if (!ledger) throw new AppError('Transaction failed'); @@ -38,7 +38,7 @@ export const init = ({ db, bus }) => ({ }); bus.publish('account.withdraw', { accountId, amount }); }, - transfer: async (fromId, toId, amount) => { + transfer: async ({ fromId, toId, amount }) => { const reserveLedger = await db.ledger.findUnique({ where: { name: 'HouseReserve' }, }); @@ -98,8 +98,8 @@ export const init = ({ db, bus }) => ({ state: 'completed', }); }, - getBalance: (accountId) => getBalance(db, accountId), - getTransactions: (accountId) => + getBalance: ({ accountId }) => getBalance(db, accountId), + getTransactions: ({ accountId }) => db.accountTransaction.findMany({ where: { accountId } }), }); diff --git a/src/services/account/account.test.js b/src/services/account/account.test.js index 011dc5c..0fcfda6 100644 --- a/src/services/account/account.test.js +++ b/src/services/account/account.test.js @@ -73,11 +73,13 @@ describe('account', () => { it('deposits', async () => { const amount = 50; - await service.deposit(account1.id, amount); + await service.deposit({ accountId: account1.id, amount }); - const currentBalance = await service.getBalance(account1.id); + const currentBalance = await service.getBalance({ accountId: account1.id }); assert.equal(currentBalance, account1.initialBalance + amount); - const transactions = await service.getTransactions(account1.id); + const transactions = await service.getTransactions({ + accountId: account1.id, + }); assert.equal(transactions.length, 1); const tx = transactions[0]; assert.equal(tx.amount, amount); @@ -87,11 +89,13 @@ describe('account', () => { it('withdraws', async () => { const amount = 100; - await service.withdraw(account1.id, amount); + await service.withdraw({ accountId: account1.id, amount }); - const currentBalance = await service.getBalance(account1.id); + const currentBalance = await service.getBalance({ accountId: account1.id }); assert.equal(currentBalance, account1.initialBalance - amount); - const transactions = await service.getTransactions(account1.id); + const transactions = await service.getTransactions({ + accountId: account1.id, + }); assert.equal(transactions.length, 1); const tx = transactions[0]; assert.equal(tx.amount, amount); @@ -101,19 +105,27 @@ describe('account', () => { it('transfers', async () => { const amount = 200; - await service.transfer(account1.id, account2.id, amount); + await service.transfer({ fromId: account1.id, toId: account2.id, amount }); - const currentBalance1 = await service.getBalance(account1.id); - const currentBalance2 = await service.getBalance(account2.id); + const currentBalance1 = await service.getBalance({ + accountId: account1.id, + }); + const currentBalance2 = await service.getBalance({ + accountId: account2.id, + }); assert.equal(currentBalance1, account1.initialBalance - amount); assert.equal(currentBalance2, account2.initialBalance + amount); - const transactions1 = await service.getTransactions(account1.id); + const transactions1 = await service.getTransactions({ + accountId: account1.id, + }); assert.equal(transactions1.length, 1); const tx1 = transactions1[0]; assert.equal(tx1.amount, amount); assert.equal(tx1.accountId, account1.id); assert.equal(tx1.typeExternal, 'withdrawal'); - const transactions2 = await service.getTransactions(account2.id); + const transactions2 = await service.getTransactions({ + accountId: account2.id, + }); assert.equal(transactions2.length, 1); const tx2 = transactions2[0]; assert.equal(tx2.amount, amount);