diff --git a/package-lock.json b/package-lock.json index c675e0b..264289b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "license": "UNLICENSED", "dependencies": { + "awilix": "^8.0.1", "axios": "1.4.0", "express": "^4.18.2", + "generic-interceptor": "^2.2.1", "http-method-enum": "^1.0.0", "http-status-codes": "^2.2.0", "knex": "^2.5.1", @@ -69,10 +71,6 @@ "ts-jest": "^29.1.1", "tsc-watch": "4.0.0", "typescript": "4.7.4" - }, - "engines": { - "node": "18.17.1", - "npm": "9.8.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -3105,7 +3103,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -3118,7 +3115,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -3127,7 +3123,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -8877,6 +8872,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/awilix": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/awilix/-/awilix-8.0.1.tgz", + "integrity": "sha512-zDSp4R204scvQIDb2GMoWigzXemn0+3AKKIAt543T9v2h7lmoypvkmcx1W/Jet/nm27R1N1AsqrsYVviAR9KrA==", + "dependencies": { + "camel-case": "^4.1.2", + "fast-glob": "^3.2.12" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/aws-sdk": { "version": "2.1224.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1224.0.tgz", @@ -9171,7 +9178,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -9345,6 +9351,15 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -11557,7 +11572,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -11573,7 +11587,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -11610,7 +11623,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -11661,7 +11673,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -11902,6 +11913,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-interceptor": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/generic-interceptor/-/generic-interceptor-2.2.1.tgz", + "integrity": "sha512-UbUbZguVL8yx7qKewzUgy03aBIouVVxQZztcFNUIVEnGLNM4v2+b9jAKMsm2ldq2XcfKFfNANfeIfEKC8dcCRw==", + "dependencies": { + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", @@ -12768,7 +12790,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -12823,7 +12844,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -12875,7 +12895,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -14304,6 +14323,14 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -14512,7 +14539,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } @@ -14529,7 +14555,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -14706,6 +14731,15 @@ "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", "dev": true }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -19560,6 +19594,15 @@ "node": ">= 0.8" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -19716,7 +19759,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -20133,7 +20175,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -20605,7 +20646,6 @@ "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" @@ -20645,7 +20685,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -22069,7 +22108,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -22366,8 +22404,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/type-check": { "version": "0.4.0", @@ -25693,7 +25730,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -25702,14 +25738,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -29759,6 +29793,15 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, + "awilix": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/awilix/-/awilix-8.0.1.tgz", + "integrity": "sha512-zDSp4R204scvQIDb2GMoWigzXemn0+3AKKIAt543T9v2h7lmoypvkmcx1W/Jet/nm27R1N1AsqrsYVviAR9KrA==", + "requires": { + "camel-case": "^4.1.2", + "fast-glob": "^3.2.12" + } + }, "aws-sdk": { "version": "2.1224.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1224.0.tgz", @@ -29999,7 +30042,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -30117,6 +30159,15 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -31731,7 +31782,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -31744,7 +31794,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -31773,7 +31822,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -31815,7 +31863,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -31992,6 +32039,14 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "generic-interceptor": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/generic-interceptor/-/generic-interceptor-2.2.1.tgz", + "integrity": "sha512-UbUbZguVL8yx7qKewzUgy03aBIouVVxQZztcFNUIVEnGLNM4v2+b9jAKMsm2ldq2XcfKFfNANfeIfEKC8dcCRw==", + "requires": { + "uuid": "^9.0.0" + } + }, "generic-pool": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", @@ -32603,8 +32658,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-finalizationregistry": { "version": "1.0.2", @@ -32641,7 +32695,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -32671,8 +32724,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.7", @@ -33771,6 +33823,14 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "requires": { + "tslib": "^2.0.3" + } + }, "lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -33916,8 +33976,7 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "methods": { "version": "1.1.2", @@ -33928,7 +33987,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -34057,6 +34115,15 @@ "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", "dev": true }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -37661,6 +37728,15 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -37782,8 +37858,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "3.0.0", @@ -38082,8 +38157,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "5.1.1", @@ -38439,8 +38513,7 @@ "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==" }, "rimraf": { "version": "3.0.2", @@ -38464,7 +38537,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -39512,7 +39584,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -39710,8 +39781,7 @@ "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "type-check": { "version": "0.4.0", diff --git a/package.json b/package.json index 17122a3..437a8a1 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,10 @@ "prepare": "husky install" }, "dependencies": { + "awilix": "^8.0.1", "axios": "1.4.0", "express": "^4.18.2", + "generic-interceptor": "^2.2.1", "http-method-enum": "^1.0.0", "http-status-codes": "^2.2.0", "knex": "^2.5.1", diff --git a/src/index.ts b/src/index.ts index 2879422..60bacae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,75 +1,83 @@ import express from "express"; +import type { Express } from "express"; import http from "http"; import { appConfig } from "./shared/config"; import { v4 } from "uuid"; import { StatusCodes } from "http-status-codes"; import { UserRepository } from "./shared/users.repository"; +import type { Knex } from "knex"; import knex from "knex"; import axios from "axios"; +import { asClass, asValue, asFunction } from "awilix"; +import { createContainer } from "awilix"; +import { randomWordApiIntegration, type RandomWordApiIntegration } from "./shared/random-wort-api.integration"; +import type { Route } from "./routes/items-add.get.route"; +import { itemsAddGetRoute } from "./routes/items-add.get.route"; +import { asArray, asProxied } from "./shared/lib/awilix.lib"; +import { errorProxy } from "./shared/proxy/dependency-error-decorator.proxy"; +import { statsDProxy } from "./shared/proxy/dependency-statsd.proxy"; +import { loggerProxy } from "./shared/proxy/dependency-logger.proxy"; +import { ItemRepository } from "./shared/items.repository"; -const app = express(); -const userRepository = new UserRepository(); +export interface Dependencies { + userRepository: UserRepository; + itemRepository: ItemRepository; + randomWordApiIntegration: RandomWordApiIntegration; + app: Express; + database: Knex; + appConfig: typeof appConfig; + routes: Route[]; +} -const database = knex({ - client: "pg", - connection: { - user: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_NAME, - host: process.env.DATABASE_HOST, - port: Number(process.env.DATABASE_PORT), - }, - acquireConnectionTimeout: 30000, +export const container = createContainer().register({ + userRepository: asClass(UserRepository), + itemRepository: asClass(ItemRepository), + appConfig: asValue(appConfig), + app: asValue(express()), + randomWordApiIntegration: asProxied(randomWordApiIntegration, [errorProxy, statsDProxy, loggerProxy]), + database: asFunction((dependencies: Dependencies) => + knex({ + client: "pg", + connection: { + user: dependencies.appConfig.user, + password: dependencies.appConfig.password, + database: dependencies.appConfig.database, + host: dependencies.appConfig.host, + port: dependencies.appConfig.port, + }, + acquireConnectionTimeout: 30000, + }), + ), + routes: asArray([itemsAddGetRoute].map((route) => asFunction(route))), }); -app.use(function (req, res, next) { +container.cradle.routes.forEach((route) => route.loadRoute()); + +container.cradle.app.use(function (req, res, next) { console.log(`App received http request: ${req.method} ${req.url} | ${JSON.stringify(req.params)}`); next(); }); -app.get("/items/add/:itemName/:userId", async (req, res) => { - const itemId = v4(); - const randomWordAxiosResponse = await axios({ - url: "https://random-word-api.herokuapp.com/word", - }); - await database.table("app.items").insert({ - item_id: itemId, - user_id: req.params.userId, - item_name: req.params.itemName, - random_word: randomWordAxiosResponse.data[0], +container.cradle.app.get("/items/remove/:itemId", async (req, res) => { + await container.cradle.itemRepository.deleteOne({ + itemId: req.params.itemId, }); - res.status(StatusCodes.OK).json({ itemId }); -}); - -app.get("/items/remove/:itemId", async (req, res) => { - await database - .table("app.items") - .where({ - item_id: req.params.itemId, - }) - .delete(); res.sendStatus(StatusCodes.OK); }); -app.get("/items/list", async (req, res) => { - const items = await database.table("app.items").select(); +container.cradle.app.get("/items/list", async (req, res) => { + const items = await container.cradle.itemRepository.listAll(); res.status(StatusCodes.OK).json({ - items: items.map((item) => ({ - itemId: item.item_id, - userId: item.user_id, - createdAt: item.created_at, - itemName: item.item_name, - randomWord: item.random_word, - })), + items, }); }); -app.get("/users/add/:userName", async (req, res) => { +container.cradle.app.get("/users/add/:userName", async (req, res) => { const userId = v4(); const randomWordAxiosResponse = await axios({ url: "https://random-word-api.herokuapp.com/word", }); - await userRepository.insertOne({ + await container.cradle.userRepository.insertOne({ userId, userName: req.params.userName, randomWord: randomWordAxiosResponse.data[0], @@ -77,13 +85,13 @@ app.get("/users/add/:userName", async (req, res) => { res.status(StatusCodes.OK).json({ userId }); }); -app.get("/users/list", async (req, res) => { - const users = await userRepository.listAll(); +container.cradle.app.get("/users/list", async (req, res) => { + const users = await container.cradle.userRepository.listAll(); res.status(StatusCodes.OK).json({ users }); }); -app.get("/user-items/list/:userId", async (req, res) => { - const userItems = await database +container.cradle.app.get("/user-items/list/:userId", async (req, res) => { + const userItems = await container.cradle.database .select("*") .from("app.users") .leftJoin("app.items", "users.user_id", "items.user_id") @@ -101,12 +109,12 @@ app.get("/user-items/list/:userId", async (req, res) => { }); }); -app.use(function (req, res, next) { +container.cradle.app.use(function (req, res, next) { console.log(`App responded on http request: <${res.statusCode}> ${req.method} ${req.url}`); next(); }); -app.use([ +container.cradle.app.use([ // eslint-disable-next-line @typescript-eslint/no-unused-vars (err, req, res, next): void => { console.warn(err); @@ -115,5 +123,5 @@ app.use([ ]); http - .createServer(app) + .createServer(container.cradle.app) .listen({ port: appConfig.PORT }, () => console.log(`Server is listening on port: ${appConfig.PORT}`)); diff --git a/src/routes/items-add.get.route.ts b/src/routes/items-add.get.route.ts new file mode 100644 index 0000000..b2357f7 --- /dev/null +++ b/src/routes/items-add.get.route.ts @@ -0,0 +1,22 @@ +import { StatusCodes } from "http-status-codes"; +import type { Dependencies } from ".."; +import { v4 } from "uuid"; + +export interface Route { + loadRoute: () => void; +} + +export const itemsAddGetRoute = (dependencies: Dependencies): Route => ({ + loadRoute: () => + dependencies.app.get("/items/add/:itemName/:userId", async (req, res) => { + const itemId = v4(); + const randomWord = await dependencies.randomWordApiIntegration.getRandomWord(); + await dependencies.itemRepository.insertOne({ + itemId, + userId: req.params.userId, + itemName: req.params.itemName, + randomWord, + }); + res.status(StatusCodes.OK).json({ itemId }); + }), +}); diff --git a/src/shared/config.ts b/src/shared/config.ts index 7084812..3949071 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -5,10 +5,20 @@ const appConfigValidation = zod .object({ NODE_STAGE: zod.string(), PORT: zod.number({ coerce: true }), + user: zod.string(), + password: zod.string(), + database: zod.string(), + host: zod.string(), + port: zod.number({ coerce: true }), }) .safeParse({ NODE_STAGE: process.env.NODE_STAGE, PORT: process.env.PORT, + user: process.env.DATABASE_USER, + password: process.env.DATABASE_PASSWORD, + database: process.env.DATABASE_NAME, + host: process.env.DATABASE_HOST, + port: process.env.DATABASE_PORT, }); if (appConfigValidation.success === false) { diff --git a/src/shared/items.repository.ts b/src/shared/items.repository.ts new file mode 100644 index 0000000..c4353de --- /dev/null +++ b/src/shared/items.repository.ts @@ -0,0 +1,34 @@ +import type { Dependencies } from ".."; + +export class ItemRepository { + public constructor(private dependencies: Dependencies) {} + + public async insertOne(payload: { itemId: string; userId: string; randomWord: string; itemName: string }) { + await this.dependencies.database.table("app.items").insert({ + item_id: payload.itemId, + user_id: payload.userId, + item_name: payload.itemName, + random_word: payload.randomWord, + }); + } + + public async deleteOne(payload: { itemId: string }) { + await this.dependencies.database + .table("app.items") + .where({ + item_id: payload.itemId, + }) + .delete(); + } + + public async listAll() { + const items = await this.dependencies.database.table("app.items").select(); + return items.map((item) => ({ + itemId: item.item_id, + userId: item.user_id, + createdAt: item.created_at, + itemName: item.item_name, + randomWord: item.random_word, + })); + } +} diff --git a/src/shared/lib/awilix.lib.ts b/src/shared/lib/awilix.lib.ts new file mode 100644 index 0000000..e7ea308 --- /dev/null +++ b/src/shared/lib/awilix.lib.ts @@ -0,0 +1,24 @@ +import * as awilix from "awilix"; +import type { BuildResolver } from "awilix"; + +export const asArray = (resolvers: awilix.Resolver[]): awilix.Resolver => ({ + resolve: (container) => resolvers.map((resolver) => container.build(resolver)), +}); +export interface DependencyProxyHandlerOptions { + dependencyName: string; +} + +export const asProxied = any>( + fun: ((...args: any) => any) | (new (...args: any) => any), + proxies: ((options: DependencyProxyHandlerOptions) => ProxyHandler)[], +): BuildResolver> => + awilix.asFunction>( + proxies.reduce( + //@ts-ignore + (target: any, handler: TFunction) => (some: any) => + new Proxy(target(some), handler({ dependencyName: fun.name })) as any, + fun.prototype === undefined + ? fun + : (...classConstructorArgs: any): any => new (fun as new (...args: any) => any)(...classConstructorArgs), + ) as any, + ); diff --git a/src/shared/lib/falso.lib.ts b/src/shared/lib/falso.lib.ts new file mode 100644 index 0000000..e0fef0f --- /dev/null +++ b/src/shared/lib/falso.lib.ts @@ -0,0 +1 @@ +export * as falso from "@ngneat/falso"; diff --git a/src/shared/parser/secure-json.parser.spec.ts b/src/shared/parser/secure-json.parser.spec.ts new file mode 100644 index 0000000..0fec5a5 --- /dev/null +++ b/src/shared/parser/secure-json.parser.spec.ts @@ -0,0 +1,119 @@ +import { falso } from "../lib/falso.lib"; +import { buildReplacementString, getCircularReplacer, getSecureReplacer, js2SecureJson } from "./secure-json.parser"; + +interface Dataset { + fieldKey: string; + fieldValue: any; + object: any; + seen: WeakSet; + reason: "SECURE"; + securedKeys: string[]; +} + +const prepare = ({ + fieldKey = falso.randProductName(), + fieldValue = falso.randProductName(), + object = { [fieldKey]: fieldValue }, + seen = new WeakSet(), + securedKeys = [fieldKey], +}: Partial>) => { + const dataset: Dataset = { + seen, + fieldKey, + fieldValue, + object, + reason: "SECURE", + securedKeys, + }; + const dependencies = { + secureReplacer: jest.fn(getSecureReplacer(dataset.securedKeys)), + circularReplacer: jest.fn(getCircularReplacer()), + }; + const executeSecureJsonStringify = () => + js2SecureJson(dependencies.secureReplacer, dependencies.circularReplacer)(object); + const executeBuildReplacementString = () => + buildReplacementString(dataset.reason, dataset.fieldKey, dataset.fieldValue); + const executeSecureReplacer = () => dependencies.secureReplacer(dataset.fieldKey, dataset.fieldValue); + const executeCircularReplacer = () => dependencies.circularReplacer(dataset.fieldKey, dataset.fieldValue); + return { + dependencies, + dataset, + executeSecureJsonStringify, + executeBuildReplacementString, + executeSecureReplacer, + executeCircularReplacer, + }; +}; + +it("build-replacement-string", () => { + const test = prepare({}); + + const result = test.executeBuildReplacementString(); + + expect(result).toStrictEqual(`_${test.dataset.reason}_<${typeof test.dataset.fieldValue}>${test.dataset.fieldKey}_`); +}); + +it("secure-replacer", () => { + const test = prepare({}); + + const result = test.executeSecureReplacer(); + + expect(result).toStrictEqual(buildReplacementString("SECURE", test.dataset.fieldKey, test.dataset.fieldValue)); +}); + +it("secure-replacer without replacement", () => { + const test = prepare({ securedKeys: [] }); + + const result = test.executeSecureReplacer(); + + expect(result).toStrictEqual(test.dataset.fieldValue); +}); + +it.skip("get-circular-replacer", () => { + const test = prepare({ seen: { has: jest.fn(), add: jest.fn() } as any, fieldValue: {} }); + + const result = test.executeCircularReplacer(); + + expect(test.dataset.seen.has).toBeCalledTimes(1); + expect(test.dataset.seen.add).toBeCalledTimes(1); + expect(result).toStrictEqual(test.dataset.fieldValue); +}); + +it("get-circular-replacer not object", () => { + const test = prepare({ seen: { has: jest.fn(() => false), add: jest.fn() } as any }); + + const result = test.executeCircularReplacer(); + + expect(test.dataset.seen.has).toBeCalledTimes(0); + expect(test.dataset.seen.add).toBeCalledTimes(0); + expect(result).toStrictEqual(test.dataset.fieldValue); +}); + +it.skip("get-circular-replacer already seen", () => { + const test = prepare({ seen: { has: jest.fn(() => true), add: jest.fn() } as any, fieldValue: {} }); + + const result = test.executeCircularReplacer(); + + expect(test.dataset.seen.has).toBeCalledTimes(1); + expect(test.dataset.seen.add).toBeCalledTimes(0); + expect(result).toStrictEqual(buildReplacementString("CIRCULAR", test.dataset.fieldKey, test.dataset.fieldValue)); +}); + +it("secure-json-stringify", () => { + const test = prepare({}); + + test.executeSecureJsonStringify(); + + expect(test.dependencies.secureReplacer).toBeCalledTimes(Object.keys(test.dataset.object).length + 1); +}); + +it("secure-json-stringify circular dependency", () => { + const circular: any = {}; + circular.next = circular; + const test = prepare({ object: circular }); + + test.executeSecureJsonStringify(); + + expect(test.dependencies.secureReplacer).toBeCalledTimes(Object.keys(test.dataset.object).length * 2 + 2); + expect(test.dependencies.circularReplacer).toBeCalledTimes(Object.keys(test.dataset.object).length + 1); +}); diff --git a/src/shared/parser/secure-json.parser.ts b/src/shared/parser/secure-json.parser.ts new file mode 100644 index 0000000..c8c4a13 --- /dev/null +++ b/src/shared/parser/secure-json.parser.ts @@ -0,0 +1,54 @@ +export const buildReplacementString = (reason: "SECURE" | "CIRCULAR", key: string, value: unknown): string => + `_${reason}_<${typeof value}>${key}_`; + +//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value +export function getCircularReplacer(): any { + const ancestors: any[] = []; + return function (key: any, value: any): any { + if (typeof value !== "object" || value === null) { + return value; + } + //@ts-ignore + while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) { + ancestors.pop(); + } + if (ancestors.includes(value)) { + return buildReplacementString("CIRCULAR", key, value); + } + ancestors.push(value); + return value; + }; +} + +const securedKeys: string[] = [ + "token", + "req", + "response", + "externalPassword", + "unsignedToken", + "pesel", + "sellerPassword", + "cookies", + "redirect", + "unsignedToken", + "signedToken", +]; +export const getSecureReplacer = (keys: string[]) => (key: string, value: any) => + keys.includes(key) ? buildReplacementString("SECURE", key, value) : value; + +export const js2SecureJson = (secureReplacer: any, circularReplacer: any) => (object: any) => { + if (typeof object === "string") { + return object; + } + try { + const result = JSON.stringify(object, secureReplacer); + return result; + } catch { + const result = JSON.stringify(object, function (key, value) { + return [circularReplacer, secureReplacer].reduce((acc, replacer) => replacer.bind(this)(key, acc), value); + }); + return result; + } +}; + +export const secureJsonStringify = js2SecureJson(getSecureReplacer(securedKeys), getCircularReplacer()); diff --git a/src/shared/parser/stringify-error.parser.ts b/src/shared/parser/stringify-error.parser.ts new file mode 100644 index 0000000..b98ccdb --- /dev/null +++ b/src/shared/parser/stringify-error.parser.ts @@ -0,0 +1,2 @@ +export const stringifyError = (error: unknown): string => + error instanceof Error ? error.toString() : JSON.stringify(error); diff --git a/src/shared/proxy/dependency-error-decorator.proxy.ts b/src/shared/proxy/dependency-error-decorator.proxy.ts new file mode 100644 index 0000000..945d8c4 --- /dev/null +++ b/src/shared/proxy/dependency-error-decorator.proxy.ts @@ -0,0 +1,24 @@ +import type { OnErrorPayload } from "generic-interceptor"; +import { interceptor } from "generic-interceptor"; +import { stringifyError } from "../parser/stringify-error.parser"; +import type { DependencyProxyHandlerOptions } from "../lib/awilix.lib"; +import { appConfig } from "../config"; + +export const buildMessage = (payload: OnErrorPayload, options: DependencyProxyHandlerOptions): string => + `${options.dependencyName}.${payload.fieldKey} > ${stringifyError(payload.functionError)}`; + +export const errorProxy = (options: DependencyProxyHandlerOptions): ProxyHandler<{ [key: string]: unknown }> => + interceptor({ + passId: false, + onSuccess: () => {}, + onBefore: () => {}, + onNonFunction: () => {}, + onError: (payload) => { + if (appConfig.NODE_STAGE !== "production") { + Object.assign(payload.functionError as Error & { decoration: string }, { + decoration: buildMessage(payload, options), + }); + } + return payload.functionError; + }, + }); diff --git a/src/shared/proxy/dependency-logger.proxy.ts b/src/shared/proxy/dependency-logger.proxy.ts new file mode 100644 index 0000000..e10b164 --- /dev/null +++ b/src/shared/proxy/dependency-logger.proxy.ts @@ -0,0 +1,35 @@ +import type { OnErrorPayload, OnNonFunctionPayload, OnSuccessPayload } from "generic-interceptor"; +import { ProcessingResult } from "generic-interceptor"; +import { interceptor } from "generic-interceptor"; +import { secureJsonStringify } from "../parser/secure-json.parser"; +import type { DependencyProxyHandlerOptions } from "../lib/awilix.lib"; + +export const buildFunctionMessage = (payload: OnErrorPayload | OnSuccessPayload): string => + `(${secureJsonStringify(payload.functionArgs)}) => ${payload.processingStrategy}#${ + payload.processingResult + }:${secureJsonStringify( + payload.processingResult === ProcessingResult.failed ? payload.functionError : payload.functionResult, + )}`; + +export const buildMessage = ( + payload: OnErrorPayload | OnSuccessPayload | OnNonFunctionPayload, + options: DependencyProxyHandlerOptions, +): string => + `${options.dependencyName}.<${payload.fieldValueType}>${String(payload.fieldKey)}${ + "processingResult" in payload ? buildFunctionMessage(payload) : "" + }`; + +export const loggerProxy = (options: DependencyProxyHandlerOptions): ProxyHandler<{ [key: string]: unknown }> => + interceptor({ + passId: false, + onBefore: (payload) => { + console.log(buildMessage(payload, options)); + }, + onError: (payload) => { + console.warn(buildMessage(payload, options), payload.functionError); + }, + onSuccess: (payload) => { + console.log(buildMessage(payload, options)); + }, + onNonFunction: () => {}, + }); diff --git a/src/shared/proxy/dependency-statsd.proxy.ts b/src/shared/proxy/dependency-statsd.proxy.ts new file mode 100644 index 0000000..2a58ba9 --- /dev/null +++ b/src/shared/proxy/dependency-statsd.proxy.ts @@ -0,0 +1,43 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { interceptor } from "generic-interceptor"; +import type { DependencyProxyHandlerOptions } from "../lib/awilix.lib"; +// import { DateTime } from "luxon"; +// import { asyncLocalStorage } from "../middleware/common.middleware"; +// import { statsDFunction } from "../statsd"; + +// const storage: { [key: string]: { startTime: number } } = {}; + +export const statsDProxy = (options: DependencyProxyHandlerOptions): ProxyHandler<{ [key: string]: unknown }> => + interceptor({ + passId: true, + onBefore: (payload) => { + // storage[payload.uniqueAccessId] = { startTime: DateTime.now().toMillis() }; + }, + onError: (payload) => { + // statsDFunction.timing( + // "function_execution", + // DateTime.now().toMillis() - storage[payload.uniqueAccessId].startTime, + // { + // function_name: payload.fieldKey, + // dependency_name: options.dependencyName, + // function_result: "failure", + // request_id: asyncLocalStorage.getStore()?.requestId || "UNKNOWN", + // user_id: asyncLocalStorage.getStore()?.userId || "UNKNOWN", + // }, + // ); + }, + onSuccess: (payload) => { + // statsDFunction.timing( + // "function_execution", + // DateTime.now().toMillis() - storage[payload.uniqueAccessId].startTime, + // { + // function_name: payload.fieldKey, + // dependency_name: options.dependencyName, + // function_result: "succeed", + // request_id: asyncLocalStorage.getStore()?.requestId || "UNKNOWN", + // user_id: asyncLocalStorage.getStore()?.userId || "UNKNOWN", + // }, + // ); + }, + onNonFunction: () => {}, + }); diff --git a/src/shared/random-wort-api.integration.ts b/src/shared/random-wort-api.integration.ts new file mode 100644 index 0000000..18e76a9 --- /dev/null +++ b/src/shared/random-wort-api.integration.ts @@ -0,0 +1,13 @@ +import axios from "axios"; + +export interface RandomWordApiIntegration { + getRandomWord: () => Promise; +} +export const randomWordApiIntegration = (): RandomWordApiIntegration => ({ + getRandomWord: async () => { + const randomWordAxiosResponse = await axios({ + url: "https://random-word-api.herokuapp.com/word", + }); + return randomWordAxiosResponse.data[0]; + }, +}); diff --git a/src/shared/users.repository.ts b/src/shared/users.repository.ts index 2b12d5c..0175645 100644 --- a/src/shared/users.repository.ts +++ b/src/shared/users.repository.ts @@ -1,25 +1,10 @@ -import type { Knex } from "knex"; -import knex from "knex"; +import type { Dependencies } from ".."; export class UserRepository { - private database: Knex; - - public constructor() { - this.database = knex({ - client: "pg", - connection: { - user: process.env.DATABASE_USER, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_NAME, - host: process.env.DATABASE_HOST, - port: Number(process.env.DATABASE_PORT), - }, - acquireConnectionTimeout: 30000, - }); - } + public constructor(private dependencies: Dependencies) {} public async insertOne(payload: { userId: string; userName: string; randomWord: string }) { - await this.database.table("app.users").insert({ + await this.dependencies.database.table("app.users").insert({ user_id: payload.userId, user_name: payload.userName, random_word: payload.randomWord, @@ -27,7 +12,7 @@ export class UserRepository { } public async deleteOne(payload: { userId: string }) { - await this.database + await this.dependencies.database .table("app.users") .where({ user_id: payload.userId, @@ -36,7 +21,7 @@ export class UserRepository { } public async listAll() { - const users = await this.database.table("app.users").select(); + const users = await this.dependencies.database.table("app.users").select(); return users.map((user) => ({ userId: user.user_id, userName: user.user_name,