diff --git a/.env.example b/.env.example index 96d43fa0..a5605cb9 100644 --- a/.env.example +++ b/.env.example @@ -28,4 +28,13 @@ HUB_SPOT_ACCESS_TOKEN= HUB_SPOT_DEAL_OWNER_ID= # We should disable it on local instances to avoid deal duplicates # TODO: remove it after migration from Firebase RDB to SQL -HUB_SPOT_DISABLED=true \ No newline at end of file +HUB_SPOT_DISABLED=true + +# Netvisor +NETVISOR_BASE_URL=https://isvapi.netvisor.fi/netvisor/ +NETVISOR_ORG_ID= +NETVISOR_CUSTOMER_ID= +NETVISOR_CUSTOMER_KEY= +NETVISOR_PARTNER_ID= +NETVISOR_PARTNER_KEY= +NETVISOR_SENDER= \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 36197b2a..576aa452 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,7 @@ const initialRules = { 'warn', { ignore: ['eslint-enable'] }, ], // we don't need to comment why we used "eslint-enable" + 'eslint-comments/disable-enable-pair': 'off', 'prefer-arrow-callback': 'error', 'arrow-parens': ['error', 'always'], 'quote-props': ['error', 'consistent-as-needed'], @@ -106,6 +107,7 @@ const importSortOrderRule = { }; const importRules = { + 'import/extensions': 'off', // if error then throws error on $module/???/module 'no-duplicate-imports': 'error', // imports from the same source must be in one record 'no-restricted-imports': [ 'error', @@ -115,6 +117,10 @@ const importRules = { group: ['lodash', '!lodash/'], // disallow imports from 'lodash' directly message: "Please use 'lodash/*' instead.", }, + { + group: ['./module'], + message: 'Please import modules directly.', + }, ], }, ], @@ -356,11 +362,11 @@ const override = { }, }, importExtensions: { - files: ['src/*.ts', "src/**/*.ts"], + files: ['src/*.ts', 'src/**/*.ts'], rules: { - "import/extensions": "off", + 'import/extensions': 'off', }, - } + }, }; module.exports = { diff --git a/cspell.config.yaml b/cspell.config.yaml index 98d255c8..2b169312 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -1,12 +1,23 @@ words: + # Common - 'iban' - - 'netvisor' - 'signin' - 'signup' - - 'xlink' - 'workareas' - 'unattach' + # Uuid lib - 'uuidv' - 'uuidv4' + # Hubspot - 'appspot' - 'hubspot' + # Netvisor + - 'netvisor' + # Netvisor headers + - 'Organisation' + - 'TRANSID' + # Netvisor query parameters + - 'paymentstatus' + - 'lastmodifiedstart' + # Netvisor return object field + - 'purchaseinvoicelist' diff --git a/env.d.ts b/env.d.ts index d8e11f2e..66492813 100644 --- a/env.d.ts +++ b/env.d.ts @@ -34,6 +34,15 @@ declare global { HUB_SPOT_ACCESS_TOKEN: string; HUB_SPOT_DEAL_OWNER_ID: string; HUB_SPOT_DISABLED: 'true' | 'false'; + + // Netvisor + NETVISOR_BASE_URL: string; + NETVISOR_ORG_ID: string; + NETVISOR_CUSTOMER_ID: string; + NETVISOR_CUSTOMER_KEY: string; + NETVISOR_PARTNER_ID: string; + NETVISOR_PARTNER_KEY: string; + NETVISOR_SENDER: string; } } } diff --git a/package-lock.json b/package-lock.json index 75eb1a22..42c8cf0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^10.2.0", "@nestjs/platform-express": "^9.0.0", + "@nestjs/schedule": "^3.0.4", "apollo-server-express": "^3.11.1", "body-parser": "^1.20.2", "dataloader": "^2.2.2", @@ -25,14 +26,15 @@ "firebase-admin": "^11.10.1", "graphql": "^16.6.0", "graphql-subscriptions": "^2.0.0", - "hero24-types": "^0.2.2", + "hero24-types": "^1.4.1", "joi": "^17.8.3", "lodash": "^4.17.21", "moment": "^2.29.4", "precision": "^1.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "xml2js": "^0.6.2" }, "devDependencies": { "@cspell/eslint-plugin": "^7.3.6", @@ -45,6 +47,7 @@ "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@types/uuid": "^9.0.2", + "@types/xml2js": "^0.4.12", "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.6.0", "eslint": "^8.48.0", @@ -68,7 +71,9 @@ "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", - "typescript": "^4.7.4" + "typescript": "^5.2.2", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" }, "engines": { "node": "^18.17", @@ -1479,6 +1484,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3120,6 +3134,19 @@ "node": ">=6" } }, + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@nestjs/cli/node_modules/webpack": { "version": "5.82.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz", @@ -3351,6 +3378,32 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" }, + "node_modules/@nestjs/schedule": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-3.0.4.tgz", + "integrity": "sha512-uFJpuZsXfpvgx2y7/KrIZW9e1L68TLiwRodZ6+Gc8xqQiHSUzAVn+9F4YMxWFlHITZvvkjWziUFgRNCitDcTZQ==", + "dependencies": { + "cron": "2.4.3", + "uuid": "9.0.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.12" + } + }, + "node_modules/@nestjs/schedule/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@nestjs/schematics": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-9.2.0.tgz", @@ -3839,6 +3892,11 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "node_modules/@types/luxon": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.2.tgz", + "integrity": "sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==" + }, "node_modules/@types/markdown-it": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", @@ -3975,6 +4033,15 @@ "integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==", "dev": true }, + "node_modules/@types/xml2js": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.12.tgz", + "integrity": "sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -4466,6 +4533,50 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5897,6 +6008,20 @@ "node": ">=0.8" } }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6175,6 +6300,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.3.tgz", + "integrity": "sha512-YBvExkQYF7w0PxyeFLRyr817YVDhGxaCi5/uRRMqa4aWD3IFKRd+uNbpW1VWMdqQy8PZ7CElc+accXJcauPKzQ==", + "dependencies": { + "@types/luxon": "~3.3.0", + "luxon": "~3.3.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6939,6 +7073,18 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/envinfo": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8083,9 +8229,9 @@ "optional": true }, "node_modules/fast-xml-parser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", - "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.1.tgz", + "integrity": "sha512-viVv3xb8D+SiS1W4cv4tva3bni08kAkx0gQnWrykMM8nXPc1FxqZPU00dCEVjkiCg4HoXd2jC4x29Nzg/l2DAA==", "funding": [ { "type": "paypal", @@ -8104,6 +8250,15 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -9216,9 +9371,9 @@ } }, "node_modules/hero24-types": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/hero24-types/-/hero24-types-0.2.2.tgz", - "integrity": "sha512-ENvkafSLTWGdp3ZA37GNWpahPErvrBPQObRplwbE+t5VJY68vbYaIGnvO5HcKIzz0wkNhym7TSX83OAzdMliFA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/hero24-types/-/hero24-types-1.4.1.tgz", + "integrity": "sha512-k4jp99/hBuzp0AtVi/GVNMg7keFA58zK8FOXMx0d9z0yd/FZUWdYdWBRBy6bU8TjNZlfkR0zuaCMmj1MkJjEzg==" }, "node_modules/hexoid": { "version": "1.0.0", @@ -9809,6 +9964,18 @@ "node": ">=8" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -10003,6 +10170,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -11024,6 +11200,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/klaw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", @@ -12041,6 +12226,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" }, + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, "node_modules/macos-release": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", @@ -13996,6 +14189,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -14157,6 +14355,18 @@ "sha.js": "bin.js" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15289,16 +15499,16 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uc.micro": { @@ -15576,7 +15786,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "dev": true, - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -15619,6 +15828,94 @@ } } }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-cli/node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-cli/node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/webpack-node-externals": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", @@ -15642,7 +15939,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -15656,7 +15952,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -15798,6 +16093,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, "node_modules/windows-release": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", @@ -15985,6 +16286,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", @@ -17237,6 +17558,12 @@ } } }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -18527,6 +18854,12 @@ "strip-bom": "^3.0.0" } }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, "webpack": { "version": "5.82.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz", @@ -18655,6 +18988,22 @@ } } }, + "@nestjs/schedule": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-3.0.4.tgz", + "integrity": "sha512-uFJpuZsXfpvgx2y7/KrIZW9e1L68TLiwRodZ6+Gc8xqQiHSUzAVn+9F4YMxWFlHITZvvkjWziUFgRNCitDcTZQ==", + "requires": { + "cron": "2.4.3", + "uuid": "9.0.1" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, "@nestjs/schematics": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-9.2.0.tgz", @@ -19098,6 +19447,11 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "@types/luxon": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.2.tgz", + "integrity": "sha512-l5cpE57br4BIjK+9BSkFBOsWtwv6J9bJpC7gdXIzZyI0vuKvNTk0wZZrkQxMGsUAuGW9+WMNWF2IJMD7br2yeQ==" + }, "@types/markdown-it": { "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", @@ -19233,6 +19587,15 @@ "integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==", "dev": true }, + "@types/xml2js": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.12.tgz", + "integrity": "sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", @@ -19578,6 +19941,27 @@ "@xtuc/long": "4.2.2" } }, + "@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "requires": {} + }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -20623,6 +21007,17 @@ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -20859,6 +21254,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cron": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.3.tgz", + "integrity": "sha512-YBvExkQYF7w0PxyeFLRyr817YVDhGxaCi5/uRRMqa4aWD3IFKRd+uNbpW1VWMdqQy8PZ7CElc+accXJcauPKzQ==", + "requires": { + "@types/luxon": "~3.3.0", + "luxon": "~3.3.0" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -21402,6 +21806,12 @@ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", "optional": true }, + "envinfo": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -22293,14 +22703,20 @@ "optional": true }, "fast-xml-parser": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", - "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.1.tgz", + "integrity": "sha512-viVv3xb8D+SiS1W4cv4tva3bni08kAkx0gQnWrykMM8nXPc1FxqZPU00dCEVjkiCg4HoXd2jC4x29Nzg/l2DAA==", "optional": true, "requires": { "strnum": "^1.0.5" } }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -23134,9 +23550,9 @@ } }, "hero24-types": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/hero24-types/-/hero24-types-0.2.2.tgz", - "integrity": "sha512-ENvkafSLTWGdp3ZA37GNWpahPErvrBPQObRplwbE+t5VJY68vbYaIGnvO5HcKIzz0wkNhym7TSX83OAzdMliFA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/hero24-types/-/hero24-types-1.4.1.tgz", + "integrity": "sha512-k4jp99/hBuzp0AtVi/GVNMg7keFA58zK8FOXMx0d9z0yd/FZUWdYdWBRBy6bU8TjNZlfkR0zuaCMmj1MkJjEzg==" }, "hexoid": { "version": "1.0.0", @@ -23531,6 +23947,15 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -23664,6 +24089,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -24488,6 +24919,12 @@ "json-buffer": "3.0.1" } }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, "klaw": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", @@ -25235,6 +25672,11 @@ } } }, + "luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==" + }, "macos-release": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", @@ -26668,6 +27110,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, "schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -26801,6 +27248,15 @@ "safe-buffer": "^5.0.1" } }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -27612,9 +28068,9 @@ } }, "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true }, "uc.micro": { @@ -27828,7 +28284,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", "dev": true, - "peer": true, "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -27861,7 +28316,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "peer": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -27871,11 +28325,64 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, - "peer": true + "requires": { + "resolve": "^1.20.0" + } } } }, + "webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, "webpack-node-externals": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", @@ -27992,6 +28499,12 @@ "has-tostringtag": "^1.0.0" } }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, "windows-release": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", @@ -28121,6 +28634,20 @@ "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", "dev": true }, + "xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", diff --git a/package.json b/package.json index e4e27a88..efd5475a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@nestjs/core": "^9.0.0", "@nestjs/graphql": "^10.2.0", "@nestjs/platform-express": "^9.0.0", + "@nestjs/schedule": "^3.0.4", "apollo-server-express": "^3.11.1", "body-parser": "^1.20.2", "dataloader": "^2.2.2", @@ -49,14 +50,15 @@ "firebase-admin": "^11.10.1", "graphql": "^16.6.0", "graphql-subscriptions": "^2.0.0", - "hero24-types": "^0.2.2", + "hero24-types": "^1.4.1", "joi": "^17.8.3", "lodash": "^4.17.21", "moment": "^2.29.4", "precision": "^1.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "xml2js": "^0.6.2" }, "devDependencies": { "@cspell/eslint-plugin": "^7.3.6", @@ -69,6 +71,7 @@ "@types/node": "18.11.18", "@types/supertest": "^2.0.11", "@types/uuid": "^9.0.2", + "@types/xml2js": "^0.4.12", "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.6.0", "eslint": "^8.48.0", @@ -92,7 +95,9 @@ "ts-loader": "^9.2.3", "ts-node": "^10.0.0", "tsconfig-paths": "4.1.1", - "typescript": "^4.7.4" + "typescript": "^5.2.2", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" }, "jest": { "moduleFileExtensions": [ diff --git a/src/app.module.ts b/src/app.module.ts index ea6b1157..7f6ed1de 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,6 +2,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { GraphQLModule } from '@nestjs/graphql'; +import { ScheduleModule } from '@nestjs/schedule'; import { AppResolver } from './app.resolver'; import { GraphQlBaseContext, GraphQlConnectionParams } from './app.types'; @@ -19,7 +20,6 @@ import { FirebaseModule } from './modules/firebase/firebase.module'; import { GraphQlContextManagerModule } from './modules/graphql-context-manager/graphql-context-manager.module'; import { GraphQlContextManagerService } from './modules/graphql-context-manager/graphql-context-manager.service'; import { GraphQlPubsubModule } from './modules/graphql-pubsub/graphql-pubsub.module'; -import { HeroPortfolioModule } from './modules/hero-portfolio'; import { ImageModule } from './modules/image/image.module'; import { NewsModule } from './modules/news/news.module'; import { OfferModule } from './modules/offer/offer.module'; @@ -31,6 +31,11 @@ import { SubscriberModule } from './modules/subscriber/subscriber.module'; import { UserModule } from './modules/user/user.module'; import { UserMergeModule } from './modules/user-merge/user-merge.module'; +import { CryptoModule } from '$modules/crypto/module'; +import { HeroPortfolioModule } from '$modules/hero-portfolio/module'; +import { NetvisorModule } from '$modules/netvisor/module'; +import { Xml2JsModule } from '$modules/xml2js/module'; + @Module({ imports: [ ConfigModule.forRoot({ @@ -59,6 +64,7 @@ import { UserMergeModule } from './modules/user-merge/user-merge.module'; graphQLManagerService.createContext(ctx), }), }), + ScheduleModule.forRoot(), GraphQlPubsubModule, FirebaseModule, UserModule, @@ -77,6 +83,9 @@ import { UserMergeModule } from './modules/user-merge/user-merge.module'; FeeModule, ImageModule, HeroPortfolioModule, + NetvisorModule, + CryptoModule, + Xml2JsModule, ], providers: [AppResolver], }) diff --git a/src/common/utils/custom-fetcher.ts b/src/common/utils/custom-fetcher.ts new file mode 100644 index 00000000..973e610b --- /dev/null +++ b/src/common/utils/custom-fetcher.ts @@ -0,0 +1,27 @@ +export class CustomFetcher { + private readonly url: URL; + + private readonly searchParams: URLSearchParams; + + constructor(baseName: string, path: string, params: Record) { + this.url = new URL(path, baseName); + this.searchParams = new URLSearchParams(params); + } + + getStringifiedUrl(): string { + return `${this.url.origin}${ + this.url.pathname + }?${this.searchParams.toString()}`; + } + + getUrl(): URL { + return new URL(this.getStringifiedUrl()); + } + + async get(headers: HeadersInit): Promise { + return fetch(this.getUrl(), { + headers, + method: 'GET', + }); + } +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts new file mode 100644 index 00000000..91438219 --- /dev/null +++ b/src/common/utils/index.ts @@ -0,0 +1 @@ +export * from './custom-fetcher'; diff --git a/src/config/index.ts b/src/config/index.ts index 7aeab62d..35063ddd 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -4,6 +4,7 @@ import * as Joi from 'joi'; import app, { appValidationSchema } from './app'; import firebase, { firebaseValidationSchema } from './firebase'; import hubSpot, { hubSpotValidationSchema } from './hubSpot'; +import netvisor from './netvisor'; export const configValidationSchema = Joi.object() .concat(appValidationSchema) @@ -14,6 +15,7 @@ const getConfig = () => ({ app: app(), firebase: firebase(), hubSpot: hubSpot(), + netvisor: netvisor(), }); const registerConfig = registerAs('config', getConfig); diff --git a/src/config/netvisor.ts b/src/config/netvisor.ts new file mode 100644 index 00000000..a5b30273 --- /dev/null +++ b/src/config/netvisor.ts @@ -0,0 +1,21 @@ +import * as Joi from 'joi'; + +export const hubSpotValidationSchema = Joi.object({ + NETVISOR_BASE_URL: Joi.string().required(), + NETVISOR_ORG_ID: Joi.string().required(), + NETVISOR_CUSTOMER_ID: Joi.string().required(), + NETVISOR_CUSTOMER_KEY: Joi.string().required(), + NETVISOR_PARTNER_ID: Joi.string().required(), + NETVISOR_PARTNER_KEY: Joi.string().required(), + NETVISOR_SENDER: Joi.string().required(), +}); + +export default () => ({ + baseUrl: process.env.NETVISOR_BASE_URL, + orgId: process.env.NETVISOR_ORG_ID, + customerId: process.env.NETVISOR_CUSTOMER_ID, + customerKey: process.env.NETVISOR_CUSTOMER_KEY, + partnerId: process.env.NETVISOR_PARTNER_ID, + partnerKey: process.env.NETVISOR_PARTNER_KEY, + sender: process.env.NETVISOR_SENDER, +}); diff --git a/src/modules/crypto/index.ts b/src/modules/crypto/index.ts new file mode 100644 index 00000000..f78beabc --- /dev/null +++ b/src/modules/crypto/index.ts @@ -0,0 +1 @@ +export * from './service'; diff --git a/src/modules/crypto/module.ts b/src/modules/crypto/module.ts new file mode 100644 index 00000000..11ded4e4 --- /dev/null +++ b/src/modules/crypto/module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { CryptoService } from './service'; + +@Module({ + providers: [CryptoService], + exports: [CryptoService], +}) +export class CryptoModule {} diff --git a/src/modules/crypto/service.ts b/src/modules/crypto/service.ts new file mode 100644 index 00000000..7af97f77 --- /dev/null +++ b/src/modules/crypto/service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; +import { createHash } from 'crypto'; + +@Injectable() +export class CryptoService { + hashWithSha256(text: string): string { + return createHash('sha256').update(text).digest('hex'); + } +} diff --git a/src/modules/hero-portfolio/index.ts b/src/modules/hero-portfolio/index.ts index e48aff7e..02acd184 100644 --- a/src/modules/hero-portfolio/index.ts +++ b/src/modules/hero-portfolio/index.ts @@ -1,6 +1,5 @@ export * from './constants'; export * from './dto'; -export * from './module'; export * from './resolver'; export * from './service'; export * from './sorters'; diff --git a/src/modules/netvisor/constants.ts b/src/modules/netvisor/constants.ts new file mode 100644 index 00000000..ea50fe4c --- /dev/null +++ b/src/modules/netvisor/constants.ts @@ -0,0 +1,22 @@ +import { + PaymentStatus, + PurchaseInvoiceListKeys, + PurchaseInvoiceListParameters, + ScheduleFetchDay, +} from './types'; + +export const UPDATE_PAID_STATUS_CRON_TIME = '0 0 12 * * 2,5'; // Tuesday and Friday at 12:00 + +export const purchaseInvoiceListParameters = { + [PurchaseInvoiceListKeys.PAYMENT_STATUS]: PaymentStatus.PAID, +} satisfies PurchaseInvoiceListParameters; + +export const TUESDAY = 2; +export const FRIDAY = 5; +export const FROM_TUESDAY_TO_FRIDAY = 4; +export const FROM_FRIDAY_TO_TUESDAY = 3; + +export const scheduleFetchDays: ScheduleFetchDay[] = [ + { day: FRIDAY, transform: FROM_FRIDAY_TO_TUESDAY }, + { day: TUESDAY, transform: FROM_TUESDAY_TO_FRIDAY }, +]; diff --git a/src/modules/netvisor/fetcher.ts b/src/modules/netvisor/fetcher.ts new file mode 100644 index 00000000..7ca38a12 --- /dev/null +++ b/src/modules/netvisor/fetcher.ts @@ -0,0 +1,117 @@ +import { Injectable } from '@nestjs/common'; + +import { Xml2JsService } from '../xml2js/service'; + +import { purchaseInvoiceListParameters } from './constants'; +import { + NetvisorBaseHeaders, + NetvisorEndpoint, + NetvisorHeaders, + NetvisorHeadersName, + NetvisorPurchaseInvoiceListResponse, +} from './types'; + +import { ConfigType } from '$config'; +import { Config } from '$decorator'; +import { CryptoService } from '$modules/crypto/service'; +import { CustomFetcher } from '$utils'; + +@Injectable() +export class NetvisorFetcher { + private readonly baseHeaders: NetvisorBaseHeaders; + + private readonly baseUrl: string; + + private readonly config: ConfigType['netvisor']; + + constructor( + @Config() config: ConfigType, + private readonly cryptoService: CryptoService, + private readonly xml2JsService: Xml2JsService, + ) { + this.config = config.netvisor; + + this.baseUrl = this.config.baseUrl; + this.baseHeaders = { + [NetvisorHeadersName.CONTENT_TYPE]: 'text/plain', + [NetvisorHeadersName.SENDER]: this.config.sender, + [NetvisorHeadersName.CUSTOMER_ID]: this.config.customerId, + [NetvisorHeadersName.PARTNER_ID]: this.config.partnerId, + [NetvisorHeadersName.LANGUAGE]: 'FI', + [NetvisorHeadersName.ORGANISATION_ID]: this.config.orgId, + [NetvisorHeadersName.ALGORITHM]: 'SHA256', + }; + } + + private createMac( + url: string, + timestamp: number, + transactionId: string, + ): string { + const verifiedString = [ + url, + this.config.sender, + this.config.customerId, + timestamp, + 'FI', + this.config.orgId, + transactionId, + this.config.customerKey, + this.config.partnerKey, + ].join('&'); + + return this.cryptoService.hashWithSha256(verifiedString); + } + + private createFullHeaders(url: string): NetvisorHeaders { + const timestamp = Date.now(); + const transactionId = `TRANSID${timestamp}`; + + const mac = this.createMac(url, timestamp, transactionId); + + const headers: NetvisorHeaders = { + ...this.baseHeaders, + [NetvisorHeadersName.TRANSACTION_ID]: transactionId, + [NetvisorHeadersName.TIMESTAMP]: String(timestamp), + [NetvisorHeadersName.MAC]: mac, + }; + + return headers; + } + + async fetchPurchaseInvoiceList(startDate: string): Promise { + const fetcher = new CustomFetcher( + this.baseUrl, + NetvisorEndpoint.PURCHASE_INVOICE_LIST, + { + ...purchaseInvoiceListParameters, + lastmodifiedstart: startDate, + }, + ); + + const headers = this.createFullHeaders(fetcher.getStringifiedUrl()); + + try { + const response = await fetcher.get(headers); + + const xmlString = await response.text(); + + const data = + await this.xml2JsService.createObjectFromXml( + xmlString, + ); + + if (Array.isArray(data.Root.PurchaseInvoiceList)) { + return data.Root.PurchaseInvoiceList[0].PurchaseInvoice.map( + (invoice) => { + return invoice.NetvisorKey[0]; + }, + ); + } + + return []; + } catch (error) { + console.error(error); + } + } +} diff --git a/src/modules/netvisor/index.ts b/src/modules/netvisor/index.ts new file mode 100644 index 00000000..03975d49 --- /dev/null +++ b/src/modules/netvisor/index.ts @@ -0,0 +1,5 @@ +export * from './types'; +export * from './utils'; +export * from './fetcher'; +export * from './schedule'; +export * from './constants'; diff --git a/src/modules/netvisor/module.ts b/src/modules/netvisor/module.ts new file mode 100644 index 00000000..8d616feb --- /dev/null +++ b/src/modules/netvisor/module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; + +import { NetvisorFetcher } from './fetcher'; +import { NetvisorSchedule } from './schedule'; + +import { CryptoModule } from '$modules/crypto/module'; +import { OfferModule } from '$modules/offer/offer.module'; +import { OfferRequestModule } from '$modules/offer-request/offer-request.module'; +import { Xml2JsModule } from '$modules/xml2js/module'; + +@Module({ + imports: [OfferModule, OfferRequestModule, CryptoModule, Xml2JsModule], + providers: [NetvisorSchedule, NetvisorFetcher], + exports: [], +}) +export class NetvisorModule {} diff --git a/src/modules/netvisor/schedule.ts b/src/modules/netvisor/schedule.ts new file mode 100644 index 00000000..a051ae30 --- /dev/null +++ b/src/modules/netvisor/schedule.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { Cron } from '@nestjs/schedule'; +import { PaidStatus } from 'hero24-types'; + +import { UPDATE_PAID_STATUS_CRON_TIME } from './constants'; +import { NetvisorFetcher } from './fetcher'; +import { getScheduleFetchDate } from './utils'; + +import { OfferService } from '$modules/offer/services/offer.service'; +import { OfferRequestService } from '$modules/offer-request/offer-request.service'; + +@Injectable() +export class NetvisorSchedule { + constructor( + private readonly netvisorFetcher: NetvisorFetcher, + private readonly offerService: OfferService, + private readonly offerRequestService: OfferRequestService, + ) {} + + @Cron(UPDATE_PAID_STATUS_CRON_TIME) + async updatePaidStatus(): Promise { + const startDate = getScheduleFetchDate(); + + if (!startDate) { + return; + } + + const paidInvoices = await this.netvisorFetcher.fetchPurchaseInvoiceList( + startDate, + ); + + if (!paidInvoices) { + return; + } + + const offers = await this.offerService.getOffersByInvoiceIds(paidInvoices); + + offers.forEach((offer) => { + const { offerRequestId } = offer.data.initial; + + void this.offerRequestService.updatePaidStatus( + offerRequestId, + PaidStatus.PAID, + ); + }); + } +} diff --git a/src/modules/netvisor/types/base-headers.ts b/src/modules/netvisor/types/base-headers.ts new file mode 100644 index 00000000..28e0a9bb --- /dev/null +++ b/src/modules/netvisor/types/base-headers.ts @@ -0,0 +1,13 @@ +import { NetvisorHeaders } from './headers'; +import { NetvisorHeadersName } from './headers-name'; + +export type NetvisorBaseHeaders = Pick< + NetvisorHeaders, + | NetvisorHeadersName.CONTENT_TYPE + | NetvisorHeadersName.CUSTOMER_ID + | NetvisorHeadersName.ALGORITHM + | NetvisorHeadersName.PARTNER_ID + | NetvisorHeadersName.SENDER + | NetvisorHeadersName.LANGUAGE + | NetvisorHeadersName.ORGANISATION_ID +>; diff --git a/src/modules/netvisor/types/endpoint.ts b/src/modules/netvisor/types/endpoint.ts new file mode 100644 index 00000000..0e708858 --- /dev/null +++ b/src/modules/netvisor/types/endpoint.ts @@ -0,0 +1,3 @@ +export enum NetvisorEndpoint { + PURCHASE_INVOICE_LIST = 'purchaseinvoicelist.nv', +} diff --git a/src/modules/netvisor/types/headers-name.ts b/src/modules/netvisor/types/headers-name.ts new file mode 100644 index 00000000..36dc84e6 --- /dev/null +++ b/src/modules/netvisor/types/headers-name.ts @@ -0,0 +1,12 @@ +export enum NetvisorHeadersName { + CONTENT_TYPE = 'Content-Type', + CUSTOMER_ID = 'X-Netvisor-Authentication-CustomerId', + MAC = 'X-Netvisor-Authentication-MAC', + ALGORITHM = 'X-Netvisor-Authentication-MACHashCalculationAlgorithm', + PARTNER_ID = 'X-Netvisor-Authentication-PartnerId', + SENDER = 'X-Netvisor-Authentication-Sender', + TIMESTAMP = 'X-Netvisor-Authentication-Timestamp', + TRANSACTION_ID = 'X-Netvisor-Authentication-TransactionId', + LANGUAGE = 'X-Netvisor-Interface-Language', + ORGANISATION_ID = 'X-Netvisor-Organisation-Id', +} diff --git a/src/modules/netvisor/types/headers.ts b/src/modules/netvisor/types/headers.ts new file mode 100644 index 00000000..9c678e33 --- /dev/null +++ b/src/modules/netvisor/types/headers.ts @@ -0,0 +1,14 @@ +import { NetvisorHeadersName } from './headers-name'; + +export interface NetvisorHeaders extends Record { + [NetvisorHeadersName.CONTENT_TYPE]: string; + [NetvisorHeadersName.CUSTOMER_ID]: string; + [NetvisorHeadersName.MAC]: string; + [NetvisorHeadersName.ALGORITHM]: string; + [NetvisorHeadersName.PARTNER_ID]: string; + [NetvisorHeadersName.SENDER]: string; + [NetvisorHeadersName.TIMESTAMP]: string; + [NetvisorHeadersName.TRANSACTION_ID]: string; + [NetvisorHeadersName.LANGUAGE]: string; + [NetvisorHeadersName.ORGANISATION_ID]: string; +} diff --git a/src/modules/netvisor/types/index.ts b/src/modules/netvisor/types/index.ts new file mode 100644 index 00000000..b839ac79 --- /dev/null +++ b/src/modules/netvisor/types/index.ts @@ -0,0 +1,10 @@ +export * from './base-headers'; +export * from './endpoint'; +export * from './headers'; +export * from './headers-name'; +export * from './purchase-invoice-list-response'; +export * from './purchase-invoice'; +export * from './schedule-fetch-day'; +export * from './purchase-invoice-list-keys'; +export * from './purchase-invoice-list-parameters'; +export * from './payment-status'; diff --git a/src/modules/netvisor/types/payment-status.ts b/src/modules/netvisor/types/payment-status.ts new file mode 100644 index 00000000..99eeaf89 --- /dev/null +++ b/src/modules/netvisor/types/payment-status.ts @@ -0,0 +1,4 @@ +export enum PaymentStatus { + PAID = 'paid', + UNPAID = 'unpaid', +} diff --git a/src/modules/netvisor/types/purchase-invoice-list-keys.ts b/src/modules/netvisor/types/purchase-invoice-list-keys.ts new file mode 100644 index 00000000..c59c097d --- /dev/null +++ b/src/modules/netvisor/types/purchase-invoice-list-keys.ts @@ -0,0 +1,3 @@ +export enum PurchaseInvoiceListKeys { + PAYMENT_STATUS = 'paymentstatus', +} diff --git a/src/modules/netvisor/types/purchase-invoice-list-parameters.ts b/src/modules/netvisor/types/purchase-invoice-list-parameters.ts new file mode 100644 index 00000000..c881a263 --- /dev/null +++ b/src/modules/netvisor/types/purchase-invoice-list-parameters.ts @@ -0,0 +1,6 @@ +import { PaymentStatus } from './payment-status'; +import { PurchaseInvoiceListKeys } from './purchase-invoice-list-keys'; + +export interface PurchaseInvoiceListParameters { + [PurchaseInvoiceListKeys.PAYMENT_STATUS]: PaymentStatus; +} diff --git a/src/modules/netvisor/types/purchase-invoice-list-response.ts b/src/modules/netvisor/types/purchase-invoice-list-response.ts new file mode 100644 index 00000000..c3b7f09f --- /dev/null +++ b/src/modules/netvisor/types/purchase-invoice-list-response.ts @@ -0,0 +1,11 @@ +import { NetvisorPurchaseInvoice } from './purchase-invoice'; + +export interface NetvisorPurchaseInvoiceListResponse { + Root: { + PurchaseInvoiceList: [ + { + PurchaseInvoice: NetvisorPurchaseInvoice[]; + }, + ]; + }; +} diff --git a/src/modules/netvisor/types/purchase-invoice.ts b/src/modules/netvisor/types/purchase-invoice.ts new file mode 100644 index 00000000..09de42de --- /dev/null +++ b/src/modules/netvisor/types/purchase-invoice.ts @@ -0,0 +1,11 @@ +export interface NetvisorPurchaseInvoice { + InvoiceDate: [{ $: { format: string }; _: string }]; + InvoiceNumber: [string]; + NetvisorKey: [string]; + OpenSum: [string]; + Payments: [string]; + Sum: [string]; + Uri: [string]; + Vendor: [string]; + VendorOrganizationIdentifier: [string]; +} diff --git a/src/modules/netvisor/types/schedule-fetch-day.ts b/src/modules/netvisor/types/schedule-fetch-day.ts new file mode 100644 index 00000000..36ab285d --- /dev/null +++ b/src/modules/netvisor/types/schedule-fetch-day.ts @@ -0,0 +1,4 @@ +export interface ScheduleFetchDay { + day: number; + transform: number; +} diff --git a/src/modules/netvisor/utils/get-previous-fetch-date.ts b/src/modules/netvisor/utils/get-previous-fetch-date.ts new file mode 100644 index 00000000..9c35cb86 --- /dev/null +++ b/src/modules/netvisor/utils/get-previous-fetch-date.ts @@ -0,0 +1,5 @@ +import moment from 'moment'; + +export const getPreviousFetchDate = (days: number): Date => { + return moment().subtract(days, 'days').toDate(); +}; diff --git a/src/modules/netvisor/utils/get-schedule-fetch-date.ts b/src/modules/netvisor/utils/get-schedule-fetch-date.ts new file mode 100644 index 00000000..c5d391b7 --- /dev/null +++ b/src/modules/netvisor/utils/get-schedule-fetch-date.ts @@ -0,0 +1,22 @@ +import moment from 'moment'; + +import { scheduleFetchDays } from '../constants'; + +import { getPreviousFetchDate } from './get-previous-fetch-date'; + +export const getScheduleFetchDate = (): string | null => { + const today = new Date(); + const dayOfWeek = today.getDay(); + + const scheduleFetchDay = scheduleFetchDays.find( + ({ day }) => day === dayOfWeek, + ); + + if (!scheduleFetchDay) { + return null; + } + + const previousDate = getPreviousFetchDate(scheduleFetchDay.transform); + + return moment(previousDate).format('YYYY-MM-DD'); +}; diff --git a/src/modules/netvisor/utils/index.ts b/src/modules/netvisor/utils/index.ts new file mode 100644 index 00000000..c5b1f26e --- /dev/null +++ b/src/modules/netvisor/utils/index.ts @@ -0,0 +1,2 @@ +export * from './get-previous-fetch-date'; +export * from './get-schedule-fetch-date'; diff --git a/src/modules/offer-request/dto/offer-request/offer-request-data-initial.dto.ts b/src/modules/offer-request/dto/offer-request/offer-request-data-initial.dto.ts index 94bdd415..85ea21a7 100644 --- a/src/modules/offer-request/dto/offer-request/offer-request-data-initial.dto.ts +++ b/src/modules/offer-request/dto/offer-request/offer-request-data-initial.dto.ts @@ -1,5 +1,5 @@ import { Field, Float, ObjectType } from '@nestjs/graphql'; -import { AddressesAnswered, OfferRequestDB } from 'hero24-types'; +import { AddressesAnswered, OfferRequestDB, PaidStatus } from 'hero24-types'; import { OfferRequestQuestionAdapter, @@ -41,7 +41,7 @@ export class OfferRequestDataInitialDto { sendInvoiceWith?: MaybeType<'sms' | 'email'>; @Field(() => String, { nullable: true }) - prepaid?: MaybeType<'waiting' | 'paid'>; + prepaid?: MaybeType; @Field(() => String, { nullable: true }) postpaid?: MaybeType<'no' | 'yes'>; diff --git a/src/modules/offer-request/dto/offer-request/offer-request.dto.ts b/src/modules/offer-request/dto/offer-request/offer-request.dto.ts index e7ae1fb2..41426b33 100644 --- a/src/modules/offer-request/dto/offer-request/offer-request.dto.ts +++ b/src/modules/offer-request/dto/offer-request/offer-request.dto.ts @@ -39,6 +39,9 @@ export class OfferRequestDto { @Field(() => Float, { nullable: true }) customerVAT?: MaybeType; + @Field(() => Float, { nullable: true }) + hero24Cut?: MaybeType; + @Field(() => Float, { nullable: true }) serviceProviderVAT?: MaybeType; @@ -118,6 +121,7 @@ OfferRequestDto.adapter = new FirebaseAdapter({ stripeSubscriptionId: external.stripeSubscriptionId ?? undefined, stripePaymentIntentId: external.stripePaymentIntentId ?? undefined, customerVAT: external.customerVAT ?? undefined, + hero24Cut: external.hero24Cut ?? undefined, serviceProviderVAT: external.serviceProviderVAT ?? undefined, netvisorSalesOrderId: external.netvisorSalesOrderId ?? undefined, netvisorSalesInvoiceNumber: @@ -137,6 +141,7 @@ OfferRequestDto.adapter = new FirebaseAdapter({ stripeSubscriptionId: internal.stripeSubscriptionId, stripePaymentIntentId: internal.stripePaymentIntentId, customerVAT: internal.customerVAT, + hero24Cut: internal.hero24Cut, serviceProviderVAT: internal.serviceProviderVAT, netvisorSalesOrderId: internal.netvisorSalesOrderId, netvisorSalesInvoiceNumber: internal.netvisorSalesInvoiceNumber, diff --git a/src/modules/offer-request/offer-request.service.ts b/src/modules/offer-request/offer-request.service.ts index fb8428e0..bca0a91e 100644 --- a/src/modules/offer-request/offer-request.service.ts +++ b/src/modules/offer-request/offer-request.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { PubSub } from 'graphql-subscriptions'; -import { AddressesAnswered, OfferRequestDB } from 'hero24-types'; +import { AddressesAnswered, OfferRequestDB, PaidStatus } from 'hero24-types'; import get from 'lodash/get'; import isString from 'lodash/isString'; import map from 'lodash/map'; @@ -376,6 +376,20 @@ export class OfferRequestService { return this.strictGetOfferRequestById(offerRequestId); } + async updatePaidStatus( + offerRequestId: string, + status: PaidStatus, + ): Promise { + await this.offerRequestTableRef + .child(offerRequestId) + .child('data') + .child('initial') + .child('prepaid') + .set(status); + + return this.strictGetOfferRequestById(offerRequestId); + } + emitOfferRequestUpdated(offerRequest: OfferRequestDto): void { emitOfferRequestUpdated(this.pubSub, offerRequest); } diff --git a/src/modules/offer/services/offer.service.ts b/src/modules/offer/services/offer.service.ts index 44b99e74..f1f62596 100644 --- a/src/modules/offer/services/offer.service.ts +++ b/src/modules/offer/services/offer.service.ts @@ -50,6 +50,14 @@ export class OfferService { return offer && OfferDto.adapter.toExternal({ id: offerId, ...offer }); } + async getOffersByInvoiceIds(paidInvoices: string[]): Promise { + const offers = await this.getAllOffers(); + + return offers.filter((offer) => + paidInvoices.includes(offer.netvisorPurchaseInvoiceId ?? ''), + ); + } + async strictGetOfferById(offerId: string): Promise { const offer = await this.getOfferById(offerId); diff --git a/src/modules/xml2js/index.ts b/src/modules/xml2js/index.ts new file mode 100644 index 00000000..f78beabc --- /dev/null +++ b/src/modules/xml2js/index.ts @@ -0,0 +1 @@ +export * from './service'; diff --git a/src/modules/xml2js/module.ts b/src/modules/xml2js/module.ts new file mode 100644 index 00000000..5158b6a8 --- /dev/null +++ b/src/modules/xml2js/module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { Xml2JsService } from './service'; + +@Module({ + providers: [Xml2JsService], + exports: [Xml2JsService], +}) +export class Xml2JsModule {} diff --git a/src/modules/xml2js/service.ts b/src/modules/xml2js/service.ts new file mode 100644 index 00000000..50fcb2d2 --- /dev/null +++ b/src/modules/xml2js/service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { Parser } from 'xml2js'; + +@Injectable() +export class Xml2JsService { + private readonly parser: Parser; + + constructor() { + this.parser = new Parser(); + } + + async createObjectFromXml(xml: string): Promise { + return this.parser.parseStringPromise(xml) as Promise; + } +} diff --git a/tsconfig.json b/tsconfig.json index d3f38599..52180eb5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "$configs/*": ["src/config/*"], "$config": ["src/config/index"], "$decorator": ["src/common/decorators/index"], + "$utils": ["src/common/utils/index"], "$/*": ["src/*"] }, "module": "commonjs", @@ -19,7 +20,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "ESNext", "sourceMap": true, "outDir": "./dist", "incremental": true,