diff --git a/docker-compose.yml b/docker-compose.yml index 6ea762cb0..a0db1f596 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,18 @@ services: ports: - 27017:27017 + timescaledb: + image: timescale/timescaledb:latest-pg12 + restart: always + shm_size: 1gb + ports: + - 5431:5432 + environment: + POSTGRES_USER: timescaledb + POSTGRES_PASSWORD: password + volumes: + - timescaledb:/var/lib/postgresql/data + db: image: mysql:latest container_name: nft-db @@ -69,6 +81,9 @@ services: # Where our data will be persisted volumes: - my-db:/var/lib/mysql + # Names our volume volumes: my-db: + timescaledb: + name: timescaledb diff --git a/package-lock.json b/package-lock.json index 152043dce..7972475d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "amqp-connection-manager": "3.7.0", "amqplib": "0.8.0", "apollo-server": "3.11.1", - "aws-sdk": "2.974.0", + "aws-sdk": "2.1366.0", "axios": "0.21.4", "axios-retry": "3.1.8", "bignumber.js": "9.0.1", @@ -51,6 +51,7 @@ "graphql-upload": "13.0.0", "helmet": "4.4.1", "jwks-rsa": "2.0.3", + "moment": "^2.29.4", "mongoose": "6.9.0", "mysql2": "2.2.5", "nest-winston": "1.8.0", @@ -58,6 +59,7 @@ "passport": "0.6.0", "passport-jwt": "4.0.1", "passport-local": "1.0.0", + "pg": "8.10.0", "prom-client": "13.1.0", "redis": "3.1.2", "reflect-metadata": "0.1.13", @@ -66,11 +68,11 @@ "save": "2.5.0", "swagger-ui-express": "4.3.0", "tiny-async-pool": "1.2.0", - "typeorm": "0.3.11", + "typeorm": "0.3.16", "winston": "3.7.2" }, "devDependencies": { - "@nestjs/cli": "9.2.0", + "@nestjs/cli": "9.4.2", "@nestjs/schematics": "9.0.4", "@nestjs/testing": "9.3.12", "@types/express": "^4.17.11", @@ -109,9 +111,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "15.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.1.4.tgz", - "integrity": "sha512-PW5MRmd9DHJR4FaXchwQtj9pXnsghSTnwRvfZeCRNYgU2sv0DKyTV+YTSJB+kNXnoPNG1Je6amDEkiXecpspXg==", + "version": "15.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.6.tgz", + "integrity": "sha512-YVTWZ+M+xNKdFX4EnY9QX49PZraawiaA0iTd2CUW8ZoTUvU7yOGMKZLSdz6aokTMRVfm0449wt6YL994ibOo1g==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -153,14 +155,14 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "15.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.1.4.tgz", - "integrity": "sha512-jpddxo9Qd2yRQ1t9FLhAx5S+luz6HkyhDytq0LFKbxf9ikf1J4oy9riPBFl4pRmrNARWcHZ6GbD20/Ky8PjmXQ==", + "version": "15.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.6.tgz", + "integrity": "sha512-f7VgnAcok7AwR/DhX0ZWskB0rFBo/KsvtIUA2qZSrpKMf8eFiwu03dv/b2mI0vnf+1FBfIQzJvO0ww45zRp6dA==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.1.4", + "@angular-devkit/core": "15.2.6", "jsonc-parser": "3.2.0", - "magic-string": "0.27.0", + "magic-string": "0.29.0", "ora": "5.4.1", "rxjs": "6.6.7" }, @@ -171,13 +173,13 @@ } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "15.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-15.1.4.tgz", - "integrity": "sha512-qkM5Mfs28jZzNcJnSM6RlyrKkYvzhQmWFTxBXnn15k5T4EnSs1gI6O054Xn7jo/senfwNNt7h2Mlz2OmBLo6+w==", + "version": "15.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-15.2.6.tgz", + "integrity": "sha512-dkmJAvLmiXIX3uAY0a7GcnEvKNN/RKR5Q/ez4OQb+jaz+2/XbAiQVmTgZ5uwU2gYkFNLvG9ZCAaQdC4JJp9xaw==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.1.4", - "@angular-devkit/schematics": "15.1.4", + "@angular-devkit/core": "15.2.6", + "@angular-devkit/schematics": "15.2.6", "ansi-colors": "4.1.3", "inquirer": "8.2.4", "symbol-observable": "4.0.0", @@ -219,9 +221,9 @@ } }, "node_modules/@angular-devkit/schematics-cli/node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" @@ -759,6 +761,23 @@ "xss": "^1.0.8" } }, + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "optional": true, + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, "node_modules/@aws-crypto/ie11-detection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", @@ -846,507 +865,518 @@ "optional": true }, "node_modules/@aws-sdk/abort-controller": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.266.1.tgz", - "integrity": "sha512-6tG6dAgMMKh86U2kgo58J6pyC2pSEAtm1bXnhYOuuXBjFgieNvikwjoj//zzciudmp1qTu5Wh99u8LBLmYofFg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.347.0.tgz", + "integrity": "sha512-P/2qE6ntYEmYG4Ez535nJWZbXqgbkJx8CMz7ChEuEg3Gp3dvVYEKg+iEUEvlqQ2U5dWP5J3ehw5po9t86IsVPQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.266.1.tgz", - "integrity": "sha512-kLKsQtPmbXeIxwv3NvR/xQYCyIG6NE9UsVtiSulOkmK6W7u9RVyYitCPpmo1X/YC5ORcr+Qf8aDLkUeIxygeVg==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.348.0.tgz", + "integrity": "sha512-1fcJFUQTsAXjkaAn/kn9ty790uHbCpukkuqJ/0QNPFYaa6vu93xx7FnzOvRK4XvaojwZ/C+yxp0fNQ+GjXG0vg==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.266.1", - "@aws-sdk/config-resolver": "3.266.1", - "@aws-sdk/credential-provider-node": "3.266.1", - "@aws-sdk/fetch-http-handler": "3.266.1", - "@aws-sdk/hash-node": "3.266.1", - "@aws-sdk/invalid-dependency": "3.266.1", - "@aws-sdk/middleware-content-length": "3.266.1", - "@aws-sdk/middleware-endpoint": "3.266.1", - "@aws-sdk/middleware-host-header": "3.266.1", - "@aws-sdk/middleware-logger": "3.266.1", - "@aws-sdk/middleware-recursion-detection": "3.266.1", - "@aws-sdk/middleware-retry": "3.266.1", - "@aws-sdk/middleware-serde": "3.266.1", - "@aws-sdk/middleware-signing": "3.266.1", - "@aws-sdk/middleware-stack": "3.266.1", - "@aws-sdk/middleware-user-agent": "3.266.1", - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/node-http-handler": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/smithy-client": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/url-parser": "3.266.1", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.266.1", - "@aws-sdk/util-defaults-mode-node": "3.266.1", - "@aws-sdk/util-endpoints": "3.266.1", - "@aws-sdk/util-retry": "3.266.1", - "@aws-sdk/util-user-agent-browser": "3.266.1", - "@aws-sdk/util-user-agent-node": "3.266.1", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/client-sts": "3.348.0", + "@aws-sdk/config-resolver": "3.347.0", + "@aws-sdk/credential-provider-node": "3.348.0", + "@aws-sdk/fetch-http-handler": "3.347.0", + "@aws-sdk/hash-node": "3.347.0", + "@aws-sdk/invalid-dependency": "3.347.0", + "@aws-sdk/middleware-content-length": "3.347.0", + "@aws-sdk/middleware-endpoint": "3.347.0", + "@aws-sdk/middleware-host-header": "3.347.0", + "@aws-sdk/middleware-logger": "3.347.0", + "@aws-sdk/middleware-recursion-detection": "3.347.0", + "@aws-sdk/middleware-retry": "3.347.0", + "@aws-sdk/middleware-serde": "3.347.0", + "@aws-sdk/middleware-signing": "3.347.0", + "@aws-sdk/middleware-stack": "3.347.0", + "@aws-sdk/middleware-user-agent": "3.347.0", + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/node-http-handler": "3.348.0", + "@aws-sdk/smithy-client": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/url-parser": "3.347.0", + "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-body-length-browser": "3.310.0", + "@aws-sdk/util-body-length-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.347.0", + "@aws-sdk/util-defaults-mode-node": "3.347.0", + "@aws-sdk/util-endpoints": "3.347.0", + "@aws-sdk/util-retry": "3.347.0", + "@aws-sdk/util-user-agent-browser": "3.347.0", + "@aws-sdk/util-user-agent-node": "3.347.0", + "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.266.1.tgz", - "integrity": "sha512-mgrRfNSa7sJyBgAuMvRE5W2izHYl1n0tpxjLZ8rP+AoOp0GrZLpuj9T2XhmVwyR4ibVBNFKdr8nUHWekF4HA+w==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.348.0.tgz", + "integrity": "sha512-5S23gVKBl0fhZ96RD8LdPhMKeh8E5fmebyZxMNZuWliSXz++Q9ZCrwPwQbkks3duPOTcKKobs3IoqP82HoXMvQ==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.266.1", - "@aws-sdk/fetch-http-handler": "3.266.1", - "@aws-sdk/hash-node": "3.266.1", - "@aws-sdk/invalid-dependency": "3.266.1", - "@aws-sdk/middleware-content-length": "3.266.1", - "@aws-sdk/middleware-endpoint": "3.266.1", - "@aws-sdk/middleware-host-header": "3.266.1", - "@aws-sdk/middleware-logger": "3.266.1", - "@aws-sdk/middleware-recursion-detection": "3.266.1", - "@aws-sdk/middleware-retry": "3.266.1", - "@aws-sdk/middleware-serde": "3.266.1", - "@aws-sdk/middleware-stack": "3.266.1", - "@aws-sdk/middleware-user-agent": "3.266.1", - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/node-http-handler": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/smithy-client": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/url-parser": "3.266.1", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.266.1", - "@aws-sdk/util-defaults-mode-node": "3.266.1", - "@aws-sdk/util-endpoints": "3.266.1", - "@aws-sdk/util-retry": "3.266.1", - "@aws-sdk/util-user-agent-browser": "3.266.1", - "@aws-sdk/util-user-agent-node": "3.266.1", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.347.0", + "@aws-sdk/fetch-http-handler": "3.347.0", + "@aws-sdk/hash-node": "3.347.0", + "@aws-sdk/invalid-dependency": "3.347.0", + "@aws-sdk/middleware-content-length": "3.347.0", + "@aws-sdk/middleware-endpoint": "3.347.0", + "@aws-sdk/middleware-host-header": "3.347.0", + "@aws-sdk/middleware-logger": "3.347.0", + "@aws-sdk/middleware-recursion-detection": "3.347.0", + "@aws-sdk/middleware-retry": "3.347.0", + "@aws-sdk/middleware-serde": "3.347.0", + "@aws-sdk/middleware-stack": "3.347.0", + "@aws-sdk/middleware-user-agent": "3.347.0", + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/node-http-handler": "3.348.0", + "@aws-sdk/smithy-client": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/url-parser": "3.347.0", + "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-body-length-browser": "3.310.0", + "@aws-sdk/util-body-length-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.347.0", + "@aws-sdk/util-defaults-mode-node": "3.347.0", + "@aws-sdk/util-endpoints": "3.347.0", + "@aws-sdk/util-retry": "3.347.0", + "@aws-sdk/util-user-agent-browser": "3.347.0", + "@aws-sdk/util-user-agent-node": "3.347.0", + "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.266.1.tgz", - "integrity": "sha512-eErpowPr6etcZH25v8JfJNdSPr+jet98cFWhsCN8GSxVNkyZci6aZnx6pBsTQCQn7L/zx8i4QZuOo5LYXdzF6A==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.348.0.tgz", + "integrity": "sha512-tvHpcycx4EALvk38I9rAOdPeHvBDezqIB4lrE7AvnOJljlvCcdQ2gXa9GDrwrM7zuYBIZMBRE/njTMrCwoOdAA==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.266.1", - "@aws-sdk/fetch-http-handler": "3.266.1", - "@aws-sdk/hash-node": "3.266.1", - "@aws-sdk/invalid-dependency": "3.266.1", - "@aws-sdk/middleware-content-length": "3.266.1", - "@aws-sdk/middleware-endpoint": "3.266.1", - "@aws-sdk/middleware-host-header": "3.266.1", - "@aws-sdk/middleware-logger": "3.266.1", - "@aws-sdk/middleware-recursion-detection": "3.266.1", - "@aws-sdk/middleware-retry": "3.266.1", - "@aws-sdk/middleware-serde": "3.266.1", - "@aws-sdk/middleware-stack": "3.266.1", - "@aws-sdk/middleware-user-agent": "3.266.1", - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/node-http-handler": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/smithy-client": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/url-parser": "3.266.1", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.266.1", - "@aws-sdk/util-defaults-mode-node": "3.266.1", - "@aws-sdk/util-endpoints": "3.266.1", - "@aws-sdk/util-retry": "3.266.1", - "@aws-sdk/util-user-agent-browser": "3.266.1", - "@aws-sdk/util-user-agent-node": "3.266.1", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.347.0", + "@aws-sdk/fetch-http-handler": "3.347.0", + "@aws-sdk/hash-node": "3.347.0", + "@aws-sdk/invalid-dependency": "3.347.0", + "@aws-sdk/middleware-content-length": "3.347.0", + "@aws-sdk/middleware-endpoint": "3.347.0", + "@aws-sdk/middleware-host-header": "3.347.0", + "@aws-sdk/middleware-logger": "3.347.0", + "@aws-sdk/middleware-recursion-detection": "3.347.0", + "@aws-sdk/middleware-retry": "3.347.0", + "@aws-sdk/middleware-serde": "3.347.0", + "@aws-sdk/middleware-stack": "3.347.0", + "@aws-sdk/middleware-user-agent": "3.347.0", + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/node-http-handler": "3.348.0", + "@aws-sdk/smithy-client": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/url-parser": "3.347.0", + "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-body-length-browser": "3.310.0", + "@aws-sdk/util-body-length-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.347.0", + "@aws-sdk/util-defaults-mode-node": "3.347.0", + "@aws-sdk/util-endpoints": "3.347.0", + "@aws-sdk/util-retry": "3.347.0", + "@aws-sdk/util-user-agent-browser": "3.347.0", + "@aws-sdk/util-user-agent-node": "3.347.0", + "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.266.1.tgz", - "integrity": "sha512-P1hIyJkzojIG5NHuW2u/oae36KUvTB2q4nSIWuU4BrUPDeBoHg+5+zRRavtfK88aLRohwYDumRdLegT6sQNt0g==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.348.0.tgz", + "integrity": "sha512-4iaQlWAOHMEF4xjR/FB/ws3aUjXjJHwbsIcqbdYAxsKijDYYTZYCPc/gM0NE1yi28qlNYNhMzHipe5xTYbU2Eg==", "optional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/config-resolver": "3.266.1", - "@aws-sdk/credential-provider-node": "3.266.1", - "@aws-sdk/fetch-http-handler": "3.266.1", - "@aws-sdk/hash-node": "3.266.1", - "@aws-sdk/invalid-dependency": "3.266.1", - "@aws-sdk/middleware-content-length": "3.266.1", - "@aws-sdk/middleware-endpoint": "3.266.1", - "@aws-sdk/middleware-host-header": "3.266.1", - "@aws-sdk/middleware-logger": "3.266.1", - "@aws-sdk/middleware-recursion-detection": "3.266.1", - "@aws-sdk/middleware-retry": "3.266.1", - "@aws-sdk/middleware-sdk-sts": "3.266.1", - "@aws-sdk/middleware-serde": "3.266.1", - "@aws-sdk/middleware-signing": "3.266.1", - "@aws-sdk/middleware-stack": "3.266.1", - "@aws-sdk/middleware-user-agent": "3.266.1", - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/node-http-handler": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/smithy-client": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/url-parser": "3.266.1", - "@aws-sdk/util-base64": "3.208.0", - "@aws-sdk/util-body-length-browser": "3.188.0", - "@aws-sdk/util-body-length-node": "3.208.0", - "@aws-sdk/util-defaults-mode-browser": "3.266.1", - "@aws-sdk/util-defaults-mode-node": "3.266.1", - "@aws-sdk/util-endpoints": "3.266.1", - "@aws-sdk/util-retry": "3.266.1", - "@aws-sdk/util-user-agent-browser": "3.266.1", - "@aws-sdk/util-user-agent-node": "3.266.1", - "@aws-sdk/util-utf8": "3.254.0", - "fast-xml-parser": "4.0.11", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.347.0", + "@aws-sdk/credential-provider-node": "3.348.0", + "@aws-sdk/fetch-http-handler": "3.347.0", + "@aws-sdk/hash-node": "3.347.0", + "@aws-sdk/invalid-dependency": "3.347.0", + "@aws-sdk/middleware-content-length": "3.347.0", + "@aws-sdk/middleware-endpoint": "3.347.0", + "@aws-sdk/middleware-host-header": "3.347.0", + "@aws-sdk/middleware-logger": "3.347.0", + "@aws-sdk/middleware-recursion-detection": "3.347.0", + "@aws-sdk/middleware-retry": "3.347.0", + "@aws-sdk/middleware-sdk-sts": "3.347.0", + "@aws-sdk/middleware-serde": "3.347.0", + "@aws-sdk/middleware-signing": "3.347.0", + "@aws-sdk/middleware-stack": "3.347.0", + "@aws-sdk/middleware-user-agent": "3.347.0", + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/node-http-handler": "3.348.0", + "@aws-sdk/smithy-client": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/url-parser": "3.347.0", + "@aws-sdk/util-base64": "3.310.0", + "@aws-sdk/util-body-length-browser": "3.310.0", + "@aws-sdk/util-body-length-node": "3.310.0", + "@aws-sdk/util-defaults-mode-browser": "3.347.0", + "@aws-sdk/util-defaults-mode-node": "3.347.0", + "@aws-sdk/util-endpoints": "3.347.0", + "@aws-sdk/util-retry": "3.347.0", + "@aws-sdk/util-user-agent-browser": "3.347.0", + "@aws-sdk/util-user-agent-node": "3.347.0", + "@aws-sdk/util-utf8": "3.310.0", + "@smithy/protocol-http": "^1.0.1", + "@smithy/types": "^1.0.0", + "fast-xml-parser": "4.2.4", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/config-resolver": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.266.1.tgz", - "integrity": "sha512-MqMVki/y40Ot7XWJnziYuO35zqww3JbpH9jzCRCf8vtOE9u6C8VpuiG/OHIR9WQj63Yhcr+7fohmN3kGFnNWFg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.347.0.tgz", + "integrity": "sha512-2ja+Sf/VnUO7IQ3nKbDQ5aumYKKJUaTm/BuVJ29wNho8wYHfuf7wHZV0pDTkB8RF5SH7IpHap7zpZAj39Iq+EA==", "optional": true, "dependencies": { - "@aws-sdk/signature-v4": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-config-provider": "3.310.0", + "@aws-sdk/util-middleware": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.266.1.tgz", - "integrity": "sha512-q0ff3P04e1LIHeryrnVkrztd1OqAsqP7NtzIvH+BMmgiW6t2pWXMU+hA7CzroE9KILwxqIqzuF+huXaY74Duuw==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.348.0.tgz", + "integrity": "sha512-VQQVEP844mAwn5iEIzc/hBOuSzMGBL61sqEGqqgxhe6Sjnd8NfGNlOjV6fOxlUHhOelumqBMXgn6liIZbfcqFQ==", "optional": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/client-cognito-identity": "3.348.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.266.1.tgz", - "integrity": "sha512-RPq9/FV7fOv14P5DxpqpcwuCa7P6ijUrN1vhpiYaWMQNJSsJK8cIsPECI3xQ1z+oPZ5/1qA++0RpTLqIhq/ifg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.347.0.tgz", + "integrity": "sha512-UnEM+LKGpXKzw/1WvYEQsC6Wj9PupYZdQOE+e2Dgy2dqk/pVFy4WueRtFXYDT2B41ppv3drdXUuKZRIDVqIgNQ==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-imds": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.266.1.tgz", - "integrity": "sha512-pTJnJtKaR0JWVqyt9XgHiqlK+3GnZfd3cuKGv9IsYxumVzladm7gNKiNFw0A2KsDj9jhrCRRZwEsH9ooDzZ/Ow==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.347.0.tgz", + "integrity": "sha512-7scCy/DCDRLIhlqTxff97LQWDnRwRXji3bxxMg+xWOTTaJe7PWx+etGSbBWaL42vsBHFShQjSLvJryEgoBktpw==", "optional": true, "dependencies": { - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/url-parser": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/url-parser": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.266.1.tgz", - "integrity": "sha512-N52GNeHRJufEx+V0mWfwe5cV3ukHong75uRAB0IeapJwj+kKwxxLH1dKOUaGjd/ALx6/hsISoUE/6jm/Qf/DsA==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.266.1", - "@aws-sdk/credential-provider-imds": "3.266.1", - "@aws-sdk/credential-provider-process": "3.266.1", - "@aws-sdk/credential-provider-sso": "3.266.1", - "@aws-sdk/credential-provider-web-identity": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.348.0.tgz", + "integrity": "sha512-0IEH5mH/cz2iLyr/+pSa3sCsQcGADiLSEn6yivsXdfz1zDqBiv+ffDoL0+Pvnp+TKf8sA6OlX8PgoMoEBvBdKw==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.347.0", + "@aws-sdk/credential-provider-imds": "3.347.0", + "@aws-sdk/credential-provider-process": "3.347.0", + "@aws-sdk/credential-provider-sso": "3.348.0", + "@aws-sdk/credential-provider-web-identity": "3.347.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/shared-ini-file-loader": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.266.1.tgz", - "integrity": "sha512-6/iTi/zugdvuyQDmEakYn01kiFKUArL+rIYwcMf20YguXNml6G4HVWJGbX2JklY6ovnznU5ENw6+ftzBAiw/PA==", - "optional": true, - "dependencies": { - "@aws-sdk/credential-provider-env": "3.266.1", - "@aws-sdk/credential-provider-imds": "3.266.1", - "@aws-sdk/credential-provider-ini": "3.266.1", - "@aws-sdk/credential-provider-process": "3.266.1", - "@aws-sdk/credential-provider-sso": "3.266.1", - "@aws-sdk/credential-provider-web-identity": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.348.0.tgz", + "integrity": "sha512-ngRWphm9e36i58KqVi7Z8WOub+k0cSl+JZaAmgfFm0+dsfBG5uheo598OeiwWV0DqlilvaQZFaMVQgG2SX/tHg==", + "optional": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "3.347.0", + "@aws-sdk/credential-provider-imds": "3.347.0", + "@aws-sdk/credential-provider-ini": "3.348.0", + "@aws-sdk/credential-provider-process": "3.347.0", + "@aws-sdk/credential-provider-sso": "3.348.0", + "@aws-sdk/credential-provider-web-identity": "3.347.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/shared-ini-file-loader": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.266.1.tgz", - "integrity": "sha512-4V/7zVnaZo1IP4Is09dlwd2CkltlUdgbX4NUIb+QxZ/BlY7Ws47xyCjjyJhVVCe+y184M58bG4+HR5dHnrBfSA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.347.0.tgz", + "integrity": "sha512-yl1z4MsaBdXd4GQ2halIvYds23S67kElyOwz7g8kaQ4kHj+UoYWxz3JVW/DGusM6XmQ9/F67utBrUVA0uhQYyw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/shared-ini-file-loader": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.266.1.tgz", - "integrity": "sha512-d9hcV7XV1Gh0Dkt8kADsSoB/hZPlbuTp/Vzbj0HMO7hlGxFGcTrGN1UoQc11UAp4kKeF3i2ZQlMsch0d/2gK3w==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.348.0.tgz", + "integrity": "sha512-5cQao705376KgGkLv9xgkQ3T5H7KdNddWuyoH2wDcrHd1BA2Lnrell3Yyh7R6jQeV7uCQE/z0ugUOKhDqNKIqQ==", "optional": true, "dependencies": { - "@aws-sdk/client-sso": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/token-providers": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/client-sso": "3.348.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/shared-ini-file-loader": "3.347.0", + "@aws-sdk/token-providers": "3.348.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.266.1.tgz", - "integrity": "sha512-JIktczlqxIc+Gqc/99e7pPzNSgUjYX23fA2dmLt1bHRPH15p8S1Kv73lvqsgLF5EKP1H/UXDu+jVWDklYM6fVA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.347.0.tgz", + "integrity": "sha512-DxoTlVK8lXjS1zVphtz/Ab+jkN/IZor9d6pP2GjJHNoAIIzXfRwwj5C8vr4eTayx/5VJ7GRP91J8GJ2cKly8Qw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.266.1.tgz", - "integrity": "sha512-Iz8zX1ZmZ7z5yFV4bFNu7xbNBGPUHJubp+mYFpf/lXueQpW4STVNbWGnfyLnKrT1glPtJdsXDFb/4GI0jhSKcw==", - "optional": true, - "dependencies": { - "@aws-sdk/client-cognito-identity": "3.266.1", - "@aws-sdk/client-sso": "3.266.1", - "@aws-sdk/client-sts": "3.266.1", - "@aws-sdk/credential-provider-cognito-identity": "3.266.1", - "@aws-sdk/credential-provider-env": "3.266.1", - "@aws-sdk/credential-provider-imds": "3.266.1", - "@aws-sdk/credential-provider-ini": "3.266.1", - "@aws-sdk/credential-provider-node": "3.266.1", - "@aws-sdk/credential-provider-process": "3.266.1", - "@aws-sdk/credential-provider-sso": "3.266.1", - "@aws-sdk/credential-provider-web-identity": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.348.0.tgz", + "integrity": "sha512-lpq1aHjFyExqD/6L8BK0OaROpCJuhnexGrABYljGI6yaLsyHbQpdE2+Y/WaxuRAK9wyP5s+7KNJ1ZK1ktrk5uQ==", + "optional": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.348.0", + "@aws-sdk/client-sso": "3.348.0", + "@aws-sdk/client-sts": "3.348.0", + "@aws-sdk/credential-provider-cognito-identity": "3.348.0", + "@aws-sdk/credential-provider-env": "3.347.0", + "@aws-sdk/credential-provider-imds": "3.347.0", + "@aws-sdk/credential-provider-ini": "3.348.0", + "@aws-sdk/credential-provider-node": "3.348.0", + "@aws-sdk/credential-provider-process": "3.347.0", + "@aws-sdk/credential-provider-sso": "3.348.0", + "@aws-sdk/credential-provider-web-identity": "3.347.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/eventstream-codec": { + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.347.0.tgz", + "integrity": "sha512-61q+SyspjsaQ4sdgjizMyRgVph2CiW4aAtfpoH69EJFJfTxTR/OqnZ9Jx/3YiYi0ksrvDenJddYodfWWJqD8/w==", + "optional": true, + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-hex-encoding": "3.310.0", + "tslib": "^2.5.0" + } + }, "node_modules/@aws-sdk/fetch-http-handler": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.266.1.tgz", - "integrity": "sha512-tyVMLBrJF1weMUqLU81lhuHES5QtFg7RmSysYM8mndePwBl81iQjLF5D7M8CU3aVzXY3TNU3rZBrm5xEK3xK1w==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.347.0.tgz", + "integrity": "sha512-sQ5P7ivY8//7wdxfA76LT1sF6V2Tyyz1qF6xXf9sihPN5Q1Y65c+SKpMzXyFSPqWZ82+SQQuDliYZouVyS6kQQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/querystring-builder": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-base64": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/querystring-builder": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-base64": "3.310.0", + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/hash-node": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.266.1.tgz", - "integrity": "sha512-2DbuY/AmtF4ORJVEAdzHfbM1p8w9ThRlu4BGdI7DXpO6/o1kgRBvNEbZc6MZkg7D2bI7TT6bI83u7AAbbMUMng==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.347.0.tgz", + "integrity": "sha512-96+ml/4EaUaVpzBdOLGOxdoXOjkPgkoJp/0i1fxOJEvl8wdAQSwc3IugVK9wZkCxy2DlENtgOe6DfIOhfffm/g==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-buffer-from": "3.208.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-buffer-from": "3.310.0", + "@aws-sdk/util-utf8": "3.310.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/invalid-dependency": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.266.1.tgz", - "integrity": "sha512-rGc2Bv10eEVQW2Zwrd4/I2QBj5MOhl8qr1NA3UCHJa2501Z97/jn2BGZoX+Cc+iE55so66GKmqMYpibqdtDARw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.347.0.tgz", + "integrity": "sha512-8imQcwLwqZ/wTJXZqzXT9pGLIksTRckhGLZaXT60tiBOPKuerTsus2L59UstLs5LP8TKaVZKFFSsjRIn9dQdmQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.201.0.tgz", - "integrity": "sha512-UPez5qLh3dNgt0DYnPD/q0mVJY84rA17QE26hVNOW3fAji8W2wrwrxdacWOxyXvlxWsVRcKmr+lay1MDqpAMfg==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.310.0.tgz", + "integrity": "sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-content-length": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.266.1.tgz", - "integrity": "sha512-Clq14Fr9WkiSg59jnIelL2F5D81HAhdE1MCZIAEEjN1ZK6bEM2kECnNT9CKJjDsuPvhdkrVGv9rjUSANWHLETw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.347.0.tgz", + "integrity": "sha512-i4qtWTDImMaDUtwKQPbaZpXsReiwiBomM1cWymCU4bhz81HL01oIxOxOBuiM+3NlDoCSPr3KI6txZSz/8cqXCQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-endpoint": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.266.1.tgz", - "integrity": "sha512-EVnzd51U/Jhz9x68jFwqHjU4KPsLIXfuS1PSNV598OT04WLQXerBx/fvZh17Y4Dmmu6hf/JUWI9PI5To+oC3mQ==", - "optional": true, - "dependencies": { - "@aws-sdk/middleware-serde": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/signature-v4": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/url-parser": "3.266.1", - "@aws-sdk/util-config-provider": "3.208.0", - "@aws-sdk/util-middleware": "3.266.1", - "tslib": "^2.3.1" + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint/-/middleware-endpoint-3.347.0.tgz", + "integrity": "sha512-unF0c6dMaUL1ffU+37Ugty43DgMnzPWXr/Jup/8GbK5fzzWT5NQq6dj9KHPubMbWeEjQbmczvhv25JuJdK8gNQ==", + "optional": true, + "dependencies": { + "@aws-sdk/middleware-serde": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/url-parser": "3.347.0", + "@aws-sdk/util-middleware": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.266.1.tgz", - "integrity": "sha512-3FSD8EkxOGV4O2iKgBnAwvj3PG/lABzcqmX6hABnsIusXAlUV5umh39FteipLcjnMXB04cLgmcgcG2o3cSA3tQ==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.347.0.tgz", + "integrity": "sha512-kpKmR9OvMlnReqp5sKcJkozbj1wmlblbVSbnQAIkzeQj2xD5dnVR3Nn2ogQKxSmU1Fv7dEroBtrruJ1o3fY38A==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.266.1.tgz", - "integrity": "sha512-FbD9Hqt994PyDm7OTG8PbIuB6Mv9vYhqOM2RhqC1UGtprDmk084/cEv9Sp+qY33lFPxjZstKneQK6FhAfozIAQ==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.347.0.tgz", + "integrity": "sha512-NYC+Id5UCkVn+3P1t/YtmHt75uED06vwaKyxDy0UmB2K66PZLVtwWbLpVWrhbroaw1bvUHYcRyQ9NIfnVcXQjA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.266.1.tgz", - "integrity": "sha512-rgRxdgrLOD20zIFrjFW7Bu3s4MXC1KLDbqJY6sMpc5D8mmQlxfaQiSnCQrjgUxbW0Ni+rXiatlW2q2MwCUAPzw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.347.0.tgz", + "integrity": "sha512-qfnSvkFKCAMjMHR31NdsT0gv5Sq/ZHTUD4yQsSLpbVQ6iYAS834lrzXt41iyEHt57Y514uG7F/Xfvude3u4icQ==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-retry": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.266.1.tgz", - "integrity": "sha512-xBiKAjAP1j8SbKhF28bk1g2iZoiVMI7XV/x5d0g6igsvI4RiqzywTsiLi2VVsYPCY6bwbn0Zgt93Mej/MFfn5w==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.347.0.tgz", + "integrity": "sha512-CpdM+8dCSbX96agy4FCzOfzDmhNnGBM/pxrgIVLm5nkYTLuXp/d7ubpFEUHULr+4hCd5wakHotMt7yO29NFaVw==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/service-error-classification": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-middleware": "3.266.1", - "@aws-sdk/util-retry": "3.266.1", - "tslib": "^2.3.1", + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/service-error-classification": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-middleware": "3.347.0", + "@aws-sdk/util-retry": "3.347.0", + "tslib": "^2.5.0", "uuid": "^8.3.2" }, "engines": { @@ -1354,440 +1384,439 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.266.1.tgz", - "integrity": "sha512-lM9t+S+PjmJ/xhoP9e/sIUS2bZyuEbobHo6a9WPk0UcdiqDWBIp+8MlTRDafKZtlN36gPDk5+qM9tXcI6P5YCA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.347.0.tgz", + "integrity": "sha512-38LJ0bkIoVF3W97x6Jyyou72YV9Cfbml4OaDEdnrCOo0EssNZM5d7RhjMvQDwww7/3OBY/BzeOcZKfJlkYUXGw==", "optional": true, "dependencies": { - "@aws-sdk/middleware-signing": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/signature-v4": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/middleware-signing": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-serde": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.266.1.tgz", - "integrity": "sha512-UFJ4BlRG/MUOJq5afHohkDsMDPAkbuXGCkhTz93MGxbACEOJYoEvsaMjpLft88wu4D11GY1Y2PVFkfxJUYWDXA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.347.0.tgz", + "integrity": "sha512-x5Foi7jRbVJXDu9bHfyCbhYDH5pKK+31MmsSJ3k8rY8keXLBxm2XEEg/AIoV9/TUF9EeVvZ7F1/RmMpJnWQsEg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-signing": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.266.1.tgz", - "integrity": "sha512-PbVwt7xSP3xlT5x4Xdj7+2T1PgCW00bh5QrCJi2wo3dEN9UowU/IVGzGSv4/OJItLZWe4puGb1WtA+LKeWA40w==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.347.0.tgz", + "integrity": "sha512-zVBF/4MGKnvhAE/J+oAL/VAehiyv+trs2dqSQXwHou9j8eA8Vm8HS2NdOwpkZQchIxTuwFlqSusDuPEdYFbvGw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/signature-v4": "3.266.1", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-middleware": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/signature-v4": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-middleware": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-stack": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.266.1.tgz", - "integrity": "sha512-liqq541u1eCDe+TCDOSrOcH6kAB6Dn1R8pbtJ23hP3fYM5/8W3V0f6VcywALVL9Pam+mkYmodWeDRQK8ieLEOg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.347.0.tgz", + "integrity": "sha512-Izidg4rqtYMcKuvn2UzgEpPLSmyd8ub9+LQ2oIzG3mpIzCBITq7wp40jN1iNkMg+X6KEnX9vdMJIYZsPYMCYuQ==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.266.1.tgz", - "integrity": "sha512-yoHQSP3OngZnLWeuqMrYkOifMD8FUZxyXoUO9iHPytxns1Gri/4Gn/1raNWMqdrSIlBKPorKzCEu24DX5klf0w==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.347.0.tgz", + "integrity": "sha512-wJbGN3OE1/daVCrwk49whhIr9E0j1N4gWwN/wi4WuyYIA+5lMUfVp0aGIOvZR+878DxuFz2hQ4XcZVT4K2WvQw==", "optional": true, "dependencies": { - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-endpoints": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/node-config-provider": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.266.1.tgz", - "integrity": "sha512-cDDuj64nGskZNJQdwglIRqTazfZt0f8pooT1ZJrFoydLfMmR9yi6orizQ7C0i1vMkY02HxgwqJiwXuJ73gmaqA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.347.0.tgz", + "integrity": "sha512-faU93d3+5uTTUcotGgMXF+sJVFjrKh+ufW+CzYKT4yUHammyaIab/IbTPWy2hIolcEGtuPeVoxXw8TXbkh/tuw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/shared-ini-file-loader": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/node-http-handler": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.266.1.tgz", - "integrity": "sha512-oa1cDeD+fwGFg8xMfNUZ95xAE0dxiXaTdJwSqOzCVIBz/auahHrcfXey+Oynw1zUjv8ijOH9z/SXYrqfwlZosw==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.348.0.tgz", + "integrity": "sha512-wxdgc4tO5F6lN4wHr0CZ4TyIjDW/ORp4SJZdWYNs2L5J7+/SwqgJY2lxRlGi0i7Md+apAdE3sT3ukVQ/9pVfPg==", "optional": true, "dependencies": { - "@aws-sdk/abort-controller": "3.266.1", - "@aws-sdk/protocol-http": "3.266.1", - "@aws-sdk/querystring-builder": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/abort-controller": "3.347.0", + "@aws-sdk/protocol-http": "3.347.0", + "@aws-sdk/querystring-builder": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/property-provider": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.266.1.tgz", - "integrity": "sha512-1ZRWqc4sNFGDRZ0Tl4WaukU9jR4ghB84QEQOqc48cJIoDiwOAP9UBJTNBJXCVllmPWGNgx4/lfWJoaFcvwsrzw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.347.0.tgz", + "integrity": "sha512-t3nJ8CYPLKAF2v9nIHOHOlF0CviQbTvbFc2L4a+A+EVd/rM4PzL3+3n8ZJsr0h7f6uD04+b5YRFgKgnaqLXlEg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/protocol-http": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.266.1.tgz", - "integrity": "sha512-8Z1Yfkf59of1R9qRSPmDKIHDo0n5YNCh1FrRLmCRqjjiZ4Ed7FJV/W6YYnJ6VbPcVv1WK6FvwzrGPM2gg4P48Q==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.347.0.tgz", + "integrity": "sha512-2YdBhc02Wvy03YjhGwUxF0UQgrPWEy8Iq75pfS42N+/0B/+eWX1aQgfjFxIpLg7YSjT5eKtYOQGlYd4MFTgj9g==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/querystring-builder": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.266.1.tgz", - "integrity": "sha512-D1LoDv3A+c6YIYq6F2T5m8V0C14vQAarSoT6romVIIYCDuMK4R5BwB1NLFRco1dczyAYmqScxdV2C26+xjXJfw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.347.0.tgz", + "integrity": "sha512-phtKTe6FXoV02MoPkIVV6owXI8Mwr5IBN3bPoxhcPvJG2AjEmnetSIrhb8kwc4oNhlwfZwH6Jo5ARW/VEWbZtg==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-uri-escape": "3.201.0", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-uri-escape": "3.310.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/querystring-parser": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.266.1.tgz", - "integrity": "sha512-Ck8Ahluj+/eK4FcX8IlbO7DA1MNWdnh1rKjc1qx/ZWh71G/FdZ8Sse33N+Ed/z9v7H8W695dprRT6CuRlqyAbw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.347.0.tgz", + "integrity": "sha512-5VXOhfZz78T2W7SuXf2avfjKglx1VZgZgp9Zfhrt/Rq+MTu2D+PZc5zmJHhYigD7x83jLSLogpuInQpFMA9LgA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/service-error-classification": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.266.1.tgz", - "integrity": "sha512-c2EvUvn9XLaDjKozCcYlO4cbtbJzBgx6EuhW1eLsMGLY3EobVRo1hGT0PtRmWQNnoW0BXv6oi/8NLOV6x37fxA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.347.0.tgz", + "integrity": "sha512-xZ3MqSY81Oy2gh5g0fCtooAbahqh9VhsF8vcKjVX8+XPbGC8y+kej82+MsMg4gYL8gRFB9u4hgYbNgIS6JTAvg==", "optional": true, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/shared-ini-file-loader": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.266.1.tgz", - "integrity": "sha512-yV8GY1Cgbc6pl0SRRQtx3PPcZpqYvKf/h1pz0FgkMBPHwOhp7zJYUkYmu3yvXulfORNsM5ro7wnKa0kxb5ljmg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.347.0.tgz", + "integrity": "sha512-Xw+zAZQVLb+xMNHChXQ29tzzLqm3AEHsD8JJnlkeFjeMnWQtXdUfOARl5s8NzAppcKQNlVe2gPzjaKjoy2jz1Q==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/signature-v4": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.266.1.tgz", - "integrity": "sha512-kiHHA3voQKz4QYLKbR/3hKkY2n62MuGewYctvtQsh1069U/OI7FVceIE5hZnrlC5XX4jiNoF1lKdyRhXmK5GMQ==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.347.0.tgz", + "integrity": "sha512-58Uq1do+VsTHYkP11dTK+DF53fguoNNJL9rHRWhzP+OcYv3/mBMLoS2WPz/x9FO5mBg4ESFsug0I6mXbd36tjw==", "optional": true, "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "@aws-sdk/types": "3.266.1", - "@aws-sdk/util-hex-encoding": "3.201.0", - "@aws-sdk/util-middleware": "3.266.1", - "@aws-sdk/util-uri-escape": "3.201.0", - "@aws-sdk/util-utf8": "3.254.0", - "tslib": "^2.3.1" + "@aws-sdk/eventstream-codec": "3.347.0", + "@aws-sdk/is-array-buffer": "3.310.0", + "@aws-sdk/types": "3.347.0", + "@aws-sdk/util-hex-encoding": "3.310.0", + "@aws-sdk/util-middleware": "3.347.0", + "@aws-sdk/util-uri-escape": "3.310.0", + "@aws-sdk/util-utf8": "3.310.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/smithy-client": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.266.1.tgz", - "integrity": "sha512-fg/+JzHeYPS0poVckSiaE/h1eWf5+u2Cs8/zh/4bAvVPqSA3Gg/yBrtvP+HxKLoSo+ObuPb9aXXkeCKPke6ktA==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.347.0.tgz", + "integrity": "sha512-PaGTDsJLGK0sTjA6YdYQzILRlPRN3uVFyqeBUkfltXssvUzkm8z2t1lz2H4VyJLAhwnG5ZuZTNEV/2mcWrU7JQ==", "optional": true, "dependencies": { - "@aws-sdk/middleware-stack": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/middleware-stack": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.266.1.tgz", - "integrity": "sha512-N+qiLQvPvel9dFdEoffRG4Mcp2p82OMyUvS12P5iYWqPCDuPzU72rYT2PmVFKINmflqEySjsKo8vIaWx7Kl4pQ==", + "version": "3.348.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.348.0.tgz", + "integrity": "sha512-nTjoJkUsJUrJTZuqaeMD9PW2//Rdg2HgfDjiyC4jmAXtayWYCi11mqauurMaUHJ3p5qJ8f5xzxm6vBTbrftPag==", "optional": true, "dependencies": { - "@aws-sdk/client-sso-oidc": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/shared-ini-file-loader": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/client-sso-oidc": "3.348.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/shared-ini-file-loader": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.266.1.tgz", - "integrity": "sha512-OVg3CjHKT3/Ws33jx3TUYYkbFOv/CLb9m3P4gZQDvgKPsOagp96LOsG8ZWdcVZCvSorAUqSb5kuc1utsjJxDTw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.347.0.tgz", + "integrity": "sha512-GkCMy79mdjU9OTIe5KT58fI/6uqdf8UmMdWqVHmFJ+UpEzOci7L/uw4sOXWo7xpPzLs6cJ7s5ouGZW4GRPmHFA==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/url-parser": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.266.1.tgz", - "integrity": "sha512-7IBZ8TjTWafug26CnNpz6cdrLU0TZ0G7N9LNfqjM/+69KI/Ragvv2Lsm4jhSv2uMx5OEzwlVYIEYaKMnAUiRLQ==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.347.0.tgz", + "integrity": "sha512-lhrnVjxdV7hl+yCnJfDZOaVLSqKjxN20MIOiijRiqaWGLGEAiSqBreMhL89X1WKCifxAs4zZf9YB9SbdziRpAA==", "optional": true, "dependencies": { - "@aws-sdk/querystring-parser": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/querystring-parser": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-base64": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.208.0.tgz", - "integrity": "sha512-PQniZph5A6N7uuEOQi+1hnMz/FSOK/8kMFyFO+4DgA1dZ5pcKcn5wiFwHkcTb/BsgVqQa3Jx0VHNnvhlS8JyTg==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64/-/util-base64-3.310.0.tgz", + "integrity": "sha512-v3+HBKQvqgdzcbL+pFswlx5HQsd9L6ZTlyPVL2LS9nNXnCcR3XgGz9jRskikRUuUvUXtkSG1J88GAOnJ/apTPg==", "optional": true, "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/util-buffer-from": "3.310.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-body-length-browser": { - "version": "3.188.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", - "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.310.0.tgz", + "integrity": "sha512-sxsC3lPBGfpHtNTUoGXMQXLwjmR0zVpx0rSvzTPAuoVILVsp5AU/w5FphNPxD5OVIjNbZv9KsKTuvNTiZjDp9g==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-body-length-node": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.208.0.tgz", - "integrity": "sha512-3zj50e5g7t/MQf53SsuuSf0hEELzMtD8RX8C76f12OSRo2Bca4FLLYHe0TZbxcfQHom8/hOaeZEyTyMogMglqg==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.310.0.tgz", + "integrity": "sha512-2tqGXdyKhyA6w4zz7UPoS8Ip+7sayOg9BwHNidiGm2ikbDxm1YrCfYXvCBdwaJxa4hJfRVz+aL9e+d3GqPI9pQ==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.208.0.tgz", - "integrity": "sha512-7L0XUixNEFcLUGPeBF35enCvB9Xl+K6SQsmbrPk1P3mlV9mguWSDQqbOBwY1Ir0OVbD6H/ZOQU7hI/9RtRI0Zw==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.310.0.tgz", + "integrity": "sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==", "optional": true, "dependencies": { - "@aws-sdk/is-array-buffer": "3.201.0", - "tslib": "^2.3.1" + "@aws-sdk/is-array-buffer": "3.310.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-config-provider": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.208.0.tgz", - "integrity": "sha512-DSRqwrERUsT34ug+anlMBIFooBEGwM8GejC7q00Y/9IPrQy50KnG5PW2NiTjuLKNi7pdEOlwTSEocJE15eDZIg==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.310.0.tgz", + "integrity": "sha512-xIBaYo8dwiojCw8vnUcIL4Z5tyfb1v3yjqyJKJWV/dqKUFOOS0U591plmXbM+M/QkXyML3ypon1f8+BoaDExrg==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-defaults-mode-browser": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.266.1.tgz", - "integrity": "sha512-4arGHXzTwLIPlNb3a2v7i2fpKFBLQfFygUDT1E6VCAbNpvPVJk+/w0foFs0Zc8BQsPQsC+ZKe20pFw0hnHZJGw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.347.0.tgz", + "integrity": "sha512-+JHFA4reWnW/nMWwrLKqL2Lm/biw/Dzi/Ix54DAkRZ08C462jMKVnUlzAI+TfxQE3YLm99EIa0G7jiEA+p81Qw==", "optional": true, "dependencies": { - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", "bowser": "^2.11.0", - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@aws-sdk/util-defaults-mode-node": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.266.1.tgz", - "integrity": "sha512-EOo2pPtvJUd9vkwRAptBIeF4P5zHeHcvCcCw6ZuP7bLvaUNHxepKAy4iesaB4aqqRgVn6AdV7w489HnTxa8Kpw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.347.0.tgz", + "integrity": "sha512-A8BzIVhAAZE5WEukoAN2kYebzTc99ZgncbwOmgCCbvdaYlk5tzguR/s+uoT4G0JgQGol/4hAMuJEl7elNgU6RQ==", "optional": true, "dependencies": { - "@aws-sdk/config-resolver": "3.266.1", - "@aws-sdk/credential-provider-imds": "3.266.1", - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/property-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/config-resolver": "3.347.0", + "@aws-sdk/credential-provider-imds": "3.347.0", + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/property-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.266.1.tgz", - "integrity": "sha512-w2VjoAIvfw2gau+cVQ5vahfy5CqQJrNOnSXbH6kjpd8RVQ0wOWBDVKb8tUwF4ROD1zovx0jT9d7bsYdMyo3HJw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.347.0.tgz", + "integrity": "sha512-/WUkirizeNAqwVj0zkcrqdQ9pUm1HY5kU+qy7xTR0OebkuJauglkmSTMD+56L1JPunWqHhlwCMVRaz5eaJdSEQ==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-hex-encoding": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.201.0.tgz", - "integrity": "sha512-7t1vR1pVxKx0motd3X9rI3m/xNp78p3sHtP5yo4NP4ARpxyJ0fokBomY8ScaH2D/B+U5o9ARxldJUdMqyBlJcA==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.310.0.tgz", + "integrity": "sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.208.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.208.0.tgz", - "integrity": "sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", + "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-middleware": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.266.1.tgz", - "integrity": "sha512-iZq+lq80byWZMsdII4OS7CdhgGeuBXBPd//iFWq4YmGts5W1QI1FLIFcsOuUnZtQMiaAuvLXtEO8ZrfaKTFKgw==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.347.0.tgz", + "integrity": "sha512-8owqUA3ePufeYTUvlzdJ7Z0miLorTwx+rNol5lourGQZ9JXsVMo23+yGA7nOlFuXSGkoKpMOtn6S0BT2bcfeiw==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-retry": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.266.1.tgz", - "integrity": "sha512-mQZshXR31iM9eV+x50pdmIFuDAjd8wDrxJ/kDnwR0H9NaeIQ3SKcNFTs0PPqtu/JUX0vb4wvm2KjIkUyO2iijg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-retry/-/util-retry-3.347.0.tgz", + "integrity": "sha512-NxnQA0/FHFxriQAeEgBonA43Q9/VPFQa8cfJDuT2A1YZruMasgjcltoZszi1dvoIRWSZsFTW42eY2gdOd0nffQ==", "optional": true, "dependencies": { - "@aws-sdk/service-error-classification": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/service-error-classification": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@aws-sdk/util-uri-escape": { - "version": "3.201.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.201.0.tgz", - "integrity": "sha512-TeTWbGx4LU2c5rx0obHeDFeO9HvwYwQtMh1yniBz00pQb6Qt6YVOETVQikRZ+XRQwEyCg/dA375UplIpiy54mA==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.310.0.tgz", + "integrity": "sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==", "optional": true, "dependencies": { - "tslib": "^2.3.1" + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.266.1.tgz", - "integrity": "sha512-zT5Sc0rNLOhBC+RhFF0FRE2y+CIf50rJZLkxRXoVRXJeFVSKPyhk3AKqe2Q6FE+yQsTV2FlwSDI98SxgaDORkQ==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.347.0.tgz", + "integrity": "sha512-ydxtsKVtQefgbk1Dku1q7pMkjDYThauG9/8mQkZUAVik55OUZw71Zzr3XO8J8RKvQG8lmhPXuAQ0FKAyycc0RA==", "optional": true, "dependencies": { - "@aws-sdk/types": "3.266.1", + "@aws-sdk/types": "3.347.0", "bowser": "^2.11.0", - "tslib": "^2.3.1" + "tslib": "^2.5.0" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.266.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.266.1.tgz", - "integrity": "sha512-o8uYR38GxaKj95acC0tIxM2K0vANVMpEpgpWcW+QTvVc4Vm4im0SBD7BvgXbQV2VW8X28ZNddVbCK7pHHEJrtg==", + "version": "3.347.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.347.0.tgz", + "integrity": "sha512-6X0b9qGsbD1s80PmbaB6v1/ZtLfSx6fjRX8caM7NN0y/ObuLoX8LhYnW6WlB2f1+xb4EjaCNgpP/zCf98MXosw==", "optional": true, "dependencies": { - "@aws-sdk/node-config-provider": "3.266.1", - "@aws-sdk/types": "3.266.1", - "tslib": "^2.3.1" + "@aws-sdk/node-config-provider": "3.347.0", + "@aws-sdk/types": "3.347.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" @@ -1802,13 +1831,13 @@ } }, "node_modules/@aws-sdk/util-utf8": { - "version": "3.254.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.254.0.tgz", - "integrity": "sha512-14Kso/eIt5/qfIBmhEL9L1IfyUqswjSTqO2mY7KOzUZ9SZbwn3rpxmtkhmATkRjD7XIlLKaxBkI7tU9Zjzj8Kw==", + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.310.0.tgz", + "integrity": "sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==", "optional": true, "dependencies": { - "@aws-sdk/util-buffer-from": "3.208.0", - "tslib": "^2.3.1" + "@aws-sdk/util-buffer-from": "3.310.0", + "tslib": "^2.5.0" }, "engines": { "node": ">=14.0.0" @@ -2373,6 +2402,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", @@ -3140,9 +3180,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -3150,9 +3190,9 @@ } }, "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", @@ -3424,32 +3464,32 @@ } }, "node_modules/@nestjs/cli": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.2.0.tgz", - "integrity": "sha512-6B1IjDcJbrOu55oMF67L1x5lDUOZ3Zs9l7bKCBH9D78965m8wq/2rlEWl/gJto5TABLQWy3hVvV/s8VzUlRMxw==", + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.4.2.tgz", + "integrity": "sha512-QWpk3UkpcAIvlqh2sSc6atHyaNFl7POi45Ujd5sAtVNogzpGphOlSyh39XuJcpe0FP3Z9IxX/0AUHF7KL/VyJQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.1.4", - "@angular-devkit/schematics": "15.1.4", - "@angular-devkit/schematics-cli": "15.1.4", - "@nestjs/schematics": "^9.0.0", - "chalk": "3.0.0", + "@angular-devkit/core": "15.2.6", + "@angular-devkit/schematics": "15.2.6", + "@angular-devkit/schematics-cli": "15.2.6", + "@nestjs/schematics": "^9.0.4", + "chalk": "4.1.2", "chokidar": "3.5.3", "cli-table3": "0.6.3", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "7.3.0", - "inquirer": "7.3.3", + "fork-ts-checker-webpack-plugin": "8.0.0", + "inquirer": "8.2.5", "node-emoji": "1.11.0", "ora": "5.4.1", "os-name": "4.0.1", - "rimraf": "4.1.2", + "rimraf": "4.4.1", "shelljs": "0.8.5", "source-map-support": "0.5.21", "tree-kill": "1.2.2", - "tsconfig-paths": "4.1.2", - "tsconfig-paths-webpack-plugin": "4.0.0", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.0.1", "typescript": "4.9.5", - "webpack": "5.75.0", + "webpack": "5.80.0", "webpack-node-externals": "3.0.0" }, "bin": { @@ -3459,24 +3499,56 @@ "node": ">= 12.9.0" } }, - "node_modules/@nestjs/cli/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/@nestjs/cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@nestjs/cli/node_modules/rimraf": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.2.tgz", - "integrity": "sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dev": true, + "dependencies": { + "glob": "^9.2.0" + }, "bin": { "rimraf": "dist/cjs/src/bin.js" }, @@ -3497,9 +3569,9 @@ } }, "node_modules/@nestjs/cli/node_modules/tsconfig-paths": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz", - "integrity": "sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "dependencies": { "json5": "^2.2.2", @@ -4282,6 +4354,31 @@ "@sinonjs/commons": "^2.0.0" } }, + "node_modules/@smithy/protocol-http": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.0.1.tgz", + "integrity": "sha512-9OrEn0WfOVtBNYJUjUAn9AOiJ4lzERCJJ/JeZs8E6yajTGxBaFRxUnNBHiNqoDJVg076hY36UmEnPx7xXrvUSg==", + "optional": true, + "dependencies": { + "@smithy/types": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.0.0.tgz", + "integrity": "sha512-kc1m5wPBHQCTixwuaOh9vnak/iJm21DrSf9UK6yDE5S3mQQ4u11pqAUiKWnlrZnYkeLfAI9UEHj9OaMT1v5Umg==", + "optional": true, + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@sqltools/formatter": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", @@ -4450,9 +4547,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, "node_modules/@types/express": { @@ -4960,148 +5057,148 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz", + "integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==", "dev": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz", + "integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==", "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz", + "integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==", "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz", + "integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz", + "integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==", "dev": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.5", + "@webassemblyjs/helper-api-error": "1.11.5", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz", + "integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==", "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz", + "integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz", + "integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==", "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz", + "integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==", "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz", + "integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==", "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz", + "integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/helper-wasm-section": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5", + "@webassemblyjs/wasm-opt": "1.11.5", + "@webassemblyjs/wasm-parser": "1.11.5", + "@webassemblyjs/wast-printer": "1.11.5" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz", + "integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/ieee754": "1.11.5", + "@webassemblyjs/leb128": "1.11.5", + "@webassemblyjs/utf8": "1.11.5" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz", + "integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-buffer": "1.11.5", + "@webassemblyjs/wasm-gen": "1.11.5", + "@webassemblyjs/wasm-parser": "1.11.5" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz", + "integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.11.5", + "@webassemblyjs/helper-api-error": "1.11.5", + "@webassemblyjs/helper-wasm-bytecode": "1.11.5", + "@webassemblyjs/ieee754": "1.11.5", + "@webassemblyjs/leb128": "1.11.5", + "@webassemblyjs/utf8": "1.11.5" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz", + "integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.11.5", "@xtuc/long": "4.2.2" } }, @@ -5166,11 +5263,10 @@ } }, "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "peer": true, + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "devOptional": true, "bin": { "acorn": "bin/acorn" }, @@ -5638,8 +5734,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "optional": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -5648,23 +5742,23 @@ } }, "node_modules/aws-sdk": { - "version": "2.974.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.974.0.tgz", - "integrity": "sha512-/oNslIqWT8f9MKgl4jMS+B/M0pIPjWYsXC+DeiFKSH6xMAaVDURr+Qtze5FqEz13mMroO5PDQKfHaC/Z/yoZ3w==", - "hasInstallScript": true, + "version": "2.1366.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1366.0.tgz", + "integrity": "sha512-tS7y18KHH0eq43I8WJsxVBpwOMY45FSsu6a+A/6k0bMrE4zhd1fzhPN+5coNIODaGxnqVvgw9guLg3AOe4SpaA==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", - "jmespath": "0.15.0", + "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", - "uuid": "3.3.2", - "xml2js": "0.4.19" + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.5.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 10.0.0" } }, "node_modules/aws-sdk/node_modules/buffer": { @@ -5683,12 +5777,11 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/aws-sdk/node_modules/uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/axios": { @@ -6176,6 +6269,14 @@ "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/buffer/node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -6910,9 +7011,12 @@ "integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==" }, "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, "engines": { "node": ">=0.11" }, @@ -7246,9 +7350,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz", + "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -7308,9 +7412,9 @@ } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", "dev": true }, "node_modules/escalade": { @@ -7613,18 +7717,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/espree/node_modules/eslint-visitor-keys": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", @@ -7957,19 +8049,25 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fast-xml-parser": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", - "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", + "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "optional": true, "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" - }, - "funding": { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } }, "node_modules/fastq": { @@ -8139,16 +8237,14 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "optional": true, - "peer": true, "dependencies": { "is-callable": "^1.1.3" } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", - "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", @@ -8170,13 +8266,7 @@ }, "peerDependencies": { "typescript": ">3.6.0", - "vue-template-compiler": "*", "webpack": "^5.11.0" - }, - "peerDependenciesMeta": { - "vue-template-compiler": { - "optional": true - } } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { @@ -8470,8 +8560,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "optional": true, - "peer": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -8712,8 +8800,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "optional": true, - "peer": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -8958,47 +9044,40 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", + "chalk": "^4.1.1", "cli-cursor": "^3.1.0", "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "mute-stream": "0.0.8", + "ora": "^5.4.1", "run-async": "^2.4.0", - "rxjs": "^6.6.0", + "rxjs": "^7.5.5", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", - "through": "^2.3.6" + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" } }, "node_modules/inquirer/node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, - "node_modules/inquirer/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, "node_modules/internal-slot": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", @@ -9071,8 +9150,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "optional": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -9150,8 +9227,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "optional": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -9212,6 +9287,20 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -9382,8 +9471,6 @@ "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "optional": true, - "peer": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -10222,9 +10309,9 @@ } }, "node_modules/jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", "engines": { "node": ">= 0.6.0" } @@ -10740,9 +10827,9 @@ } }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.29.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.29.0.tgz", + "integrity": "sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.13" @@ -10876,9 +10963,9 @@ } }, "node_modules/memfs": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", - "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.1.tgz", + "integrity": "sha512-UWbFJKvj5k+nETdteFndTpYxdeTMox/ULeqX5k/dpaQJCCFmj5EeKv3dBcyO2xmkRAx2vppRu5dVG7SOtsGOzA==", "dev": true, "dependencies": { "fs-monkey": "^1.0.3" @@ -10993,11 +11080,10 @@ "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/minipass": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", - "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==", - "optional": true, - "peer": true, + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "devOptional": true, "engines": { "node": ">=8" } @@ -11155,6 +11241,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -11170,61 +11258,21 @@ "node": "*" } }, - "node_modules/mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", - "optional": true, - "peer": true, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" - }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, - "node_modules/mongodb-connection-string-url/node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dependencies": { - "punycode": "^2.1.1" + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" }, "engines": { "node": ">=12" @@ -11250,67 +11298,6 @@ "node": ">=12" } }, - "node_modules/mongodb/node_modules/bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "optional": true, - "peer": true, - "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/mongodb/node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/mongodb/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "optional": true, - "peer": true - }, - "node_modules/mongodb/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/mongodb/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true, - "peer": true - }, - "node_modules/mongodb/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/mongoose": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.9.0.tgz", @@ -11781,19 +11768,6 @@ "@wry/trie": "^0.3.0" } }, - "node_modules/optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "optional": true, - "peer": true, - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -11926,6 +11900,11 @@ "node": ">=6" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12076,6 +12055,40 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-scurry": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", + "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", + "dev": true, + "dependencies": { + "lru-cache": "^9.0.0", + "minipass": "^5.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", + "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -12118,6 +12131,80 @@ "node": ">=0.12" } }, + "node_modules/pg": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.10.0.tgz", + "integrity": "sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.6.0", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz", + "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -12165,6 +12252,41 @@ "node": ">=4" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -12175,9 +12297,9 @@ } }, "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz", + "integrity": "sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -12553,6 +12675,11 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -12583,16 +12710,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12833,9 +12950,9 @@ "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -12955,9 +13072,9 @@ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -13176,6 +13293,14 @@ "node": "*" } }, + "node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -13563,9 +13688,9 @@ } }, "node_modules/terser": { - "version": "5.15.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz", - "integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -13581,16 +13706,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" }, "engines": { "node": ">= 10.13.0" @@ -13614,18 +13739,6 @@ } } }, - "node_modules/terser/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -13906,18 +14019,6 @@ } } }, - "node_modules/ts-node/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "devOptional": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ts-node/node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -13940,14 +14041,14 @@ } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.0.tgz", - "integrity": "sha512-fw/7265mIWukrSHd0i+wSwx64kYUSAKPfxRDksjKIYTxSAp9W9/xcZVBF4Kl0eqQd5eBpAQ/oQrc5RyM/0c1GQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.0.0" + "tsconfig-paths": "^4.1.2" }, "engines": { "node": ">=10.13.0" @@ -13963,9 +14064,9 @@ } }, "node_modules/tsconfig-paths-webpack-plugin/node_modules/tsconfig-paths": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz", - "integrity": "sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, "dependencies": { "json5": "^2.2.2", @@ -14079,27 +14180,25 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, "node_modules/typeorm": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.11.tgz", - "integrity": "sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==", + "version": "0.3.16", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.16.tgz", + "integrity": "sha512-wJ4Qy1oqRKNDdZiBTTaVMqwo/XxC52Q7uNPTjltPgLhvIW173bL6Iad0lhptMOsFlpixFPaUu3PNziaRBwX2Zw==", "dependencies": { - "@sqltools/formatter": "^1.2.2", - "app-root-path": "^3.0.0", + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "chalk": "^4.1.0", + "chalk": "^4.1.2", "cli-highlight": "^2.1.11", - "date-fns": "^2.28.0", - "debug": "^4.3.3", - "dotenv": "^16.0.0", - "glob": "^7.2.0", - "js-yaml": "^4.1.0", - "mkdirp": "^1.0.4", + "date-fns": "^2.29.3", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^8.1.0", + "mkdirp": "^2.1.3", "reflect-metadata": "^0.1.13", "sha.js": "^2.4.11", - "tslib": "^2.3.1", - "uuid": "^8.3.2", - "xml2js": "^0.4.23", - "yargs": "^17.3.1" + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" }, "bin": { "typeorm": "cli.js", @@ -14118,9 +14217,9 @@ "better-sqlite3": "^7.1.2 || ^8.0.0", "hdb-pool": "^0.1.6", "ioredis": "^5.0.4", - "mongodb": "^3.6.0", - "mssql": "^7.3.0", - "mysql2": "^2.2.5", + "mongodb": "^5.2.0", + "mssql": "^9.1.1", + "mysql2": "^2.2.5 || ^3.0.1", "oracledb": "^5.1.0", "pg": "^8.5.1", "pg-native": "^3.0.0", @@ -14185,10 +14284,13 @@ } } }, - "node_modules/typeorm/node_modules/argparse": { + "node_modules/typeorm/node_modules/brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } }, "node_modules/typeorm/node_modules/cliui": { "version": "8.0.1", @@ -14203,35 +14305,55 @@ "node": ">=12" } }, - "node_modules/typeorm/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/typeorm/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dependencies": { - "argparse": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typeorm/node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "node_modules/typeorm/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=4.0.0" + "node": ">=10" } }, - "node_modules/typeorm/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.5.tgz", + "integrity": "sha512-jbjfql+shJtAPrFoKxHOXip4xS+kul9W3OzfzzrqueWK2QMGon2bFH2opl6W9EagBThjEz+iysyi/swOoVfB/w==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, "engines": { - "node": ">=4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/typeorm/node_modules/yargs": { @@ -14384,6 +14506,18 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -14487,22 +14621,22 @@ } }, "node_modules/webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.80.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", + "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.13.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -14511,9 +14645,9 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.1.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -14551,18 +14685,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/webpack/node_modules/acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", @@ -14641,8 +14763,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "optional": true, - "peer": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -14876,18 +14996,21 @@ } }, "node_modules/xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dependencies": { "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, "node_modules/xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==", + "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" } diff --git a/package.json b/package.json index 0bf0a0150..0f84516f1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,11 @@ "typeorm": "ts-node --require ts-node/register ./node_modules/typeorm/cli.js -d src/datasource.ts", "generate-migration": "npm run typeorm -- migration:generate src/db/migrations/$npm_config_name", "run-migrations": "npm run typeorm migration:run", - "revert-migrations": "npm run typeorm migration:revert" + "revert-migrations": "npm run typeorm migration:revert", + "typeormTimescale": "ts-node --require ts-node/register ./node_modules/typeorm/cli.js -d src/common/persistence/timescaledb/typeorm.config.ts", + "generate-migration-timescale": "npm run typeormTimescale -- migration:generate src/common/persistence/timescaledb/migrations/$npm_config_name", + "run-migrations-timescale": "npm run typeormTimescale migration:run --transaction=each", + "revert-migrations-timescale": "npm run typeormTimescale migration:revert" }, "dependencies": { "@elastic/elasticsearch": "7.12.0", @@ -52,7 +56,7 @@ "amqp-connection-manager": "3.7.0", "amqplib": "0.8.0", "apollo-server": "3.11.1", - "aws-sdk": "2.974.0", + "aws-sdk": "2.1366.0", "axios": "0.21.4", "axios-retry": "3.1.8", "bignumber.js": "9.0.1", @@ -71,6 +75,7 @@ "graphql-upload": "13.0.0", "helmet": "4.4.1", "jwks-rsa": "2.0.3", + "moment": "^2.29.4", "mongoose": "6.9.0", "mysql2": "2.2.5", "nest-winston": "1.8.0", @@ -78,6 +83,7 @@ "passport": "0.6.0", "passport-jwt": "4.0.1", "passport-local": "1.0.0", + "pg": "8.10.0", "prom-client": "13.1.0", "redis": "3.1.2", "reflect-metadata": "0.1.13", @@ -86,11 +92,11 @@ "save": "2.5.0", "swagger-ui-express": "4.3.0", "tiny-async-pool": "1.2.0", - "typeorm": "0.3.11", + "typeorm": "0.3.16", "winston": "3.7.2" }, "devDependencies": { - "@nestjs/cli": "9.2.0", + "@nestjs/cli": "9.4.2", "@nestjs/schematics": "9.0.4", "@nestjs/testing": "9.3.12", "@types/express": "^4.17.11", diff --git a/schema.gql b/schema.gql index 7e07b9510..93ecd5d01 100644 --- a/schema.gql +++ b/schema.gql @@ -54,6 +54,27 @@ input AddLikeArgs { identifier: String! } +type AnalyticsAggregateValue { + avg: Float + count: Float + max: Float + min: Float + series: String + time: String + value: Float +} + +input AnalyticsArgs { + metric: String! + series: String + time: String +} + +input AnalyticsInput { + range: TimeRange + resolution: TimeResolutionsEnum +} + input ArtistFilters { sorting: ArtistSortingEnum! } @@ -459,6 +480,10 @@ type Collection { website: String } +input CollectionAnalyticsArgs { + series: String +} + type CollectionAsset { assets(input: CollectionAssetsRetriveCount): [CollectionAssetModel] totalCount: String @@ -546,6 +571,41 @@ input CollectionStatsFilter { paymentToken: String } +type CollectionsAnalyticsModel { + collectionIdentifier: String! + details: CollectionsDetailsModel! + floorPrice: Float! + floorPriceData(input: AnalyticsArgs): [AnalyticsAggregateValue!]! + holders: Int! + volume24h: Float + volumeData(input: AnalyticsArgs): [AnalyticsAggregateValue!]! +} + +type CollectionsAnalyticsModelEdge { + cursor: String + node: CollectionsAnalyticsModel +} + +type CollectionsAnalyticsModelPageInfo { + endCursor: String + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String +} + +type CollectionsAnalyticsResponse { + edges: [CollectionsAnalyticsModelEdge!] + pageData: PageData + pageInfo: CollectionsAnalyticsModelPageInfo +} + +type CollectionsDetailsModel { + collectionIdentifier: String! + collectionName: String! + items: Int! + owner: String! +} + input CollectionsFilter { """Flag for active last 30 days""" activeLast30Days: Boolean @@ -688,6 +748,15 @@ input FlagNftInput { nsfwFlag: Float! } +type GeneralAnalyticsModel { + collections: Int! + holders: Int! + listing(input: AnalyticsInput!): [AnalyticsAggregateValue!]! + marketplaces: Int! + nfts(input: AnalyticsInput!): [AnalyticsAggregateValue!]! + volume(input: AnalyticsInput!): [AnalyticsAggregateValue!]! +} + enum GroupBy { IDENTIFIER } @@ -1165,12 +1234,14 @@ type Query { campaigns(filters: CampaignsFilter, pagination: ConnectionArgs): CampaignsResponse! collectionStats(filters: CollectionStatsFilter!): CollectionStats! collections(filters: CollectionsFilter, pagination: ConnectionArgs, sorting: CollectionsSortingEnum): CollectionResponse! + collectionsAnalytics(input: CollectionAnalyticsArgs, pagination: ConnectionArgs): CollectionsAnalyticsResponse! currentPaymentTokens(filters: CurrentPaymentTokensFilters): [Token!]! exploreCollectionsStats: ExploreCollectionsStats! exploreNftsStats: ExploreNftsStats! exploreStats: ExploreStats! featuredCollections(filters: FeaturedCollectionsFilter, pagination: ConnectionArgs): CollectionResponse! featuredNfts(pagination: ConnectionArgs): AssetsResponse! + generalAnalytics(input: AnalyticsInput): GeneralAnalyticsModel! hasClaimedTickets(collectionIdentifier: String!): Boolean! isWhitelisted: WhitelistedInfo! marketplaces(filters: MarketplaceFilters, pagination: ConnectionArgs): MarketplacesResponse! @@ -1333,6 +1404,25 @@ enum TierStatusEnum { Sold } +enum TimeRange { + ALL + DAY + HOUR + MONTH + WEEK + YEAR +} + +enum TimeResolutionsEnum { + INTERVAL_1_MINUTE + INTERVAL_10_MINUTES + INTERVAL_30_MINUTES + INTERVAL_DAY + INTERVAL_HOUR + INTERVAL_MONTH + INTERVAL_WEEK +} + type Token { activeAuctions: Int decimals: Float! diff --git a/src/analytics.indexer.ts b/src/analytics.indexer.ts new file mode 100644 index 000000000..d1618ccd0 --- /dev/null +++ b/src/analytics.indexer.ts @@ -0,0 +1,17 @@ +import { NestFactory } from '@nestjs/core'; +import BigNumber from 'bignumber.js'; +import { AnalyticsService } from './modules/analytics/analytics.service'; +import { AnalyticsModule } from './modules/analytics/analytics.module'; + +export async function run(startDateUtc: string, endDateUtc: string) { + BigNumber.config({ EXPONENTIAL_AT: [-100, 100] }); + const app = await NestFactory.create(AnalyticsModule); + const analyticsService = app.get(AnalyticsService); + await analyticsService.indexAnalyticsLogs( + parseInt(startDateUtc), + parseInt(endDateUtc), + ); + return process.exit(0); +} + +run(process.argv[2], process.argv[3]); diff --git a/src/app.module.ts b/src/app.module.ts index df651ed6a..681122593 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -37,6 +37,7 @@ import { BlacklistedCollectionsModule } from './modules/blacklist/blacklisted-co import '@multiversx/sdk-nestjs/lib/src/utils/extensions/date.extensions'; import '@multiversx/sdk-nestjs/lib/src/utils/extensions/array.extensions'; import '@multiversx/sdk-nestjs/lib/src/utils/extensions/number.extensions'; +import { TimescaleDbModule } from './common/persistence/timescaledb/timescaledb.module'; @Module({ imports: [ @@ -91,6 +92,7 @@ import '@multiversx/sdk-nestjs/lib/src/utils/extensions/number.extensions'; ArtistsModuleGraph, ExploreStatsModuleGraph, PrimarySaleModuleGraph, + TimescaleDbModule, ], }) export class AppModule {} diff --git a/src/common/persistence/persistence.service.ts b/src/common/persistence/persistence.service.ts index 3a7275a15..0c3f3a189 100644 --- a/src/common/persistence/persistence.service.ts +++ b/src/common/persistence/persistence.service.ts @@ -258,6 +258,21 @@ export class PersistenceService { ); } + async getCollectionFloorPrice( + identifier: string, + marketplaceKey: string = undefined, + paymentToken: string = mxConfig.egld, + ): Promise { + return await this.execute( + this.getCollectionStats.name, + this.collectionStatsRepository.getFloorPriceForCollection( + identifier, + marketplaceKey, + paymentToken, + ), + ); + } + async getCampaign( campaignId: string, minterAddress: string, diff --git a/src/common/persistence/timescaledb/analytics-data.getter.service.ts b/src/common/persistence/timescaledb/analytics-data.getter.service.ts new file mode 100644 index 000000000..e83c796a5 --- /dev/null +++ b/src/common/persistence/timescaledb/analytics-data.getter.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { computeTimeInterval } from 'src/utils/analytics.utils'; +import { AnalyticsArgs } from './entities/analytics.query'; +import { FloorPriceDaily, SumDaily } from './entities/sum-daily.entity'; +import { AnalyticsAggregateValue } from 'src/modules/analytics/models/analytics-aggregate-value'; + +@Injectable() +export class AnalyticsDataGetterService { + constructor( + @InjectRepository(SumDaily, 'timescaledb') + private readonly sumDaily: Repository, + @InjectRepository(FloorPriceDaily, 'timescaledb') + private readonly floorPriceDaily: Repository, + ) {} + + async getTopCollectionsDaily( + { metric, series }: AnalyticsArgs, + limit: number = 10, + offset: number = 0, + ): Promise<[AnalyticsAggregateValue[], number]> { + const query = this.sumDaily + .createQueryBuilder() + .select('sum(sum) as value') + .addSelect('series') + .addSelect('time') + .andWhere('key = :metric', { metric }) + .andWhere(`time between now() - INTERVAL '1 day' and now()`) + .orderBy('sum', 'DESC') + .groupBy('series, sum, time'); + if (series) { + query.andWhere('series = :series', { series }); + } + const [response, count] = await Promise.all([ + query.offset(offset).limit(limit).getRawMany(), + query.getCount(), + ]); + if (series && count === 0) { + return [[new AnalyticsAggregateValue({ value: 0, series: series })], 1]; + } + + return [ + response?.map((row) => + AnalyticsAggregateValue.fromTimescaleObjext(row), + ) ?? [], + count ?? 0, + ]; + } + + async getVolumeData({ + time, + series, + metric, + start, + }: AnalyticsArgs): Promise { + const [startDate, endDate] = computeTimeInterval(time, start); + const query = await this.sumDaily + .createQueryBuilder() + .select("time_bucket_gapfill('1 day', time) as timestamp") + .addSelect('sum(sum) as value') + .where('key = :metric', { metric }) + .andWhere('series = :series', { series }) + .andWhere( + endDate ? 'time BETWEEN :startDate AND :endDate' : 'time >= :startDate', + { startDate, endDate }, + ) + .orderBy('timestamp', 'ASC') + .groupBy('timestamp') + .getRawMany(); + + return ( + query?.map((row) => AnalyticsAggregateValue.fromTimescaleObjext(row)) ?? + [] + ); + } + + async getFloorPriceData({ + time, + series, + metric, + start, + }: AnalyticsArgs): Promise { + const [startDate, endDate] = computeTimeInterval(time, start); + const query = await this.floorPriceDaily + .createQueryBuilder() + .select("time_bucket_gapfill('1 day', time) as timestamp") + .addSelect('locf(min(min)) as value') + .where('key = :metric', { metric }) + .andWhere('series = :series', { series }) + .andWhere( + endDate ? 'time BETWEEN :startDate AND :endDate' : 'time >= :startDate', + { startDate, endDate }, + ) + .orderBy('timestamp', 'ASC') + .groupBy('timestamp') + .getRawMany(); + + return ( + query?.map((row) => AnalyticsAggregateValue.fromTimescaleObjext(row)) ?? + [] + ); + } +} diff --git a/src/common/persistence/timescaledb/analytics-data.setter.service.ts b/src/common/persistence/timescaledb/analytics-data.setter.service.ts new file mode 100644 index 000000000..b9410a2f0 --- /dev/null +++ b/src/common/persistence/timescaledb/analytics-data.setter.service.ts @@ -0,0 +1,87 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { XNftsAnalyticsEntity } from './entities/analytics.entity'; +import { DateUtils } from 'src/utils/date-utils'; + +@Injectable() +export class AnalyticsDataSetterService { + private pendingRecords: XNftsAnalyticsEntity[] = []; + constructor( + private readonly logger: Logger, + @InjectRepository(XNftsAnalyticsEntity, 'timescaledb') + private nftAnalyticsRepo: Repository, + ) {} + + async ingest({ data, timestamp, ingestLast }): Promise { + if (!ingestLast) { + const newRecordsToIngest = this.createRecords({ data, timestamp }); + this.pendingRecords.push(...newRecordsToIngest); + + if (this.pendingRecords.length < 20) { + return; + } + } + + try { + const query = this.nftAnalyticsRepo + .createQueryBuilder() + .insert() + .into(XNftsAnalyticsEntity) + .values(this.pendingRecords) + .orUpdate(['value'], ['timestamp', 'series', 'key']); + + await query.execute(); + + this.pendingRecords = []; + } catch (error) { + this.logger.error( + `Could not insert ${this.pendingRecords.length} records into TimescaleDb, ${error}, ${timestamp}`, + ); + throw error; + } + } + + async ingestSingleEvent({ data, timestamp }): Promise { + const newRecordsToIngest = this.createRecords({ data, timestamp }); + + try { + const query = this.nftAnalyticsRepo + .createQueryBuilder() + .insert() + .into(XNftsAnalyticsEntity) + .values(newRecordsToIngest) + .orUpdate(['value'], ['timestamp', 'series', 'key']); + + await query.execute(); + } catch (error) { + this.logger.error( + `Could not insert ${data} records into TimescaleDb, ${error}}`, + ); + throw error; + } + } + + createRecords({ data, timestamp }): XNftsAnalyticsEntity[] { + const records: XNftsAnalyticsEntity[] = []; + Object.keys(data).forEach((series) => { + Object.keys(data[series]).forEach((key) => { + if (key === 'paymentToken' || key === 'marketplaceKey') { + return; + } + const value = data[series][key].toString(); + records.push( + new XNftsAnalyticsEntity({ + series, + key, + value, + timestamp: DateUtils.getDatewithTimezoneInfo(timestamp), + paymentToken: data[series]['paymentToken'].toString(), + marketplaceKey: data[series]['marketplaceKey'].toString(), + }), + ); + }); + }); + return records; + } +} diff --git a/src/common/persistence/timescaledb/entities/analytics.entity.ts b/src/common/persistence/timescaledb/entities/analytics.entity.ts new file mode 100644 index 000000000..12aae68ec --- /dev/null +++ b/src/common/persistence/timescaledb/entities/analytics.entity.ts @@ -0,0 +1,60 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity('hyper_nfts_analytics') +export class XNftsAnalyticsEntity { + @PrimaryColumn({ nullable: false, type: 'timestamp without time zone' }) + timestamp: Date; + + @PrimaryColumn({ nullable: false, type: 'varchar' }) + series: string; + + @PrimaryColumn({ nullable: false, type: 'varchar' }) + key: string; + + @Column({ + type: 'decimal', + precision: 128, + scale: 64, + default: 0, + nullable: false, + }) + value = '0'; + + @Column({ nullable: false, type: 'varchar' }) + paymentToken: string; + + @Column({ nullable: false, type: 'varchar' }) + marketplaceKey: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + public static fromObject( + timestamp: Date, + object: Record>, + ): XNftsAnalyticsEntity[] { + const entities = Object.entries(object) + .map(([series, record]: [string, Record]) => + XNftsAnalyticsEntity.fromRecord(timestamp, record, series), + ) + .flat(1); + return entities; + } + + private static fromRecord( + timestamp: Date, + record: Record, + series?: string, + ): XNftsAnalyticsEntity[] { + const entities = Object.entries(record).map(([key, value]) => { + const entity = new XNftsAnalyticsEntity(); + entity.timestamp = timestamp; + entity.series = series; + entity.key = key; + entity.value = value; + return entity; + }); + return entities; + } +} diff --git a/src/common/persistence/timescaledb/entities/analytics.query.ts b/src/common/persistence/timescaledb/entities/analytics.query.ts new file mode 100644 index 000000000..c5c5991c4 --- /dev/null +++ b/src/common/persistence/timescaledb/entities/analytics.query.ts @@ -0,0 +1,8 @@ +export interface AnalyticsArgs { + table?: string; + series?: string; + metric: string; + time?: string; + start?: string; + bin?: string; +} diff --git a/src/common/persistence/timescaledb/entities/sum-daily.entity.ts b/src/common/persistence/timescaledb/entities/sum-daily.entity.ts new file mode 100644 index 000000000..f608bc38a --- /dev/null +++ b/src/common/persistence/timescaledb/entities/sum-daily.entity.ts @@ -0,0 +1,63 @@ +import { PrimaryColumn, ViewColumn, ViewEntity } from 'typeorm'; + +@ViewEntity({ + expression: ` + SELECT + time_bucket('1 day', timestamp) AS time, series, key, + last(value, timestamp) AS last,sum(value) AS sum + FROM "hyper_nfts_analytics" + WHERE key = 'volumeUSD' + GROUP BY time, series, key; + `, + materialized: true, + name: 'nfts_sum_daily', +}) +export class SumDaily { + @ViewColumn() + @PrimaryColumn() + time: Date = new Date(); + + @ViewColumn() + sum = '0'; + + @ViewColumn() + series?: string; + + @ViewColumn() + key?: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + +@ViewEntity({ + expression: ` + SELECT + time_bucket('1 day', timestamp) AS time, series, key, + last(value, timestamp) AS last,min(value) AS min + FROM "hyper_nfts_analytics" + WHERE key = 'floorPriceUSD' + GROUP BY time, series, key; + `, + materialized: true, + name: 'nfts_floor_price_daily', +}) +export class FloorPriceDaily { + @ViewColumn() + @PrimaryColumn() + time: Date = new Date(); + + @ViewColumn() + min = '0'; + + @ViewColumn() + series?: string; + + @ViewColumn() + key?: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/src/common/persistence/timescaledb/entities/sum-weekly.entity.ts b/src/common/persistence/timescaledb/entities/sum-weekly.entity.ts new file mode 100644 index 000000000..1e21e1428 --- /dev/null +++ b/src/common/persistence/timescaledb/entities/sum-weekly.entity.ts @@ -0,0 +1,33 @@ +import { PrimaryColumn, ViewColumn, ViewEntity } from 'typeorm'; + +@ViewEntity({ + expression: ` + SELECT + time_bucket('7 day', timestamp) AS time, series, key, + last(value, timestamp) AS last,sum(value) AS sum + FROM "hyper_nfts_analytics" + WHERE key = 'volumeUSD' + GROUP BY time, series, key; + `, + materialized: true, + database: 'timescaledb', + name: 'nfts_sum_weekly', +}) +export class SumWeekly { + @ViewColumn() + @PrimaryColumn() + time: Date = new Date(); + + @ViewColumn() + sum = '0'; + + @ViewColumn() + series?: string; + + @ViewColumn() + key?: string; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/src/common/persistence/timescaledb/migrations/1680617086428-AddAnalyticsTable.ts b/src/common/persistence/timescaledb/migrations/1680617086428-AddAnalyticsTable.ts new file mode 100644 index 000000000..2e8fce40f --- /dev/null +++ b/src/common/persistence/timescaledb/migrations/1680617086428-AddAnalyticsTable.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAnalyticsTable1680617086428 implements MigrationInterface { + name = 'AddAnalyticsTable1680617086428'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "hyper_nfts_analytics" ("timestamp" TIMESTAMP NOT NULL, "series" character varying NOT NULL, "key" character varying NOT NULL, "value" numeric(128,64) NOT NULL DEFAULT '0', "paymentToken" character varying NOT NULL, "marketplaceKey" character varying NOT NULL, CONSTRAINT "PK_d3e7ec5231d5fb833d84631482c" PRIMARY KEY ("timestamp", "series", "key"))`, + ); + + await queryRunner.query( + `SELECT create_hypertable('hyper_nfts_analytics', 'timestamp', chunk_time_interval => INTERVAL '14 days')`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "hyper_nfts_analytics"`); + } +} diff --git a/src/common/persistence/timescaledb/migrations/1686731191902-AddViews.ts b/src/common/persistence/timescaledb/migrations/1686731191902-AddViews.ts new file mode 100644 index 000000000..5f20fc7c6 --- /dev/null +++ b/src/common/persistence/timescaledb/migrations/1686731191902-AddViews.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddViews1686731191902 implements MigrationInterface { + name = 'AddViews1686731191902'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE MATERIALIZED VIEW "nfts_sum_weekly" WITH (timescaledb.continuous) AS + SELECT + time_bucket('7 day', timestamp) AS time, series, key, + last(value, timestamp) AS last,sum(value) AS sum + FROM "hyper_nfts_analytics" + WHERE key = 'volumeUSD' + GROUP BY time, series, key; + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'MATERIALIZED_VIEW', + 'nfts_sum_weekly', + "SELECT\n time_bucket('7 day', timestamp) AS time, series, key,\n last(value, timestamp) AS last,sum(value) AS sum\n FROM \"hyper_nfts_analytics\"\n WHERE key = 'volumeUSD'\n GROUP BY time, series, key;", + ], + ); + await queryRunner.query(`CREATE MATERIALIZED VIEW "nfts_sum_daily" WITH (timescaledb.continuous) AS + SELECT + time_bucket('1 day', timestamp) AS time, series, key, + last(value, timestamp) AS last,sum(value) AS sum + FROM "hyper_nfts_analytics" + WHERE key = 'volumeUSD' + GROUP BY time, series, key; + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'MATERIALIZED_VIEW', + 'nfts_sum_daily', + "SELECT\n time_bucket('1 day', timestamp) AS time, series, key,\n last(value, timestamp) AS last,sum(value) AS sum\n FROM \"hyper_nfts_analytics\"\n WHERE key = 'volumeUSD'\n GROUP BY time, series, key;", + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['MATERIALIZED_VIEW', 'nfts_sum_daily', 'public'], + ); + await queryRunner.query(`DROP MATERIALIZED VIEW "nfts_sum_daily"`); + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['MATERIALIZED_VIEW', 'nfts_sum_weekly', 'public'], + ); + await queryRunner.query(`DROP MATERIALIZED VIEW "nfts_sum_weekly"`); + } +} diff --git a/src/common/persistence/timescaledb/migrations/1687257217630-AddFloorPriceView.ts b/src/common/persistence/timescaledb/migrations/1687257217630-AddFloorPriceView.ts new file mode 100644 index 000000000..4307cc781 --- /dev/null +++ b/src/common/persistence/timescaledb/migrations/1687257217630-AddFloorPriceView.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFloorPriceView1687257217630 implements MigrationInterface { + name = 'AddFloorPriceView1687257217630'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE MATERIALIZED VIEW "nfts_floor_price_daily" WITH (timescaledb.continuous) AS + SELECT + time_bucket('1 day', timestamp) AS time, series, key, + last(value, timestamp) AS last,min(value) AS min + FROM "hyper_nfts_analytics" + WHERE key = 'floorPriceUSD' + GROUP BY time, series, key; + `); + await queryRunner.query( + `INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, + [ + 'public', + 'MATERIALIZED_VIEW', + 'nfts_floor_price_daily', + "SELECT\n time_bucket('1 day', timestamp) AS time, series, key,\n last(value, timestamp) AS last,min(value) AS min\n FROM \"hyper_nfts_analytics\"\n WHERE key = 'floorPriceUSD'\n GROUP BY time, series, key;", + ], + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, + ['MATERIALIZED_VIEW', 'nfts_floor_price_daily', 'public'], + ); + await queryRunner.query(`DROP MATERIALIZED VIEW "nfts_floor_price_daily"`); + } +} diff --git a/src/common/persistence/timescaledb/tests/analytics-data.getter.spec.ts b/src/common/persistence/timescaledb/tests/analytics-data.getter.spec.ts new file mode 100644 index 000000000..f1f7814ec --- /dev/null +++ b/src/common/persistence/timescaledb/tests/analytics-data.getter.spec.ts @@ -0,0 +1,257 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AnalyticsDataGetterService } from '../analytics-data.getter.service'; +import { FloorPriceDaily, SumDaily } from '../entities/sum-daily.entity'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { AnalyticsAggregateValue } from 'src/modules/analytics/models/analytics-aggregate-value'; + +describe('Analytics Data Getter Service', () => { + let service: AnalyticsDataGetterService; + let module: TestingModule; + const offset = jest.fn().mockReturnThis(); + const limit = jest.fn().mockReturnThis(); + const select = jest.fn().mockReturnThis(); + const addSelect = jest.fn().mockReturnThis(); + const andWhere = jest.fn().mockReturnThis(); + const where = jest.fn().mockReturnThis(); + const orderBy = jest.fn().mockReturnThis(); + const groupBy = jest.fn().mockReturnThis(); + + beforeEach(async () => { + module = await Test.createTestingModule({ + providers: [ + AnalyticsDataGetterService, + { + provide: getRepositoryToken(SumDaily, 'timescaledb'), + useFactory: () => ({}), + }, + { + provide: getRepositoryToken(FloorPriceDaily, 'timescaledb'), + useFactory: () => ({}), + }, + ], + }).compile(); + + service = module.get( + AnalyticsDataGetterService, + ); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('getTopCollectionsDaily', () => { + it('returns empty list when no data and no key present', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([]); + const getCount = jest.fn().mockReturnValueOnce(0); + const sumDailyRepository = module.get( + getRepositoryToken(SumDaily, 'timescaledb'), + ); + sumDailyRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + andWhere, + orderBy, + groupBy, + getCount, + })); + const result = await service.getTopCollectionsDaily({ metric: 'test' }); + const expectedResult = [[], 0]; + + expect(result).toMatchObject(expectedResult); + }); + + it('returns list with one item when no data and the searched key', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([]); + const getCount = jest.fn().mockReturnValueOnce(0); + const sumDailyRepository = module.get( + getRepositoryToken(SumDaily, 'timescaledb'), + ); + sumDailyRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + andWhere, + orderBy, + groupBy, + getCount, + })); + const result = await service.getTopCollectionsDaily({ + metric: 'test', + series: 'test', + }); + const expectedResult = [ + [new AnalyticsAggregateValue({ value: 0, series: 'test' })], + 1, + ]; + + expect(result).toMatchObject(expectedResult); + }); + + it('returns list with multiple items and expected values', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([ + { value: 2, series: 'test' }, + { value: 121, series: 'test2' }, + ]); + const getCount = jest.fn().mockReturnValueOnce(2); + const sumDailyRepository = module.get( + getRepositoryToken(SumDaily, 'timescaledb'), + ); + sumDailyRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + andWhere, + where, + orderBy, + groupBy, + getCount, + })); + + const [responseList, responseCount] = + await service.getTopCollectionsDaily({ + metric: 'test', + }); + + const [expectedList, expectedCount] = [ + [ + new AnalyticsAggregateValue({ value: 2, series: 'test' }), + new AnalyticsAggregateValue({ value: 121, series: 'test2' }), + ], + 2, + ]; + + expect(responseCount).toBe(expectedCount); + expect(responseList).toMatchObject(expectedList); + }); + }); + + describe('getFloorPriceData', () => { + it('returns empty list when no data for specific collection', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([]); + const floorPriceRepository = module.get( + getRepositoryToken(FloorPriceDaily, 'timescaledb'), + ); + floorPriceRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + andWhere, + where, + orderBy, + groupBy, + })); + const result = await service.getFloorPriceData({ + time: '10d', + metric: 'test', + series: 'test', + }); + const expectedResult = []; + + expect(result).toMatchObject(expectedResult); + }); + + it('returns list with multiple items and expected values', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([ + { value: 2, series: 'test' }, + { value: 121, series: 'test' }, + ]); + const expectedResult = [ + new AnalyticsAggregateValue({ value: 2, series: 'test' }), + new AnalyticsAggregateValue({ value: 121, series: 'test' }), + ]; + const floorPriceRepository = module.get( + getRepositoryToken(FloorPriceDaily, 'timescaledb'), + ); + floorPriceRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + where, + andWhere, + orderBy, + groupBy, + })); + + const response = await service.getFloorPriceData({ + time: '10d', + metric: 'test', + series: 'test', + }); + + expect(response).toMatchObject(expectedResult); + }); + }); + + describe('getVolumeData', () => { + it('returns empty list when no data for specific collection', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([]); + const sumDailyRepository = module.get( + getRepositoryToken(SumDaily, 'timescaledb'), + ); + sumDailyRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + andWhere, + where, + orderBy, + groupBy, + })); + const result = await service.getVolumeData({ + time: '10d', + metric: 'test', + series: 'test', + }); + const expectedResult = []; + + expect(result).toMatchObject(expectedResult); + }); + + it('returns list with multiple items and expected values', async () => { + const getRawMany = jest.fn().mockReturnValueOnce([ + { value: 2, series: 'test' }, + { value: 121, series: 'test' }, + ]); + const expectedResult = [ + new AnalyticsAggregateValue({ value: 2, series: 'test' }), + new AnalyticsAggregateValue({ value: 121, series: 'test' }), + ]; + const sumDailyRepository = module.get( + getRepositoryToken(SumDaily, 'timescaledb'), + ); + sumDailyRepository.createQueryBuilder = jest.fn(() => ({ + select, + offset, + limit, + getRawMany, + addSelect, + where, + andWhere, + orderBy, + groupBy, + })); + + const response = await service.getVolumeData({ + time: '10d', + metric: 'test', + series: 'test', + }); + + expect(response).toMatchObject(expectedResult); + }); + }); +}); diff --git a/src/common/persistence/timescaledb/tests/analytics-data.setter.spec.ts b/src/common/persistence/timescaledb/tests/analytics-data.setter.spec.ts new file mode 100644 index 000000000..d682cf6ee --- /dev/null +++ b/src/common/persistence/timescaledb/tests/analytics-data.setter.spec.ts @@ -0,0 +1,157 @@ +import { Test } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { AnalyticsDataSetterService } from '../analytics-data.setter.service'; +import { XNftsAnalyticsEntity } from '../entities/analytics.entity'; +import { Logger } from '@nestjs/common'; +import '@multiversx/sdk-nestjs/lib/src/utils/extensions/array.extensions'; + +describe('Analytics Data Setter Service', () => { + let service: AnalyticsDataSetterService; + let nftAnalyticsRepository: any; + const insert = jest.fn().mockReturnThis(); + const into = jest.fn().mockReturnThis(); + const values = jest.fn().mockReturnThis(); + const orUpdate = jest.fn().mockReturnThis(); + const validInputData = { + data: { + 'QWTCOINS-27203b': { + usdPrice: 37.64373293576205, + volume: 0.367, + volumeUSD: '13.81524998742467235', + paymentToken: 'EGLD', + marketplaceKey: 'xoxno', + }, + }, + timestamp: 12121, + ingestLast: false, + }; + + const invalidInputData = { + data: 'test', + timestamp: 12121, + ingestLast: false, + }; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + providers: [ + AnalyticsDataSetterService, + Logger, + { + provide: getRepositoryToken(XNftsAnalyticsEntity, 'timescaledb'), + useFactory: () => ({ + save: jest.fn(() => true), + }), + }, + ], + }).compile(); + + service = module.get( + AnalyticsDataSetterService, + ); + nftAnalyticsRepository = module.get( + getRepositoryToken(XNftsAnalyticsEntity, 'timescaledb'), + ); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('ingest', () => { + it('throws error when input data not in correct format', async () => { + const execute = jest.fn(() => {}); + nftAnalyticsRepository.createQueryBuilder = jest.fn(() => ({ + insert, + into, + values, + orUpdate, + execute, + })); + + await expect(service.ingest(invalidInputData)).rejects.toThrowError( + TypeError, + ); + }); + + it('throws error when execute fails and ingestlast true', async () => { + const execute = jest.fn(() => { + throw new Error(); + }); + nftAnalyticsRepository.createQueryBuilder = jest.fn(() => ({ + insert, + into, + values, + orUpdate, + execute, + })); + await expect( + service.ingest({ ...validInputData, ingestLast: true }), + ).rejects.toThrowError(Error); + }); + }); + + it('returns undefined when ingestlast false and we have less the 20 data events', async () => { + const execute = jest.fn(() => { + throw new Error(); + }); + nftAnalyticsRepository.createQueryBuilder = jest.fn(() => ({ + insert, + into, + values, + orUpdate, + execute, + })); + + await expect(service.ingest(validInputData)).resolves.toBeUndefined(); + }); + + describe('ingestSingleEvent', () => { + it('throws error when input data not in correct format', async () => { + const execute = jest.fn(() => {}); + nftAnalyticsRepository.createQueryBuilder = jest.fn(() => ({ + insert, + into, + values, + orUpdate, + execute, + })); + + await expect( + service.ingestSingleEvent(invalidInputData), + ).rejects.toThrowError(TypeError); + }); + + it('throws error when execute fails', async () => { + const execute = jest.fn(() => { + throw new Error(); + }); + nftAnalyticsRepository.createQueryBuilder = jest.fn(() => ({ + insert, + into, + values, + orUpdate, + execute, + })); + + await expect( + service.ingestSingleEvent({ ...validInputData }), + ).rejects.toThrowError(Error); + }); + + it('does not throw error when valid input', async () => { + const execute = jest.fn().mockReturnValueOnce({}); + nftAnalyticsRepository.createQueryBuilder = jest.fn(() => ({ + insert, + into, + values, + orUpdate, + execute, + })); + + await expect( + service.ingestSingleEvent(validInputData), + ).resolves.not.toThrow(); + }); + }); +}); diff --git a/src/common/persistence/timescaledb/timescaledb.module.ts b/src/common/persistence/timescaledb/timescaledb.module.ts new file mode 100644 index 000000000..92313b28b --- /dev/null +++ b/src/common/persistence/timescaledb/timescaledb.module.ts @@ -0,0 +1,45 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CommonModule } from 'src/common.module'; +import { CacheModule } from 'src/common/services/caching/caching.module'; +import { ApiConfigService } from 'src/modules/common/api-config/api.config.service'; +import { AnalyticsDataGetterService } from './analytics-data.getter.service'; +import { AnalyticsDataSetterService } from './analytics-data.setter.service'; +import { XNftsAnalyticsEntity } from './entities/analytics.entity'; +import { FloorPriceDaily, SumDaily } from './entities/sum-daily.entity'; +import { SumWeekly } from './entities/sum-weekly.entity'; + +@Module({ + imports: [ + CommonModule, + CacheModule, + TypeOrmModule.forRootAsync({ + imports: [CommonModule], + name: 'timescaledb', + useFactory: (apiConfig: ApiConfigService) => ({ + autoLoadEntities: true, + type: 'postgres', + host: apiConfig.getTimescaleDbHost(), + port: apiConfig.getTimescaleDbPort(), + database: apiConfig.getTimescaleDbDatabase(), + username: apiConfig.getTimescaleDbUsername(), + password: apiConfig.getTimescaleDbPassword(), + ssl: true, + extra: { + ssl: { + rejectUnauthorized: false, + }, + }, + entities: ['dist/**/**/**/**/*.entity{.ts,.js}'], + }), + inject: [ApiConfigService], + }), + TypeOrmModule.forFeature( + [XNftsAnalyticsEntity, SumDaily, SumWeekly, FloorPriceDaily], + 'timescaledb', + ), + ], + providers: [AnalyticsDataGetterService, AnalyticsDataSetterService], + exports: [AnalyticsDataGetterService, AnalyticsDataSetterService], +}) +export class TimescaleDbModule {} diff --git a/src/common/persistence/timescaledb/typeorm.config.ts b/src/common/persistence/timescaledb/typeorm.config.ts new file mode 100644 index 000000000..eff5eebe6 --- /dev/null +++ b/src/common/persistence/timescaledb/typeorm.config.ts @@ -0,0 +1,19 @@ +import { DataSource } from 'typeorm'; +import * as dotenv from 'dotenv'; +import { ConfigService } from '@nestjs/config'; + +dotenv.config(); + +const configService = new ConfigService(); + +export default new DataSource({ + type: 'postgres', + host: configService.get('TIMESCALEDB_URL'), + port: configService.get('TIMESCALEDB_PORT'), + username: configService.get('TIMESCALEDB_USERNAME'), + password: configService.get('TIMESCALEDB_PASSWORD'), + database: configService.get('TIMESCALEDB_DATABASE'), + migrationsTransactionMode: 'none', + entities: ['dist/common/**/**/entities/*.entity.{js,ts}'], + migrations: ['dist/common/persistence/timescaledb/migrations/*.js'], +}); diff --git a/src/common/services/caching/entities/cache.info.ts b/src/common/services/caching/entities/cache.info.ts index b1394a26e..fc12c5873 100644 --- a/src/common/services/caching/entities/cache.info.ts +++ b/src/common/services/caching/entities/cache.info.ts @@ -193,4 +193,34 @@ export class CacheInfo { key: 'nftScamInfo', ttl: Constants.oneDay(), }; + + static NftAnalytic24hCount: CacheInfo = { + key: 'nftAnalytic24hCount', + ttl: 10 * Constants.oneMinute(), + }; + + static NftAnalytic24hListing: CacheInfo = { + key: 'nftAnalytic24hListing', + ttl: 10 * Constants.oneMinute(), + }; + + static CollectionFloorPrice: CacheInfo = { + key: 'collectionFloorPrice', + ttl: 30 * Constants.oneMinute(), + }; + + static NftAnalyticsCount: CacheInfo = { + key: 'nftAnalyticsCount', + ttl: 10 * Constants.oneMinute(), + }; + + static NftsHolders: CacheInfo = { + key: 'nftsHolders', + ttl: 10 * Constants.oneMinute(), + }; + + static CollectionDetails: CacheInfo = { + key: 'collectionDetails', + ttl: 5 * Constants.oneMinute(), + }; } diff --git a/src/common/services/mx-communication/mx-api.service.ts b/src/common/services/mx-communication/mx-api.service.ts index 0cd571527..ea39f0b06 100644 --- a/src/common/services/mx-communication/mx-api.service.ts +++ b/src/common/services/mx-communication/mx-api.service.ts @@ -633,9 +633,9 @@ export class MxApiService { ); return token ? new Token({ - ...token, - symbol: token.ticker, - }) + ...token, + symbol: token.ticker, + }) : undefined; } diff --git a/src/common/services/mx-communication/mx-communication.module.ts b/src/common/services/mx-communication/mx-communication.module.ts index 71ed4e4d6..a58537836 100644 --- a/src/common/services/mx-communication/mx-communication.module.ts +++ b/src/common/services/mx-communication/mx-communication.module.ts @@ -11,6 +11,7 @@ import { SlackReportService } from './slack-report.service'; import { ConfigService } from '@nestjs/config'; import { ApiConfigService } from 'src/modules/common/api-config/api.config.service'; import { MxDataApiService } from './mx-data.service'; +import { MxToolsService } from './mx-tools.service'; @Module({ providers: [ @@ -27,6 +28,7 @@ import { MxDataApiService } from './mx-data.service'; MxFeedService, SlackReportService, MxDataApiService, + MxToolsService ], exports: [ ApiService, @@ -39,6 +41,7 @@ import { MxDataApiService } from './mx-data.service'; MxFeedService, SlackReportService, MxDataApiService, + MxToolsService, ], }) -export class MxCommunicationModule {} +export class MxCommunicationModule { } diff --git a/src/common/services/mx-communication/mx-data.service.ts b/src/common/services/mx-communication/mx-data.service.ts index 4d1567e0d..7b19c1d67 100644 --- a/src/common/services/mx-communication/mx-data.service.ts +++ b/src/common/services/mx-communication/mx-data.service.ts @@ -12,14 +12,14 @@ export class MxDataApiService { private readonly apiConfigService: ApiConfigService, private readonly apiService: ApiService, ) { - this.url = this.apiConfigService.getToolsUrl(); + this.url = this.apiConfigService.getPublicDataApi(); } async getCexPrice(timestamp: string): Promise { let requestUrl = `${this.url}/v1/quotes/cex/${mxConfig.egld}?fields=price&date=${timestamp}`; try { - let response = await this.apiService.get(requestUrl); + let response = await this.apiService.get(requestUrl, this.getHeaders()); return response.data.price; } catch (error) { this.logger.error( @@ -37,7 +37,8 @@ export class MxDataApiService { const requestUrl = `${this.url}/v1/tokens/cex?fields=identifier`; try { let response = await this.apiService.get(requestUrl); - return response?.data?.identifier; + + return response?.data?.map((x) => x.identifier); } catch (error) { this.logger.error( `An error occurred while calling the mx data service on url ${requestUrl}`, @@ -54,7 +55,7 @@ export class MxDataApiService { const requestUrl = `${this.url}/v1/tokens/xexchange?fields=identifier`; try { let response = await this.apiService.get(requestUrl); - return response?.data?.identifier; + return response?.data?.map((x) => x.identifier); } catch (error) { this.logger.error( `An error occurred while calling the mx data service on url ${requestUrl}`, @@ -73,7 +74,8 @@ export class MxDataApiService { ): Promise { const requestUrl = `${this.url}/v1/quotes/xexchange/${token}?date=${isoDateOnly}&fields=price`; try { - let response = await this.apiService.get(requestUrl); + let response = await this.apiService.get(requestUrl, this.getHeaders()); + return response.data.price; } catch (error) { this.logger.error( @@ -86,4 +88,12 @@ export class MxDataApiService { return; } } + + private getHeaders() { + if (this.apiConfigService.getDataToolsApiKey()) + return { + apiKey: this.apiConfigService.getDataToolsApiKey(), + }; + return {}; + } } diff --git a/src/common/services/mx-communication/mx-elastic.service.ts b/src/common/services/mx-communication/mx-elastic.service.ts index 09ec28ce8..158613756 100644 --- a/src/common/services/mx-communication/mx-elastic.service.ts +++ b/src/common/services/mx-communication/mx-elastic.service.ts @@ -312,6 +312,81 @@ export class MxElasticService { ); } + async getHoldersCount(): Promise { + const query = { + size: 0, + query: { + bool: { + should: [ + { + match: { + type: 'NonFungibleESDT', + }, + }, + { + match: { + type: 'SemiFungibleESDT', + }, + }, + ], + }, + }, + aggs: { + unique_addresses: { + cardinality: { + field: 'address', + }, + }, + }, + }; + + const resultRaw = await this.apiService.post( + `${this.url}/accountsesdt/_search`, + query, + { + timeout: 120000, + }, + ); + const result = resultRaw?.data?.aggregations?.unique_addresses?.value; + return result as number; + } + + async getHoldersCountForCollection( + collectionIdentifier: string, + ): Promise { + const query = { + size: 0, + query: { + bool: { + should: [ + { + match: { + token: `${collectionIdentifier}`, + }, + }, + ], + }, + }, + aggs: { + unique_addresses: { + cardinality: { + field: 'address', + }, + }, + }, + }; + + const resultRaw = await this.apiService.post( + `${this.url}/accountsesdt/_search`, + query, + { + timeout: 120000, + }, + ); + const result = resultRaw?.data?.aggregations?.unique_addresses?.value; + return result as number; + } + buildUpdateBody(fieldName: string, fieldValue: T): any { return { doc: { diff --git a/src/common/services/mx-communication/mx-tools.service.ts b/src/common/services/mx-communication/mx-tools.service.ts new file mode 100644 index 000000000..7179d955f --- /dev/null +++ b/src/common/services/mx-communication/mx-tools.service.ts @@ -0,0 +1,151 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { mxConfig } from 'src/config'; +import { NativeAuthSigner } from '@multiversx/sdk-nestjs/lib/src/utils/native.auth.signer'; +import { ApiService } from './api.service'; +import { ApiSettings } from './models/api-settings'; +import { getFilePathFromDist } from 'src/utils/helpers'; +import { ApiConfigService } from 'src/modules/common/api-config/api.config.service'; +import { AnalyticsInput } from 'src/modules/analytics/models/analytics-input.model'; +import { AnalyticsAggregateValue } from 'src/modules/analytics/models/analytics-aggregate-value'; + +@Injectable() +export class MxToolsService { + private url: string; + private nativeAuthSigner: NativeAuthSigner; + + constructor( + private readonly logger: Logger, + private readonly apiConfigService: ApiConfigService, + private readonly apiService: ApiService, + ) { + this.url = this.apiConfigService.getToolsUrl(); + this.nativeAuthSigner = new NativeAuthSigner({ + origin: 'NftService', + apiUrl: this.apiConfigService.getApiUrl(), + signerPrivateKeyPath: getFilePathFromDist(mxConfig.pemFileName), + }); + } + + async getNftsCount( + input: AnalyticsInput, + ): Promise { + try { + const query = this.getNftsCountQuery(input); + const res = await this.doPost(this.getActiveNftsStats.name, query); + return res.data?.nfts?.count.map((x) => + AnalyticsAggregateValue.fromDataApi(x), + ); + } catch (error) { + this.logger.error(`An error occurred while mapping data api response`, { + path: this.getNftsCount.name, + input, + exception: error, + }); + return; + } + } + + async getActiveNftsStats( + input: AnalyticsInput, + ): Promise { + try { + const query = this.getLatestListingNumber(input); + const res = await this.doPost(this.getActiveNftsStats.name, query); + return res.data?.nfts?.active_nfts.map((x) => + AnalyticsAggregateValue.fromDataApi(x), + ); + } catch (error) { + this.logger.error(`An error occurred while mapping data api response`, { + path: this.getActiveNftsStats.name, + input, + exception: error, + }); + return; + } + } + + async getLast24HActive( + input: AnalyticsInput, + ): Promise { + try { + const query = this.getNftsCountLast24h(input); + const res = await this.doPost(this.getActiveNftsStats.name, query); + return res.data?.nfts?.count24h.map((x) => + AnalyticsAggregateValue.fromDataApi(x), + ); + } catch (error) { + this.logger.error(`An error occurred while mapping data api response`, { + path: this.getLast24HActive.name, + input, + exception: error, + }); + return; + } + } + + private getNftsCountQuery(input: AnalyticsInput): string { + return `{ + nfts { + count(query: { range: ${input.range}, resolution: ${input.resolution} }) { + time + avg + count + max + min + sum + } + } + }`; + } + + private getNftsCountLast24h(input: AnalyticsInput): string { + return `{ + nfts { + count24h(query: { range: ${input.range}, resolution: ${input.resolution} }) { + time + avg + count + max + min + sum + } + } + }`; + } + + private getLatestListingNumber(input: AnalyticsInput): string { + return `{ + nfts { + active_nfts(query: { range: ${input.range}, resolution: ${input.resolution}}) { + time + avg + count + max + min + sum + } + } + }`; + } + + private async getConfig(): Promise { + const accessTokenInfo = await this.nativeAuthSigner.getToken(); + return { + authorization: `Bearer ${accessTokenInfo.token}`, + timeout: 500, + }; + } + + private async doPost(name: string, query: any): Promise { + try { + const config = await this.getConfig(); + const response = await this.apiService.post(this.url, { query }, config); + return response.data; + } catch (error) { + this.logger.error(`Error when trying to get run ${name}`, { + error: error.message, + path: `${MxToolsService.name}.${this.doPost.name}`, + }); + } + } +} diff --git a/src/crons/cache.warmer/trendingCollections.warmer.service.ts b/src/crons/cache.warmer/trendingCollections.warmer.service.ts index ca30f777b..43f184457 100644 --- a/src/crons/cache.warmer/trendingCollections.warmer.service.ts +++ b/src/crons/cache.warmer/trendingCollections.warmer.service.ts @@ -2,14 +2,14 @@ import { Inject, Injectable } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; import { ClientProxy } from '@nestjs/microservices'; -import { AnalyticsService } from 'src/modules/analytics/analytics.service'; import { CachingService, Locker } from '@multiversx/sdk-nestjs'; +import { TrendingCollectionsService } from 'src/modules/analytics/trending/trending-collections.service'; @Injectable() export class TrendingCollectionsWarmerService { constructor( @Inject('PUBSUB_SERVICE') private clientProxy: ClientProxy, - private analytics: AnalyticsService, + private trendingCollectionsService: TrendingCollectionsService, private cacheService: CachingService, ) {} @@ -18,9 +18,10 @@ export class TrendingCollectionsWarmerService { await Locker.lock( 'Trending Collections invalidations', async () => { - const tokens = await this.analytics.reindexTrendingCollections( - forTheLastHours, - ); + const tokens = + await this.trendingCollectionsService.reindexTrendingCollections( + forTheLastHours, + ); await this.invalidateKey( CacheInfo.TrendingByVolume.key, diff --git a/src/crons/elastic.updater/elastic-rarity.updater.module.ts b/src/crons/elastic.updater/elastic-rarity.updater.module.ts index 98c52c954..bc1486526 100644 --- a/src/crons/elastic.updater/elastic-rarity.updater.module.ts +++ b/src/crons/elastic.updater/elastic-rarity.updater.module.ts @@ -1,6 +1,5 @@ import { Logger, Module } from '@nestjs/common'; import { CommonModule } from 'src/common.module'; -import { CacheModule } from 'src/common/services/caching/caching.module'; import { NftRarityModuleGraph } from 'src/modules/nft-rarity/nft-rarity.module'; import { ElasticRarityUpdaterService } from './elastic-rarity.updater.service'; import { RarityUpdaterService } from './rarity.updater.service'; diff --git a/src/db/collection-stats/collection-stats.querries.ts b/src/db/collection-stats/collection-stats.querries.ts index 65aeb0e86..4664f7b45 100644 --- a/src/db/collection-stats/collection-stats.querries.ts +++ b/src/db/collection-stats/collection-stats.querries.ts @@ -37,3 +37,20 @@ export function getCollectionStats( ) temp `; } + + +export function getCollectionFloorPrice( + identifier: string, + marketplaceKey: string = undefined, + paymentToken: string = mxConfig.egld, +) { + return ` + SELECT MIN(if(o.priceAmountDenominated, o.priceAmountDenominated , a.minBidDenominated)) AS minPrice + FROM auctions a + LEFT JOIN orders o ON o.auctionId = a.id AND o.status ='Active' + WHERE a.collection = '${identifier}' AND a.status = 'Running' AND a.paymentToken='${paymentToken}' ${getMarketplaceKeyFilter( + 'a', + marketplaceKey, + )} + `; +} diff --git a/src/db/collection-stats/collection-stats.repository.ts b/src/db/collection-stats/collection-stats.repository.ts index 7d62e1c99..27d11a4ee 100644 --- a/src/db/collection-stats/collection-stats.repository.ts +++ b/src/db/collection-stats/collection-stats.repository.ts @@ -17,4 +17,15 @@ export class CollectionStatsRepository { ); return response?.length > 0 ? response[0] : new CollectionStatsEntity(); } + + async getFloorPriceForCollection( + identifier: string, + marketplaceKey: string = undefined, + paymentToken: string = mxConfig.egld, + ): Promise { + const response = await this.manager.query( + getCollectionStats(identifier, marketplaceKey, paymentToken), + ); + return response?.length > 0 ? response[0]?.minPrice ?? 0 : 0; + } } diff --git a/src/modules/analytics/analytics.getter.service.ts b/src/modules/analytics/analytics.getter.service.ts new file mode 100644 index 000000000..88d65e31f --- /dev/null +++ b/src/modules/analytics/analytics.getter.service.ts @@ -0,0 +1,89 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { CachingService, Constants } from '@multiversx/sdk-nestjs'; +import { AnalyticsDataGetterService } from 'src/common/persistence/timescaledb/analytics-data.getter.service'; +import { generateCacheKeyFromParams } from 'src/utils/generate-cache-key'; +import { AnalyticsArgs } from 'src/common/persistence/timescaledb/entities/analytics.query'; +import { AnalyticsAggregateValue } from './models/analytics-aggregate-value'; + +@Injectable() +export class AnalyticsGetterService { + constructor( + protected readonly cachingService: CachingService, + protected readonly logger: Logger, + private readonly analyticsQuery: AnalyticsDataGetterService, + ) {} + + async getVolumeDataForTimePeriod( + time: string, + series: string, + metric: string, + ): Promise { + const cacheKey = this.getAnalyticsCacheKey( + 'volumeData', + time, + series, + metric, + ); + return await this.cachingService.getOrSetCache( + cacheKey, + () => + this.analyticsQuery.getVolumeData({ + series, + metric, + time, + }), + Constants.oneMinute() * 2, + ); + } + + async getFloorPriceForTimePeriod( + time: string, + series: string, + metric: string, + ): Promise { + const cacheKey = this.getAnalyticsCacheKey( + 'floorPriceData', + time, + series, + metric, + ); + return await this.cachingService.getOrSetCache( + cacheKey, + () => + this.analyticsQuery.getFloorPriceData({ + series, + metric, + time, + }), + Constants.oneMinute() * 2, + ); + } + + async getTopCollectionsDaily( + { metric, series }: AnalyticsArgs, + limit: number = 10, + offset: number = 0, + ): Promise<[AnalyticsAggregateValue[], number]> { + const cacheKey = this.getAnalyticsCacheKey( + 'getTopCollectionsDaily', + metric, + limit, + offset, + series, + ); + return await this.cachingService.getOrSetCache( + cacheKey, + () => + this.analyticsQuery.getTopCollectionsDaily( + { metric, series }, + limit, + offset, + ), + Constants.oneMinute() * 2, + ); + } + + private getAnalyticsCacheKey(...args: any) { + return generateCacheKeyFromParams('analytics', ...args); + } +} diff --git a/src/modules/analytics/analytics.module.ts b/src/modules/analytics/analytics.module.ts index eb3254020..434405ad0 100644 --- a/src/modules/analytics/analytics.module.ts +++ b/src/modules/analytics/analytics.module.ts @@ -4,25 +4,68 @@ import { MarketplacesService } from '../marketplaces/marketplaces.service'; import { MarketplacesCachingService } from '../marketplaces/marketplaces-caching.service'; import { AuctionsModuleGraph } from '../auctions/auctions.module'; import { CommonModule } from 'src/common.module'; -import { AnalyticsService } from './analytics.service'; -import { BuyEventParser } from './buy-event.parser'; +import { TrendingCollectionsService } from './trending/trending-collections.service'; +import { BuyEventParser } from './trending/buy-event.parser'; import { ElasticAnalyticsService } from './elastic.indexer.service'; -import { AcceptOfferEventParser } from './acceptOffer-event.parser'; +import { AcceptOfferEventParser } from './trending/acceptOffer-event.parser'; +import { AnalyticsGetterService } from './analytics.getter.service'; +import { TimescaleDbModule } from 'src/common/persistence/timescaledb/timescaledb.module'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import * as ormconfig from 'src/ormconfig'; +import { AnalyticsService } from './analytics.service'; +import { AcceptOfferEventAnalyticsParser } from './events-parsers/acceptOffer-event-analytics.parser'; +import { BuyEventAnalyticsParser } from './events-parsers/buy-event-analytics.parser'; +import { GeneralAnalyticsResolver } from './general-analytics.resolver'; +import { CollectionsModuleGraph } from '../nftCollections/collections.module'; +import { GeneralAnalyticsService } from './general-analytics.service'; +import { CollectionsAnalyticsResolver } from './collections-analytics.resolver'; +import { CollectionsAnalyticsService } from './collections-analytics.service'; +import { CollectionDetailsProvider } from './loaders/collection-details.loader'; +import { CollectionDetailsRedisHandler } from './loaders/collection-details.redis-handler'; +import { ListingAuctionAnalyticsHandler } from './events-parsers/listing-event-analytics.parser'; +import { UpdateListingEventParser } from './events-parsers/updateListing-event.parser'; +import { UpdatePriceEventParser } from './events-parsers/updatePrice-event.parser'; @Module({ providers: [ MarketplacesService, MarketplacesCachingService, - AnalyticsService, + TrendingCollectionsService, ElasticAnalyticsService, BuyEventParser, AcceptOfferEventParser, + AnalyticsGetterService, + AnalyticsService, + AcceptOfferEventAnalyticsParser, + BuyEventAnalyticsParser, + ListingAuctionAnalyticsHandler, + GeneralAnalyticsResolver, + GeneralAnalyticsService, + CollectionsAnalyticsResolver, + CollectionsAnalyticsService, + CollectionDetailsProvider, + CollectionDetailsRedisHandler, + UpdatePriceEventParser, + UpdateListingEventParser, ], imports: [ + ConfigModule.forRoot({ + isGlobal: true, + }), + TypeOrmModule.forRoot({ ...ormconfig, keepConnectionAlive: true }), MxCommunicationModule, CommonModule, forwardRef(() => AuctionsModuleGraph), + forwardRef(() => CollectionsModuleGraph), + TimescaleDbModule, + ], + exports: [ + TrendingCollectionsService, + ElasticAnalyticsService, + AnalyticsService, + GeneralAnalyticsService, + CollectionsAnalyticsService, ], - exports: [AnalyticsService, ElasticAnalyticsService], }) export class AnalyticsModule {} diff --git a/src/modules/analytics/analytics.service.ts b/src/modules/analytics/analytics.service.ts index 18c8d263a..d97ecec86 100644 --- a/src/modules/analytics/analytics.service.ts +++ b/src/modules/analytics/analytics.service.ts @@ -1,50 +1,45 @@ import { Injectable, Logger } from '@nestjs/common'; -import { PerformanceProfiler } from '../metrics/performance.profiler'; import * as moment from 'moment'; import { ElasticAnalyticsService } from './elastic.indexer.service'; -import { trendingEventsEnum } from './trendingEventsEnum'; -import { MarketplacesService } from '../marketplaces/marketplaces.service'; +import { BuyEventAnalyticsParser } from './events-parsers/buy-event-analytics.parser'; +import { AcceptOfferEventAnalyticsParser } from './events-parsers/acceptOffer-event-analytics.parser'; +import { PerformanceProfiler } from '@multiversx/sdk-nestjs'; +import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; +import { + AuctionEventEnum, + KroganSwapAuctionEventEnum, + ExternalAuctionEventEnum, +} from 'src/modules/assets/models'; +import { AnalyticsDataSetterService } from 'src/common/persistence/timescaledb/analytics-data.setter.service'; import BigNumber from 'bignumber.js'; -import { BuyEventParser } from './buy-event.parser'; -import { UsdPriceService } from '../usdPrice/usd-price.service'; -import { computeUsd } from 'src/utils/helpers'; -import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; -import { AcceptOfferEventParser } from './acceptOffer-event.parser'; -import { CollectionVolumeLast24 } from './collection-volume'; -import { CachingService } from '@multiversx/sdk-nestjs'; +import { analyticsEventsEnum } from './analyticsEventsEnum'; +import { ListingAuctionAnalyticsHandler } from './events-parsers/listing-event-analytics.parser'; +import { UpdateListingEventParser } from './events-parsers/updateListing-event.parser'; +import { UpdatePriceEventParser } from './events-parsers/updatePrice-event.parser'; @Injectable() export class AnalyticsService { private filterAddresses: string[]; - private data: any[] = []; + private data: any[]; constructor( private readonly indexerService: ElasticAnalyticsService, private readonly marketplacesService: MarketplacesService, - private readonly usdPriceService: UsdPriceService, - private readonly cacheService: CachingService, private readonly logger: Logger, - private readonly buyEventHandler: BuyEventParser, - private readonly acceptEventParser: AcceptOfferEventParser, + private readonly buyEventHandler: BuyEventAnalyticsParser, + private readonly acceptEventParser: AcceptOfferEventAnalyticsParser, + private readonly startAuctionEventParser: ListingAuctionAnalyticsHandler, + private readonly updatePriceEventParser: UpdatePriceEventParser, + private readonly updateListingEventParser: UpdateListingEventParser, + private readonly dataSetterService: AnalyticsDataSetterService, ) {} - public async getTrendingByVolume(): Promise { - return await this.cacheService.getCache(CacheInfo.TrendingByVolume.key); - } - - public async reindexTrendingCollections( - forTheLastHours: number = 24, - ): Promise { + public async indexAnalyticsLogs( + startDateUtc: number, + endDateUtc: number, + ): Promise { try { const performanceProfiler = new PerformanceProfiler(); - - const startDateUtc = moment() - .add(-forTheLastHours, 'hours') - .format('YYYY-MM-DD HH:mm:ss'); - const endDateUtc = moment().format('YYYY-MM-DD HH:mm:ss'); - - await this.getFilterAddresses(); - const tokens = await this.fetchLogsUsingScrollApi( startDateUtc, endDateUtc, @@ -63,177 +58,236 @@ export class AnalyticsService { } } + public async processEvents( + rawEvents: any[], + startDateUtc: number, + eventsTimestamp: number, + isSingleEvent: boolean = false, + ): Promise { + const marketplaceAddresses = + await this.marketplacesService.getMarketplacesAddreses(); + const performanceProfiler = new PerformanceProfiler(); + + const events: any[] = rawEvents.filter( + (rawEvent: { address: string; identifier: string }) => + marketplaceAddresses?.find( + (filterAddress) => rawEvent.address === filterAddress, + ) !== undefined, + ); + + if (events.length === 0) { + return; + } + this.data = []; + for (const rawEvent of events) { + try { + let parsedEvent = await this.getParsedEvent(rawEvent, eventsTimestamp); + if (parsedEvent) this.updateIngestData(parsedEvent); + } catch (error) { + if (error?.message?.includes('Cannot create address from')) { + this.logger.log('Invalid event'); + } else { + this.logger.log(`Could not process event:`, rawEvent); + this.logger.log(error); + } + continue; + } + } + performanceProfiler.stop(); + + if (Object.keys(this.data).length > 0) { + const isAfter = moment(eventsTimestamp * 1000).isSameOrAfter( + startDateUtc, + ); + if (isAfter) { + await this.ingestEvent(isSingleEvent, eventsTimestamp); + } + } + } + private async fetchLogsUsingScrollApi( - startDateUtc: string, - endDateUtc: string, - ): Promise { + startDateUtc: number, + endDateUtc: number, + ): Promise { this.logger.log( `Scroll logs between '${startDateUtc}' and '${endDateUtc}'`, ); let scrollPage = 0; let lastBlockLogs = []; - ({ scrollPage, lastBlockLogs } = await this.getParsedEvents( + return await this.getProcessedEvents( startDateUtc, endDateUtc, scrollPage, lastBlockLogs, - )); - - return await this.getTrendingCollections(); - } - - private async getTrendingCollections(): Promise { - const tokensWithPrice = await this.getTokensWithDetails(); - const collections = this.getDataGroupedByCollectionAndToken(); - let trending = []; - for (const collection of collections) { - let priceUsd: BigNumber = new BigNumber(0); - - for (const token of collection.tokens) { - const tokenDetails = tokensWithPrice[token.paymentToken]; - if ( - tokenDetails && - tokenDetails.length > 0 && - tokenDetails[0].usdPrice - ) { - priceUsd = priceUsd.plus( - computeUsd( - tokenDetails[0].usdPrice, - token.sum, - tokenDetails[0].decimals, - ), - ); - } - } - trending.push({ - collection: collection.collection, - volume: priceUsd.toString(), - tokens: collection.tokens, - }); - } - return trending.sortedDescending((x) => parseFloat(x.volume)); + ); } - private async getParsedEvents( - startDateUtc: string, - endDateUtc: string, + private async getProcessedEvents( + startDateUtc: number, + endDateUtc: number, scrollPage: number, lastBlockLogs: any[], ) { + this.filterAddresses = + await this.marketplacesService.getMarketplacesAddreses(); + await this.indexerService.getAllEvents( startDateUtc, endDateUtc, - trendingEventsEnum, + analyticsEventsEnum, this.filterAddresses, async (logs: any[]) => { this.logger.log(`Fetched ${logs.length} logs on page ${scrollPage}`); scrollPage += 1; - const groupedLogs = logs.groupBy((log) => log.timestamp); - - const blockLogs = Array.from(Object.keys(groupedLogs).sort()).map( - (key) => groupedLogs[key], + const { blockLogsLength, blockLogs } = this.getBlocksGroupedByTimestamp( + logs, + lastBlockLogs, ); - - if (blockLogs.length > 0) { - blockLogs[0] = [...lastBlockLogs, ...blockLogs[0]]; - } else { - blockLogs.push(lastBlockLogs); - } - - const blockLogsLength = - blockLogs.length === 0 ? blockLogs.length : blockLogs.length - 1; for (let i = 0; i < blockLogsLength; i++) { const eventsRaw = blockLogs[i] .map((logs: { events: any }) => logs.events) .flat(); + const events = eventsRaw.filter((event: { identifier: string }) => - trendingEventsEnum.includes(event.identifier), + analyticsEventsEnum.includes(event.identifier), ); - await this.processEvents(events); + await this.processEvents( + events, + startDateUtc, + blockLogs[i][0]?.timestamp, + ); } + this.logger.log( + `Fetched blocks: ${blockLogs.length}; processed: ${blockLogsLength}`, + ); if (blockLogs.length > 0) { lastBlockLogs = blockLogs[blockLogs.length - 1]; } }, ); - return { scrollPage, lastBlockLogs }; + const eventsRaw = lastBlockLogs.map((logs) => logs.events).flat(); + const events = eventsRaw.filter((event) => + analyticsEventsEnum.includes(event.identifier), + ); + await this.processEvents(events, startDateUtc, lastBlockLogs[0].timestamp); } - private getDataGroupedByCollectionAndToken() { - return this.data - .groupBy((x) => x.collection, true) - .map((group: { key: any; values: any[] }) => ({ - collection: group.key, - tokens: group.values - .groupBy((g: { paymentToken: any }) => g.paymentToken, true) - .map((group: { key: any; values: any[] }) => ({ - paymentToken: group.key, - sum: group.values - .sumBigInt((x: { value: BigInt }) => BigInt(x.value.toString())) - .toString(), - })), - })); - } + private getBlocksGroupedByTimestamp(logs: any[], lastBlockLogs: any[]) { + const groupedLogs = logs.groupBy((log) => log.timestamp); - private async getTokensWithDetails() { - const tokens = [...new Set(this.data.map((item) => item.paymentToken))]; - let tokensWithPrice = []; - for (const token of tokens) { - const tokenData = await this.usdPriceService.getToken(token); - tokensWithPrice.push({ - key: token, - usdPrice: tokenData.priceUsd, - decimals: tokenData.decimals, - }); + const blockLogs = Array.from(Object.keys(groupedLogs).sort()).map( + (key) => groupedLogs[key], + ); + if (blockLogs.length > 0) { + blockLogs[0] = [...lastBlockLogs, ...blockLogs[0]]; + + this.logger.log(`Remained logs from last block: ${lastBlockLogs.length}`); + } else { + blockLogs.push(lastBlockLogs); } - return tokensWithPrice.groupBy((t) => t.key); - } - async getFilterAddresses(): Promise { - this.filterAddresses = []; - this.filterAddresses = - await this.marketplacesService.getMarketplacesAddreses(); + const blockLogsLength = + blockLogs.length === 0 ? blockLogs.length : blockLogs.length - 1; + return { blockLogsLength, blockLogs }; } - private async processEvents(rawEvents: any[]): Promise { - const performanceProfiler = new PerformanceProfiler(); + private async getParsedEvent(rawEvent: any, eventsTimestamp: number) { + let parsedEvent = undefined; + switch (rawEvent.identifier) { + case AuctionEventEnum.AcceptOffer: + case ExternalAuctionEventEnum.AcceptGlobalOffer: + parsedEvent = await this.acceptEventParser.handle( + rawEvent, + eventsTimestamp, + ); + break; + case AuctionEventEnum.BuySftEvent: + case AuctionEventEnum.BidEvent: + case ExternalAuctionEventEnum.BulkBuy: + case ExternalAuctionEventEnum.Buy: + case ExternalAuctionEventEnum.BuyNft: + case ExternalAuctionEventEnum.BuyFor: + parsedEvent = await this.buyEventHandler.handle( + rawEvent, + eventsTimestamp, + ); + break; + case AuctionEventEnum.AuctionTokenEvent: + case ExternalAuctionEventEnum.ListNftOnMarketplace: + case ExternalAuctionEventEnum.Listing: + case KroganSwapAuctionEventEnum.NftSwap: + parsedEvent = await this.startAuctionEventParser.handle( + rawEvent, + eventsTimestamp, + ); + break; + case ExternalAuctionEventEnum.ChangePrice: + case ExternalAuctionEventEnum.UpdatePrice: + parsedEvent = await this.updatePriceEventParser.handle( + rawEvent, + eventsTimestamp, + ); + break; - const events: any[] = rawEvents.filter( - (rawEvent: { address: string; identifier: string }) => - this.filterAddresses.find( - (filterAddress) => rawEvent.address === filterAddress, - ) !== undefined, - ); + case ExternalAuctionEventEnum.UpdateListing: + parsedEvent = await this.updateListingEventParser.handle( + rawEvent, + eventsTimestamp, + ); + break; + } + return parsedEvent; + } - if (events.length === 0) { - return; + private async ingestEvent(singleEvent: boolean, eventsTimestamp: number) { + if (singleEvent) { + await this.dataSetterService.ingestSingleEvent({ + data: this.data, + timestamp: eventsTimestamp, + }); + } else { + await this.dataSetterService.ingest({ + data: this.data, + timestamp: eventsTimestamp, + ingestLast: false, + }); } + } - for (const rawEvent of events) { - try { - let parsedEvent = undefined; - if (rawEvent.identifier === 'acceptOffer') { - parsedEvent = await this.acceptEventParser.handle(rawEvent); - } else { - parsedEvent = await this.buyEventHandler.handle(rawEvent, 'hash'); - } + private updateIngestData(eventData: any[]): void { + for (const series of Object.keys(eventData)) { + if (this.data[series] === undefined) { + this.data[series] = {}; + } - if (parsedEvent) this.data.push(parsedEvent); - } catch (error) { - if (error?.message?.includes('Cannot create address from')) { - this.logger.log('Invalid event'); + for (const measure of Object.keys(eventData[series])) { + if (measure.toLowerCase().includes('volume')) { + this.data[series][measure] = this.data[series][measure] + ? new BigNumber(this.data[series][measure]) + .plus(eventData[series][measure]) + .toFixed() + : eventData[series][measure]; + } + if (measure.toLowerCase().includes('volumeUSD')) { + this.data[series][measure] = this.data[series][measure] + ? new BigNumber(this.data[series][measure]) + .plus(eventData[series][measure]) + .toFixed() + : eventData[series][measure]; + } + if (measure.toLowerCase().includes('floorPrice')) { + this.data[series][measure] = eventData[series][measure]; + } + if (measure.toLowerCase().includes('floorPriceUSD')) { + this.data[series][measure] = eventData[series][measure]; } else { - this.logger.log(`Could not process event:`, rawEvent); - this.logger.log(error); + this.data[series][measure] = eventData[series][measure]; } - // throw error; - continue; } } - performanceProfiler.stop(); } } diff --git a/src/modules/analytics/analyticsEventsEnum.ts b/src/modules/analytics/analyticsEventsEnum.ts new file mode 100644 index 000000000..0e5394c77 --- /dev/null +++ b/src/modules/analytics/analyticsEventsEnum.ts @@ -0,0 +1,17 @@ +export const analyticsEventsEnum = [ + 'buy', + 'buyNft', + 'buyFor', + 'buySft', + 'bulkBuy', + 'acceptOffer', + 'bid', + 'purchase', + 'auctionToken', + 'listing', + 'listNftOnMarketplace', + 'nftSwap', + 'changePrice', + 'updatePrice', + 'changeListing', +]; diff --git a/src/modules/analytics/collections-analytics.resolver.ts b/src/modules/analytics/collections-analytics.resolver.ts new file mode 100644 index 000000000..c49ac6310 --- /dev/null +++ b/src/modules/analytics/collections-analytics.resolver.ts @@ -0,0 +1,98 @@ +import { Int, Parent, Query, ResolveField } from '@nestjs/graphql'; +import { Args, Resolver } from '@nestjs/graphql'; +import { CollectionsAnalyticsService } from './collections-analytics.service'; +import { CollectionsAnalyticsModel } from './models/collections-stats.model'; +import { BaseResolver } from '../common/base.resolver'; +import { CollectionsAnalyticsResponse } from './models/collections-analytics.response'; +import PageResponse from '../common/PageResponse'; +import ConnectionArgs from '../common/filters/ConnectionArgs'; +import { CollectionDetailsProvider } from './loaders/collection-details.loader'; +import { CollectionsDetailsModel } from './models/collections-details.model'; +import { + AnalyticsArgs, + CollectionAnalyticsArgs, +} from './models/analytics-args.model'; +import { AnalyticsAggregateValue } from './models/analytics-aggregate-value'; + +@Resolver(() => CollectionsAnalyticsModel) +export class CollectionsAnalyticsResolver extends BaseResolver( + CollectionsAnalyticsModel, +) { + constructor( + private generalAnalyticsService: CollectionsAnalyticsService, + private collectionsLoader: CollectionDetailsProvider, + ) { + super(); + } + + @Query(() => CollectionsAnalyticsResponse) + async collectionsAnalytics( + @Args({ name: 'pagination', type: () => ConnectionArgs, nullable: true }) + pagination: ConnectionArgs, + @Args('input', { type: () => CollectionAnalyticsArgs, nullable: true }) + input: CollectionAnalyticsArgs, + ): Promise { + const { limit, offset } = pagination.pagingParams(); + const [collections, count] = + await this.generalAnalyticsService.getCollectionsOrderByVolum( + limit, + offset, + input.series, + ); + return PageResponse.mapResponse( + collections || [], + pagination, + count || 0, + 0, + limit, + ); + } + + @ResolveField('holders', () => Int) + async holders(@Parent() collection: CollectionsAnalyticsModel) { + return await this.generalAnalyticsService.getHolders( + collection.collectionIdentifier, + ); + } + + @ResolveField('floorPrice', () => Int) + async floorPrice(@Parent() collection: CollectionsAnalyticsModel) { + return this.generalAnalyticsService.getCollectionFloorPrice( + collection.collectionIdentifier, + ); + } + + @ResolveField('details', () => CollectionsDetailsModel) + async details(@Parent() collection: CollectionsAnalyticsModel) { + const collectionDetails = await this.collectionsLoader.load( + collection.collectionIdentifier, + ); + return collectionDetails?.value ?? null; + } + + @ResolveField('volumeData', () => [AnalyticsAggregateValue]) + async volumeData( + @Args('input', { type: () => AnalyticsArgs, nullable: true }) + input: AnalyticsArgs, + @Parent() collection: CollectionsAnalyticsModel, + ) { + return await this.generalAnalyticsService.getVolumeForTimePeriod( + input.time, + collection.collectionIdentifier, + input.metric, + ); + } + + @ResolveField('floorPriceData', () => [AnalyticsAggregateValue]) + async floorPriceData( + @Args('input', { type: () => AnalyticsArgs, nullable: true }) + input: AnalyticsArgs, + @Parent() collection: CollectionsAnalyticsModel, + ) { + return await this.generalAnalyticsService.getFloorPriceVolumeForTimePeriod( + input.time, + collection.collectionIdentifier, + input.metric, + ); + } +} diff --git a/src/modules/analytics/collections-analytics.service.ts b/src/modules/analytics/collections-analytics.service.ts new file mode 100644 index 000000000..1938a408a --- /dev/null +++ b/src/modules/analytics/collections-analytics.service.ts @@ -0,0 +1,118 @@ +import { MxToolsService } from 'src/common/services/mx-communication/mx-tools.service'; +import { AnalyticsInput } from './models/analytics-input.model'; +import { MxElasticService } from 'src/common'; +import { CachingService } from '@multiversx/sdk-nestjs'; +import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; +import * as hash from 'object-hash'; +import { Injectable } from '@nestjs/common'; +import { AnalyticsAggregateValue } from './models/analytics-aggregate-value'; +import { CollectionsAnalyticsModel } from './models/collections-stats.model'; +import { PersistenceService } from 'src/common/persistence/persistence.service'; +import { AnalyticsGetterService } from './analytics.getter.service'; + +@Injectable() +export class CollectionsAnalyticsService { + constructor( + private toolsService: MxToolsService, + private elasticService: MxElasticService, + private cacheService: CachingService, + private persistenceService: PersistenceService, + private readonly analyticsGetter: AnalyticsGetterService, + ) {} + + public async getNftsCount( + input: AnalyticsInput, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftAnalyticsCount.key}_${hash(input)}`, + () => this.toolsService.getNftsCount(input), + CacheInfo.NftAnalyticsCount.ttl, + CacheInfo.NftAnalyticsCount.ttl / 2, + ); + } + + public async getLast24HActive( + input: AnalyticsInput, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftAnalytic24hCount.key}_${hash(input)}`, + () => this.toolsService.getLast24HActive(input), + CacheInfo.NftAnalytic24hCount.ttl, + CacheInfo.NftAnalytic24hCount.ttl / 2, + ); + } + public async getActiveNftsStats( + input: AnalyticsInput, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftAnalytic24hListing.key}_${hash(input)}`, + () => this.toolsService.getActiveNftsStats(input), + CacheInfo.NftAnalytic24hListing.ttl, + CacheInfo.NftAnalytic24hListing.ttl / 2, + ); + } + + public async getHolders(collectionIdentifier: string): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftsHolders.key}_${collectionIdentifier}`, + () => + this.elasticService.getHoldersCountForCollection(collectionIdentifier), + CacheInfo.NftsHolders.ttl, + CacheInfo.NftsHolders.ttl / 2, + ); + } + + public async getCollectionsOrderByVolum( + limit: number = 10, + offset: number = 0, + series: string = null, + ): Promise<[CollectionsAnalyticsModel[], number]> { + const [collections, count] = + await this.analyticsGetter.getTopCollectionsDaily( + { metric: 'volumeUSD', series }, + limit, + offset, + ); + + return [ + collections.map((c) => CollectionsAnalyticsModel.fromTimescaleModel(c)), + count, + ]; + } + + public async getVolumeForTimePeriod( + time: string, + series: string, + metric: string, + ): Promise { + return await this.analyticsGetter.getVolumeDataForTimePeriod( + time, + series, + metric, + ); + } + + public async getFloorPriceVolumeForTimePeriod( + time: string, + series: string, + metric: string, + ): Promise { + return await this.analyticsGetter.getFloorPriceForTimePeriod( + time, + series, + metric, + ); + } + + public async getCollectionFloorPrice( + collectionIdentifier: string, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.CollectionFloorPrice.key}_${collectionIdentifier}`, + () => + this.persistenceService.getCollectionFloorPrice(collectionIdentifier), + CacheInfo.CollectionFloorPrice.ttl, + CacheInfo.CollectionFloorPrice.ttl / 2, + ); + } +} diff --git a/src/modules/analytics/elastic.indexer.service.ts b/src/modules/analytics/elastic.indexer.service.ts index c4db6a463..94099c422 100644 --- a/src/modules/analytics/elastic.indexer.service.ts +++ b/src/modules/analytics/elastic.indexer.service.ts @@ -11,14 +11,14 @@ export class ElasticAnalyticsService { constructor(private readonly elasticService: MxElasticService) {} public async getAllEvents( - startDateUtc: string, - endDateUtc: string, + startDateUtc: number, + endDateUtc: number, eventNames: string[], addresses: string[], action: (items: any[]) => Promise, ): Promise { - const gte = new Date(startDateUtc).getTime() / 1000; - const lte = new Date(endDateUtc).getTime() / 1000; + const gte = new Date(startDateUtc).getTime(); + const lte = new Date(endDateUtc).getTime(); const elasticQuery = ElasticQuery.create() .withPagination({ from: 0, size: 500 }) @@ -42,7 +42,6 @@ export class ElasticAnalyticsService { ) .withDateRangeFilter('timestamp', lte, gte) .withSort([{ name: 'timestamp', order: ElasticSortOrder.ascending }]); - await this.elasticService.getScrollableList( 'logs', '_id', @@ -64,8 +63,8 @@ export class ElasticAnalyticsService { eventGroups.sort( (a, b) => - new Date(a._source.timestamp).getTime() - - new Date(b._source.timestamp).getTime(), + new Date(a._source?.timestamp).getTime() - + new Date(b._source?.timestamp).getTime(), ); return eventGroups; diff --git a/src/modules/analytics/events-parsers/acceptOffer-event-analytics.parser.ts b/src/modules/analytics/events-parsers/acceptOffer-event-analytics.parser.ts new file mode 100644 index 000000000..e1ee7a1d3 --- /dev/null +++ b/src/modules/analytics/events-parsers/acceptOffer-event-analytics.parser.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@nestjs/common'; +import { ExternalAuctionEventEnum } from 'src/modules/assets/models'; +import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; +import { MarketplaceTypeEnum } from 'src/modules/marketplaces/models/MarketplaceType.enum'; +import { AcceptOfferEvent } from 'src/modules/rabbitmq/entities/auction/acceptOffer.event'; +import { AcceptOfferDeadrareEvent } from 'src/modules/rabbitmq/entities/auction/acceptOfferDeadrare.event'; +import { AcceptOfferFrameitEvent } from 'src/modules/rabbitmq/entities/auction/acceptOfferFrameit.event'; +import { AcceptOfferXoxnoEvent } from 'src/modules/rabbitmq/entities/auction/acceptOfferXoxno.event'; +import { UsdPriceService } from 'src/modules/usdPrice/usd-price.service'; +import { BigNumberUtils } from 'src/utils/bigNumber-utils'; +import { DEADRARE_KEY, FRAMEIT_KEY, XOXNO_KEY } from 'src/utils/constants'; +import { computeUsd } from 'src/utils/helpers'; + +@Injectable() +export class AcceptOfferEventAnalyticsParser { + constructor( + private readonly marketplaceService: MarketplacesService, + private readonly usdPriceService: UsdPriceService, + ) {} + + async handle(event: any, timestamp: number) { + const marketplace = await this.marketplaceService.getMarketplaceByAddress( + event.address, + ); + let acceptOfferEvent = undefined; + let topics = undefined; + if ( + marketplace.key === XOXNO_KEY && + !( + Buffer.from(event.topics[0], 'base64').toString() === + ExternalAuctionEventEnum.UserDeposit + ) + ) { + acceptOfferEvent = new AcceptOfferXoxnoEvent(event); + topics = acceptOfferEvent.getTopics(); + } + + if (marketplace.key === DEADRARE_KEY) { + acceptOfferEvent = new AcceptOfferDeadrareEvent(event); + topics = acceptOfferEvent.getTopics(); + } + + if (marketplace.key === FRAMEIT_KEY) { + acceptOfferEvent = new AcceptOfferFrameitEvent(event); + topics = acceptOfferEvent.getTopics(); + } + if (marketplace.type === MarketplaceTypeEnum.Internal) { + acceptOfferEvent = new AcceptOfferEvent(event); + topics = acceptOfferEvent.getTopics(); + } + if (acceptOfferEvent && topics) { + const tokenData = await this.usdPriceService.getToken( + topics.paymentTokenIdentifier, + ); + const tokenPrice = await this.usdPriceService.getTokenPriceFromDate( + topics.paymentTokenIdentifier, + timestamp, + ); + const data = []; + data[topics.collection] = { + usdPrice: tokenPrice, + volume: BigNumberUtils.denominateAmount( + topics.paymentAmount, + tokenData?.decimals, + ), + volumeUSD: !tokenData + ? '0' + : computeUsd( + tokenPrice.toString(), + topics.paymentAmount, + tokenData.decimals, + ).toFixed(), + paymentToken: tokenData?.identifier, + marketplaceKey: marketplace.key, + }; + return data; + } + } +} diff --git a/src/modules/analytics/events-parsers/buy-event-analytics.parser.ts b/src/modules/analytics/events-parsers/buy-event-analytics.parser.ts new file mode 100644 index 000000000..1f5045311 --- /dev/null +++ b/src/modules/analytics/events-parsers/buy-event-analytics.parser.ts @@ -0,0 +1,158 @@ +import { Injectable } from '@nestjs/common'; +import { AuctionEntity } from 'src/db/auctions'; +import { + AuctionEventEnum, + KroganSwapAuctionEventEnum, + ExternalAuctionEventEnum, +} from 'src/modules/assets/models'; +import { AuctionsGetterService } from 'src/modules/auctions'; +import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; +import { + BuySftEvent, + EndAuctionEvent, +} from 'src/modules/rabbitmq/entities/auction'; +import { ClaimEvent } from 'src/modules/rabbitmq/entities/auction/claim.event'; +import { ElrondSwapBuyEvent } from 'src/modules/rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event'; +import { UsdPriceService } from 'src/modules/usdPrice/usd-price.service'; +import { BigNumberUtils } from 'src/utils/bigNumber-utils'; +import { computeUsd } from 'src/utils/helpers'; + +@Injectable() +export class BuyEventAnalyticsParser { + constructor( + private auctionsGetterService: AuctionsGetterService, + private readonly marketplaceService: MarketplacesService, + private readonly usdPriceService: UsdPriceService, + ) {} + + async handle(event: any, timestamp: number) { + if (event.identifier === AuctionEventEnum.BidEvent) + if ( + Buffer.from(event.topics[0], 'base64')?.toString() === + ExternalAuctionEventEnum.EndTokenEvent + ) { + return this.getBuyNowData(event, timestamp); + } else return; + const { buySftEvent, topics } = this.getEventAndTopics(event); + let auction: AuctionEntity; + + const marketplace = await this.marketplaceService.getMarketplaceByAddress( + buySftEvent.getAddress(), + ); + + if (!marketplace) return; + + if (topics.auctionId) { + auction = await this.auctionsGetterService.getAuctionByIdAndMarketplace( + parseInt(topics.auctionId, 16), + marketplace.key, + ); + } else { + const auctionIdentifier = `${topics.collection}-${topics.nonce}`; + auction = + await this.auctionsGetterService.getAuctionByIdentifierAndMarketplace( + auctionIdentifier, + marketplace.key, + ); + } + if (!auction && !topics.paymentToken) return; + + const tokenData = await this.usdPriceService.getToken( + auction?.paymentToken ?? topics.paymentToken, + ); + const tokenPrice = await this.usdPriceService.getTokenPriceFromDate( + tokenData.identifier, + timestamp, + ); + + const volume = topics.bid === '0' ? auction.minBid : topics.bid; + + const data = []; + data[topics.collection] = { + usdPrice: tokenPrice ?? 0, + volume: BigNumberUtils.denominateAmount( + volume, + tokenData?.decimals ?? 18, + ), + volumeUSD: + volume === '0' || !tokenPrice + ? '0' + : computeUsd( + tokenPrice?.toString() ?? '0', + volume, + tokenData?.decimals ?? 18, + ).toFixed(), + paymentToken: tokenData?.identifier, + marketplaceKey: marketplace.key, + }; + return data; + } + + private async getBuyNowData(event: any, timestamp: number) { + const buySftEvent = new EndAuctionEvent(event); + const topics = buySftEvent.getTopics(); + const marketplace = await this.marketplaceService.getMarketplaceByAddress( + buySftEvent.getAddress(), + ); + if (!marketplace) return; + const auction = + await this.auctionsGetterService.getAuctionByIdAndMarketplace( + parseInt(topics.auctionId, 16), + marketplace.key, + ); + if (!auction) return; + + const tokenData = await this.usdPriceService.getToken( + auction?.paymentToken, + ); + const tokenPrice = await this.usdPriceService.getTokenPriceFromDate( + tokenData.identifier, + timestamp, + ); + + const volume = topics.currentBid; + + const data = []; + data[topics.collection] = { + usdPrice: tokenPrice ?? 0, + volume: BigNumberUtils.denominateAmount( + volume, + tokenData?.decimals ?? 18, + ), + volumeUSD: + volume === '0' || !tokenPrice + ? '0' + : computeUsd( + tokenPrice?.toString() ?? '0', + volume, + tokenData?.decimals ?? 18, + ).toFixed(), + paymentToken: tokenData?.identifier, + marketplaceKey: marketplace.key, + }; + return data; + } + + private getEventAndTopics(event: any) { + if (event.identifier === KroganSwapAuctionEventEnum.Purchase) { + if ( + Buffer.from(event.topics[0], 'base64')?.toString() === + KroganSwapAuctionEventEnum.UpdateListing + ) { + return; + } + const buySftEvent = new ElrondSwapBuyEvent(event); + const topics = buySftEvent.getTopics(); + return { buySftEvent, topics }; + } + + if (event.identifier === ExternalAuctionEventEnum.BuyNft) { + const buySftEvent = new ClaimEvent(event); + const topics = buySftEvent.getTopics(); + return { buySftEvent, topics }; + } + const buySftEvent = new BuySftEvent(event); + const topics = buySftEvent.getTopics(); + return { buySftEvent, topics }; + } +} diff --git a/src/modules/analytics/events-parsers/listing-event-analytics.parser.ts b/src/modules/analytics/events-parsers/listing-event-analytics.parser.ts new file mode 100644 index 000000000..e3c9e5976 --- /dev/null +++ b/src/modules/analytics/events-parsers/listing-event-analytics.parser.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@nestjs/common'; +import { mxConfig } from 'src/config'; +import { + KroganSwapAuctionEventEnum, + ExternalAuctionEventEnum, +} from 'src/modules/assets/models'; +import { ElrondSwapAuctionTypeEnum } from 'src/modules/auctions/models'; +import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; +import { UsdPriceService } from 'src/modules/usdPrice/usd-price.service'; +import { AuctionTokenEvent } from '../../rabbitmq/entities/auction'; +import { ElrondSwapAuctionEvent } from '../../rabbitmq/entities/auction/elrondnftswap/elrondswap-auction.event'; +import { ListNftEvent } from '../../rabbitmq/entities/auction/listNft.event'; +import { BigNumberUtils } from 'src/utils/bigNumber-utils'; +import { computeUsd } from 'src/utils/helpers'; + +@Injectable() +export class ListingAuctionAnalyticsHandler { + constructor( + private usdPriceService: UsdPriceService, + private readonly marketplaceService: MarketplacesService, + ) {} + + async handle(event: any, timestamp: number) { + const { auctionTokenEvent, topics } = this.getEventAndTopics(event); + if (!auctionTokenEvent && !topics) return; + + const marketplace = await this.marketplaceService.getMarketplaceByAddress( + auctionTokenEvent.getAddress(), + ); + + if (!marketplace) return; + + const tokenData = await this.usdPriceService.getToken( + topics.paymentToken ?? mxConfig.egld, + ); + const tokenPrice = await this.usdPriceService.getTokenPriceFromDate( + tokenData.identifier, + timestamp, + ); + + const data = []; + + const floorPrice = BigNumberUtils.denominateAmount( + topics.price, + tokenData?.decimals ?? 18, + ); + + if (floorPrice < Math.pow(10, 128)) { + data[topics.collection] = { + usdPrice: tokenPrice ?? 0, + floorPrice: floorPrice, + floorPriceUSD: + topics.price === '0' || !tokenPrice + ? '0' + : computeUsd( + tokenPrice?.toString() ?? '0', + topics.price, + tokenData?.decimals ?? 18, + ).toFixed(), + paymentToken: tokenData?.identifier, + marketplaceKey: marketplace.key, + }; + + return data; + } + } + + private getEventAndTopics(event: any) { + if (event.identifier === KroganSwapAuctionEventEnum.NftSwap) { + const auctionTokenEvent = new ElrondSwapAuctionEvent(event); + const topics = auctionTokenEvent.getTopics(); + if (parseInt(topics.auctionType) === ElrondSwapAuctionTypeEnum.Swap) { + return { auctionTokenEvent: null, topics: null }; + } + return { auctionTokenEvent, topics }; + } + + if (event.identifier === ExternalAuctionEventEnum.ListNftOnMarketplace) { + const auctionTokenEvent = new ListNftEvent(event); + const topics = auctionTokenEvent.getTopics(); + return { auctionTokenEvent, topics }; + } + const auctionTokenEvent = new AuctionTokenEvent(event); + const topics = auctionTokenEvent.getTopics(); + return { auctionTokenEvent, topics }; + } +} diff --git a/src/modules/analytics/events-parsers/updateListing-event.parser.ts b/src/modules/analytics/events-parsers/updateListing-event.parser.ts new file mode 100644 index 000000000..6130c5dcd --- /dev/null +++ b/src/modules/analytics/events-parsers/updateListing-event.parser.ts @@ -0,0 +1,57 @@ +import { Injectable } from '@nestjs/common'; +import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; +import { UsdPriceService } from 'src/modules/usdPrice/usd-price.service'; +import { UpdateListingEvent } from '../../rabbitmq/entities/auction/updateListing.event'; +import { BigNumberUtils } from 'src/utils/bigNumber-utils'; +import { computeUsd } from 'src/utils/helpers'; +import { mxConfig } from 'src/config'; + +@Injectable() +export class UpdateListingEventParser { + constructor( + private readonly marketplaceService: MarketplacesService, + private readonly usdPriceService: UsdPriceService, + ) {} + + async handle(event: any, timestamp: number) { + const updateListingEvent = new UpdateListingEvent(event); + const topics = updateListingEvent.getTopics(); + const marketplace = await this.marketplaceService.getMarketplaceByAddress( + updateListingEvent.getAddress(), + ); + + if (!marketplace) return; + + const tokenData = await this.usdPriceService.getToken( + topics.paymentToken ?? mxConfig.egld, + ); + const tokenPrice = await this.usdPriceService.getTokenPriceFromDate( + tokenData.identifier, + timestamp, + ); + + const data = []; + const newFloorPrice = BigNumberUtils.denominateAmount( + topics.newBid, + tokenData?.decimals ?? 18, + ); + if (newFloorPrice < Math.pow(10, 128)) { + data[topics.collection] = { + usdPrice: tokenPrice ?? 0, + floorPrice: newFloorPrice, + floorPriceUSD: + topics.newBid === '0' || !tokenPrice + ? '0' + : computeUsd( + tokenPrice?.toString() ?? '0', + topics.newBid, + tokenData?.decimals ?? 18, + ).toFixed(), + paymentToken: tokenData?.identifier, + marketplaceKey: marketplace.key, + }; + + return data; + } + } +} diff --git a/src/modules/analytics/events-parsers/updatePrice-event.parser.ts b/src/modules/analytics/events-parsers/updatePrice-event.parser.ts new file mode 100644 index 000000000..dc0d8e771 --- /dev/null +++ b/src/modules/analytics/events-parsers/updatePrice-event.parser.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; +import { mxConfig } from 'src/config'; +import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; +import { UsdPriceService } from 'src/modules/usdPrice/usd-price.service'; +import { computeUsd } from 'src/utils/helpers'; +import { BigNumberUtils } from 'src/utils/bigNumber-utils'; +import { DEADRARE_KEY, FRAMEIT_KEY, XOXNO_KEY } from 'src/utils/constants'; +import { UpdatePriceEvent } from 'src/modules/rabbitmq/entities/auction/updatePrice.event'; + +@Injectable() +export class UpdatePriceEventParser { + constructor( + private readonly marketplaceService: MarketplacesService, + private usdPriceService: UsdPriceService, + ) {} + + async handle(event: any, timestamp: number) { + const updatePriceEvent = new UpdatePriceEvent(event); + const topics = updatePriceEvent.getTopics(); + const marketplace = await this.marketplaceService.getMarketplaceByAddress( + updatePriceEvent.getAddress(), + ); + + if (!marketplace) return; + const paymentToken = this.getPaymentToken(marketplace.key, event.topics); + const tokenData = await this.usdPriceService.getToken( + paymentToken ?? mxConfig.egld, + ); + const tokenPrice = await this.usdPriceService.getTokenPriceFromDate( + tokenData.identifier, + timestamp, + ); + + const data = []; + let newPrice = BigNumberUtils.denominateAmount( + topics.newBid, + tokenData?.decimals ?? 18, + ); + + if (newPrice && newPrice < Math.pow(10, 128)) { + data[topics.collection] = { + usdPrice: tokenPrice ?? 0, + floorPrice: newPrice, + floorPriceUSD: + topics.newBid === '0' || !tokenPrice + ? '0' + : computeUsd( + tokenPrice?.toString() ?? '0', + topics.newBid, + tokenData?.decimals ?? 18, + ).toFixed(), + paymentToken: tokenData?.identifier, + marketplaceKey: marketplace.key, + }; + + return data; + } + } + + private getPaymentToken(marketplaceKey: string, rawTopics: string[]) { + if (marketplaceKey === DEADRARE_KEY) { + return Buffer.from(rawTopics[8] ?? '', 'base64').toString(); + } + if (marketplaceKey === XOXNO_KEY) { + return Buffer.from(rawTopics[7] ?? '', 'base64').toString(); + } + if (marketplaceKey === FRAMEIT_KEY) { + return Buffer.from(rawTopics[4] ?? '', 'base64').toString(); + } + } +} diff --git a/src/modules/analytics/general-analytics.resolver.ts b/src/modules/analytics/general-analytics.resolver.ts new file mode 100644 index 000000000..d394143ed --- /dev/null +++ b/src/modules/analytics/general-analytics.resolver.ts @@ -0,0 +1,55 @@ +import { Int, Query, ResolveField } from '@nestjs/graphql'; +import { Args, Resolver } from '@nestjs/graphql'; +import { GeneralAnalyticsModel } from './models/general-stats.model'; +import { AnalyticsInput } from './models/analytics-input.model'; +import { GeneralAnalyticsService } from './general-analytics.service'; +import { AnalyticsAggregateValue } from './models/analytics-aggregate-value'; + +@Resolver(() => GeneralAnalyticsModel) +export class GeneralAnalyticsResolver { + constructor(private generalAnalyticsService: GeneralAnalyticsService) {} + + @Query(() => GeneralAnalyticsModel) + async generalAnalytics( + @Args('input', { type: () => AnalyticsInput, nullable: true }) + input: AnalyticsInput, + ): Promise { + return new GeneralAnalyticsModel(); + } + + @ResolveField('listing', () => [AnalyticsAggregateValue]) + async listing( + @Args('input', { type: () => AnalyticsInput }) input: AnalyticsInput, + ) { + return await this.generalAnalyticsService.getActiveNftsStats(input); + } + + @ResolveField('volume', () => [AnalyticsAggregateValue]) + async volume( + @Args('input', { type: () => AnalyticsInput }) input: AnalyticsInput, + ) { + return await this.generalAnalyticsService.getLast24HActive(input); + } + + @ResolveField('nfts', () => [AnalyticsAggregateValue]) + async nfts( + @Args('input', { type: () => AnalyticsInput }) input: AnalyticsInput, + ) { + return await this.generalAnalyticsService.getNftsCount(input); + } + + @ResolveField('holders', () => Int) + async holders() { + return await this.generalAnalyticsService.getHolders(); + } + + @ResolveField('collections', () => Int) + async collections() { + return await this.generalAnalyticsService.getCollections(); + } + + @ResolveField('marketplaces', () => Int) + async marketplaces() { + return await this.generalAnalyticsService.getMarketplaces(); + } +} diff --git a/src/modules/analytics/general-analytics.service.ts b/src/modules/analytics/general-analytics.service.ts new file mode 100644 index 000000000..6bf98b8c7 --- /dev/null +++ b/src/modules/analytics/general-analytics.service.ts @@ -0,0 +1,72 @@ +import { MxToolsService } from 'src/common/services/mx-communication/mx-tools.service'; +import { AnalyticsInput } from './models/analytics-input.model'; +import { MxElasticService } from 'src/common'; +import { CollectionsGetterService } from '../nftCollections/collections-getter.service'; +import { MarketplacesService } from '../marketplaces/marketplaces.service'; +import { CachingService } from '@multiversx/sdk-nestjs'; +import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; +import * as hash from 'object-hash'; +import { Injectable } from '@nestjs/common'; +import { AnalyticsAggregateValue } from './models/analytics-aggregate-value'; + +@Injectable() +export class GeneralAnalyticsService { + constructor( + private toolsService: MxToolsService, + private elasticService: MxElasticService, + private cacheService: CachingService, + private collectionsService: CollectionsGetterService, + private marketplacesService: MarketplacesService, + ) {} + + public async getNftsCount( + input: AnalyticsInput, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftAnalyticsCount.key}_${hash(input)}`, + () => this.toolsService.getNftsCount(input), + CacheInfo.NftAnalyticsCount.ttl, + CacheInfo.NftAnalyticsCount.ttl / 2, + ); + } + + public async getLast24HActive( + input: AnalyticsInput, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftAnalytic24hCount.key}_${hash(input)}`, + () => this.toolsService.getLast24HActive(input), + CacheInfo.NftAnalytic24hCount.ttl, + CacheInfo.NftAnalytic24hCount.ttl / 2, + ); + } + public async getActiveNftsStats( + input: AnalyticsInput, + ): Promise { + return this.cacheService.getOrSetCache( + `${CacheInfo.NftAnalytic24hListing.key}_${hash(input)}`, + () => this.toolsService.getActiveNftsStats(input), + CacheInfo.NftAnalytic24hListing.ttl, + CacheInfo.NftAnalytic24hListing.ttl / 2, + ); + } + + public async getHolders(): Promise { + return this.cacheService.getOrSetCache( + CacheInfo.NftsHolders.key, + () => this.elasticService.getHoldersCount(), + CacheInfo.NftsHolders.ttl, + CacheInfo.NftsHolders.ttl / 2, + ); + } + + public async getCollections(): Promise { + const [, collections] = await this.collectionsService.getCollections(); + return collections; + } + + public async getMarketplaces(): Promise { + const marketplaces = await this.marketplacesService.getMarketplaces(); + return marketplaces?.count; + } +} diff --git a/src/modules/analytics/loaders/collection-details.loader.ts b/src/modules/analytics/loaders/collection-details.loader.ts new file mode 100644 index 000000000..31a3e09c9 --- /dev/null +++ b/src/modules/analytics/loaders/collection-details.loader.ts @@ -0,0 +1,27 @@ +import { Injectable, Scope } from '@nestjs/common'; +import DataLoader = require('dataloader'); +import { BaseProvider } from '../../common/base.loader'; +import { CollectionDetailsRedisHandler } from './collection-details.redis-handler'; +import { CollectionsGetterService } from 'src/modules/nftCollections/collections-getter.service'; + +@Injectable({ + scope: Scope.REQUEST, +}) +export class CollectionDetailsProvider extends BaseProvider { + constructor( + private collectionsService: CollectionsGetterService, + collectionDetailsRedisHandler: CollectionDetailsRedisHandler, + ) { + super( + collectionDetailsRedisHandler, + new DataLoader(async (keys: string[]) => await this.batchLoad(keys)), + ); + } + + getData = async (keys: string[]): Promise => { + const response = await this.collectionsService.getCollectionsByIdentifiers( + keys, + ); + return response?.groupBy((item) => item.collection); + }; +} diff --git a/src/modules/analytics/loaders/collection-details.redis-handler.ts b/src/modules/analytics/loaders/collection-details.redis-handler.ts new file mode 100644 index 000000000..980ebb9d1 --- /dev/null +++ b/src/modules/analytics/loaders/collection-details.redis-handler.ts @@ -0,0 +1,35 @@ +import { RedisCacheService } from '@multiversx/sdk-nestjs'; +import { Injectable } from '@nestjs/common'; +import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; +import { RedisKeyValueDataloaderHandler } from 'src/modules/common/redis-key-value-dataloader.handler'; +import { RedisValue } from 'src/modules/common/redis-value.dto'; +import { CollectionsDetailsModel } from '../models/collections-details.model'; + +@Injectable() +export class CollectionDetailsRedisHandler extends RedisKeyValueDataloaderHandler { + constructor(redisCacheService: RedisCacheService) { + super(redisCacheService, CacheInfo.CollectionDetails.key); + } + + mapValues( + returnValues: { key: string; value: any }[], + collections: { [key: string]: any[] }, + ) { + const redisValues = []; + for (const item of returnValues) { + if (item.value === null) { + item.value = collections[item.key] + ? CollectionsDetailsModel.fromApiCollection(collections[item.key][0]) + : null; + redisValues.push(item); + } + } + + return [ + new RedisValue({ + values: redisValues, + ttl: CacheInfo.CollectionDetails.ttl, + }), + ]; + } +} diff --git a/src/modules/analytics/models/analytics-aggregate-value.ts b/src/modules/analytics/models/analytics-aggregate-value.ts new file mode 100644 index 000000000..9108ce720 --- /dev/null +++ b/src/modules/analytics/models/analytics-aggregate-value.ts @@ -0,0 +1,50 @@ +import { Field, Float, ObjectType } from '@nestjs/graphql'; +import * as moment from 'moment'; + +@ObjectType() +export class AnalyticsAggregateValue { + @Field(() => String, { nullable: true }) + time?: string; + + @Field({ nullable: true }) + series: string; + + @Field(() => Float, { nullable: true }) + min?: number; + + @Field(() => Float, { nullable: true }) + max?: number; + + @Field(() => Float, { nullable: true }) + count?: number; + + @Field(() => Float, { nullable: true }) + value?: number; + + @Field(() => Float, { nullable: true }) + avg?: number; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromDataApi(row: any) { + return new AnalyticsAggregateValue({ + series: row.series, + time: moment.utc(row.timestamp ?? row.time).format('yyyy-MM-DD HH:mm:ss'), + min: row.min ?? 0, + max: row.max ?? 0, + count: row.count ?? 0, + value: row.sum ?? 0, + avg: row.avg ?? 0, + }); + } + + static fromTimescaleObjext(row: any) { + return new AnalyticsAggregateValue({ + series: row.series, + time: moment.utc(row.timestamp ?? row.time).format('yyyy-MM-DD HH:mm:ss'), + value: row.value ?? 0, + }); + } +} diff --git a/src/modules/analytics/models/analytics-args.model.ts b/src/modules/analytics/models/analytics-args.model.ts new file mode 100644 index 000000000..5a5906cbc --- /dev/null +++ b/src/modules/analytics/models/analytics-args.model.ts @@ -0,0 +1,32 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { IsOptional, Matches } from 'class-validator'; +import { + COLLECTION_IDENTIFIER_ERROR, + COLLECTION_IDENTIFIER_RGX, +} from 'src/utils/constants'; + +@InputType() +export class CollectionAnalyticsArgs { + @IsOptional() + @Matches(RegExp(COLLECTION_IDENTIFIER_RGX), { + message: COLLECTION_IDENTIFIER_ERROR, + }) + @Field(() => String, { nullable: true }) + series: string; +} + +@InputType() +export class AnalyticsArgs { + @IsOptional() + @Matches(RegExp(COLLECTION_IDENTIFIER_RGX), { + message: COLLECTION_IDENTIFIER_ERROR, + }) + @Field(() => String, { nullable: true }) + series: string; + @Field() + metric: string; + @IsOptional() + @Field({ nullable: true }) + @Matches(new RegExp('[1-60][s,m,h,d]')) + time: string; +} diff --git a/src/modules/analytics/models/analytics-input.model.ts b/src/modules/analytics/models/analytics-input.model.ts new file mode 100644 index 000000000..78112cba5 --- /dev/null +++ b/src/modules/analytics/models/analytics-input.model.ts @@ -0,0 +1,12 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { TimeResolutionsEnum } from './time-resolutions.enum'; +import { TimeRangeEnum } from './time-range.enum'; + +@InputType() +export class AnalyticsInput { + @Field(() => TimeResolutionsEnum, { name: 'resolution', nullable: true }) + resolution?: TimeResolutionsEnum; + + @Field(() => TimeRangeEnum, { name: 'range', nullable: true }) + range?: TimeRangeEnum; +} diff --git a/src/modules/analytics/models/analytics-stats.model.ts b/src/modules/analytics/models/analytics-stats.model.ts new file mode 100644 index 000000000..5b4a3e7b7 --- /dev/null +++ b/src/modules/analytics/models/analytics-stats.model.ts @@ -0,0 +1,21 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class AnalyticsStats { + @Field() + nfts: number; + @Field() + collections: number; + @Field() + holders: number; + @Field() + createdLastMonth: number; + @Field() + volume: number; + @Field() + marketplaces: number; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/src/modules/analytics/collection-volume.ts b/src/modules/analytics/models/collection-volume.ts similarity index 100% rename from src/modules/analytics/collection-volume.ts rename to src/modules/analytics/models/collection-volume.ts diff --git a/src/modules/analytics/models/collections-analytics.response.ts b/src/modules/analytics/models/collections-analytics.response.ts new file mode 100644 index 000000000..8509092b8 --- /dev/null +++ b/src/modules/analytics/models/collections-analytics.response.ts @@ -0,0 +1,8 @@ +import { ObjectType } from '@nestjs/graphql'; +import relayTypes from 'src/modules/common/Relay.types'; +import { CollectionsAnalyticsModel } from './collections-stats.model'; + +@ObjectType() +export class CollectionsAnalyticsResponse extends relayTypes( + CollectionsAnalyticsModel, +) {} diff --git a/src/modules/analytics/models/collections-details.model.ts b/src/modules/analytics/models/collections-details.model.ts new file mode 100644 index 000000000..e3ecf4d42 --- /dev/null +++ b/src/modules/analytics/models/collections-details.model.ts @@ -0,0 +1,26 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql'; +import { Collection } from 'src/modules/nftCollections/models'; + +@ObjectType() +export class CollectionsDetailsModel { + @Field() + collectionIdentifier: string; + @Field() + collectionName: string; + @Field() + owner: string; + @Field(() => Int) + items: number; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromApiCollection(collection: Collection) { + return new CollectionsDetailsModel({ + collectionName: collection.name, + items: collection.nftsCount, + owner: collection.artistAddress ?? collection.ownerAddress, + }); + } +} diff --git a/src/modules/analytics/models/collections-stats.model.ts b/src/modules/analytics/models/collections-stats.model.ts new file mode 100644 index 000000000..590dc6e32 --- /dev/null +++ b/src/modules/analytics/models/collections-stats.model.ts @@ -0,0 +1,31 @@ +import { Field, Float, Int, ObjectType } from '@nestjs/graphql'; +import { CollectionsDetailsModel } from './collections-details.model'; +import { AnalyticsAggregateValue } from './analytics-aggregate-value'; + +@ObjectType() +export class CollectionsAnalyticsModel { + @Field() + collectionIdentifier: string; + @Field(() => CollectionsDetailsModel) + details: CollectionsDetailsModel; + @Field(() => Int) + holders: number; + @Field(() => Float, { nullable: true }) + volume24h: number; + @Field() + floorPrice: number; + + @Field(() => [AnalyticsAggregateValue]) + volumeData: AnalyticsAggregateValue[]; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + static fromTimescaleModel(collection: AnalyticsAggregateValue) { + return new CollectionsAnalyticsModel({ + collectionIdentifier: collection.series, + volume24h: collection.value, + }); + } +} diff --git a/src/modules/analytics/models/general-stats.model.ts b/src/modules/analytics/models/general-stats.model.ts new file mode 100644 index 000000000..d83e8a50a --- /dev/null +++ b/src/modules/analytics/models/general-stats.model.ts @@ -0,0 +1,22 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql'; +import { AnalyticsAggregateValue } from './analytics-aggregate-value'; + +@ObjectType() +export class GeneralAnalyticsModel { + @Field(() => Int) + holders: number; + @Field(() => Int) + marketplaces: number; + @Field(() => Int) + collections: number; + @Field(() => [AnalyticsAggregateValue]) + nfts: AnalyticsAggregateValue[]; + @Field(() => [AnalyticsAggregateValue]) + volume: AnalyticsAggregateValue[]; + @Field(() => [AnalyticsAggregateValue]) + listing: AnalyticsAggregateValue[]; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/src/modules/analytics/models/time-range.enum.ts b/src/modules/analytics/models/time-range.enum.ts new file mode 100644 index 000000000..1d76a13d7 --- /dev/null +++ b/src/modules/analytics/models/time-range.enum.ts @@ -0,0 +1,12 @@ +import { registerEnumType } from "@nestjs/graphql"; + +export enum TimeRangeEnum { + HOUR = "HOUR", + DAY = "DAY", + WEEK = "WEEK", + MONTH = "MONTH", + YEAR = "YEAR", + ALL = "ALL", +} + +registerEnumType(TimeRangeEnum, { name: 'TimeRange' }); diff --git a/src/modules/analytics/models/time-resolutions.enum.ts b/src/modules/analytics/models/time-resolutions.enum.ts new file mode 100644 index 000000000..04064fc0e --- /dev/null +++ b/src/modules/analytics/models/time-resolutions.enum.ts @@ -0,0 +1,15 @@ +import { registerEnumType } from "@nestjs/graphql"; + +export enum TimeResolutionsEnum { + INTERVAL_1_MINUTE = "INTERVAL_1_MINUTE", + INTERVAL_10_MINUTES = "INTERVAL_10_MINUTES", + INTERVAL_30_MINUTES = "INTERVAL_30_MINUTES", + INTERVAL_HOUR = "INTERVAL_HOUR", + INTERVAL_DAY = "INTERVAL_DAY", + INTERVAL_WEEK = "INTERVAL_WEEK", + INTERVAL_MONTH = "INTERVAL_MONTH", +} + +registerEnumType(TimeResolutionsEnum, { + name: 'TimeResolutionsEnum', +}); diff --git a/src/modules/analytics/acceptOffer-event.parser.ts b/src/modules/analytics/trending/acceptOffer-event.parser.ts similarity index 74% rename from src/modules/analytics/acceptOffer-event.parser.ts rename to src/modules/analytics/trending/acceptOffer-event.parser.ts index 2562a714f..3f64097cd 100644 --- a/src/modules/analytics/acceptOffer-event.parser.ts +++ b/src/modules/analytics/trending/acceptOffer-event.parser.ts @@ -1,12 +1,12 @@ import { Injectable } from '@nestjs/common'; import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; import { DEADRARE_KEY, FRAMEIT_KEY, XOXNO_KEY } from 'src/utils/constants'; -import { ExternalAuctionEventEnum } from '../assets/models'; -import { MarketplaceTypeEnum } from '../marketplaces/models/MarketplaceType.enum'; -import { AcceptOfferEvent } from '../rabbitmq/entities/auction/acceptOffer.event'; -import { AcceptOfferDeadrareEvent } from '../rabbitmq/entities/auction/acceptOfferDeadrare.event'; -import { AcceptOfferFrameitEvent } from '../rabbitmq/entities/auction/acceptOfferFrameit.event'; -import { AcceptOfferXoxnoEvent } from '../rabbitmq/entities/auction/acceptOfferXoxno.event'; +import { ExternalAuctionEventEnum } from '../../assets/models'; +import { MarketplaceTypeEnum } from '../../marketplaces/models/MarketplaceType.enum'; +import { AcceptOfferEvent } from '../../rabbitmq/entities/auction/acceptOffer.event'; +import { AcceptOfferDeadrareEvent } from '../../rabbitmq/entities/auction/acceptOfferDeadrare.event'; +import { AcceptOfferFrameitEvent } from '../../rabbitmq/entities/auction/acceptOfferFrameit.event'; +import { AcceptOfferXoxnoEvent } from '../../rabbitmq/entities/auction/acceptOfferXoxno.event'; @Injectable() export class AcceptOfferEventParser { diff --git a/src/modules/analytics/buy-event.parser.ts b/src/modules/analytics/trending/buy-event.parser.ts similarity index 85% rename from src/modules/analytics/buy-event.parser.ts rename to src/modules/analytics/trending/buy-event.parser.ts index 568968219..d637f9be3 100644 --- a/src/modules/analytics/buy-event.parser.ts +++ b/src/modules/analytics/trending/buy-event.parser.ts @@ -1,14 +1,14 @@ import { Injectable, Logger } from '@nestjs/common'; import { AuctionEntity } from 'src/db/auctions'; import { - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { AuctionsGetterService } from 'src/modules/auctions'; import { MarketplacesService } from 'src/modules/marketplaces/marketplaces.service'; -import { BuySftEvent } from '../rabbitmq/entities/auction'; -import { ClaimEvent } from '../rabbitmq/entities/auction/claim.event'; -import { ElrondSwapBuyEvent } from '../rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event'; +import { BuySftEvent } from '../../rabbitmq/entities/auction'; +import { ClaimEvent } from '../../rabbitmq/entities/auction/claim.event'; +import { ElrondSwapBuyEvent } from '../../rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event'; @Injectable() export class BuyEventParser { @@ -52,10 +52,10 @@ export class BuyEventParser { } private getEventAndTopics(event: any, hash: string) { - if (event.identifier === ElrondNftsSwapAuctionEventEnum.Purchase) { + if (event.identifier === KroganSwapAuctionEventEnum.Purchase) { if ( Buffer.from(event.topics[0], 'base64').toString() === - ElrondNftsSwapAuctionEventEnum.UpdateListing + KroganSwapAuctionEventEnum.UpdateListing ) { this.logger.log( `Update Listing event detected for hash '${hash}' at Purchase external marketplace ${event.address}, ignore it for the moment`, diff --git a/src/modules/analytics/trending/trending-collections.service.ts b/src/modules/analytics/trending/trending-collections.service.ts new file mode 100644 index 000000000..490e1d2a5 --- /dev/null +++ b/src/modules/analytics/trending/trending-collections.service.ts @@ -0,0 +1,239 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PerformanceProfiler } from '../../metrics/performance.profiler'; +import * as moment from 'moment'; +import { ElasticAnalyticsService } from '../elastic.indexer.service'; +import { analyticsEventsEnum } from '../analyticsEventsEnum'; +import { MarketplacesService } from '../../marketplaces/marketplaces.service'; +import BigNumber from 'bignumber.js'; +import { BuyEventParser } from './buy-event.parser'; +import { UsdPriceService } from '../../usdPrice/usd-price.service'; +import { computeUsd } from 'src/utils/helpers'; +import { CacheInfo } from 'src/common/services/caching/entities/cache.info'; +import { AcceptOfferEventParser } from './acceptOffer-event.parser'; +import { CollectionVolumeLast24 } from '../models/collection-volume'; +import { CachingService } from '@multiversx/sdk-nestjs'; + +@Injectable() +export class TrendingCollectionsService { + private filterAddresses: string[]; + private data: any[] = []; + + constructor( + private readonly indexerService: ElasticAnalyticsService, + private readonly marketplacesService: MarketplacesService, + private readonly usdPriceService: UsdPriceService, + private readonly cacheService: CachingService, + private readonly logger: Logger, + private readonly buyEventHandler: BuyEventParser, + private readonly acceptEventParser: AcceptOfferEventParser, + ) {} + + public async getTrendingByVolume(): Promise { + return await this.cacheService.getCache(CacheInfo.TrendingByVolume.key); + } + + public async reindexTrendingCollections( + forTheLastHours: number = 24, + ): Promise { + try { + const performanceProfiler = new PerformanceProfiler(); + + const startDateUtc = moment() + .add(-forTheLastHours, 'hours') + .format('YYYY-MM-DD HH:mm:ss'); + const endDateUtc = moment().format('YYYY-MM-DD HH:mm:ss'); + + await this.getFilterAddresses(); + + const tokens = await this.fetchLogsUsingScrollApi( + new Date(startDateUtc).getTime(), + new Date(endDateUtc).getTime(), + ); + + performanceProfiler.stop(); + + this.logger.log( + `Finish indexing analytics data. Elapsed time: ${moment( + performanceProfiler.duration, + ).format('HH:mm:ss')}`, + ); + return tokens; + } catch (error) { + this.logger.log(error); + } + } + + private async fetchLogsUsingScrollApi( + startDateUtc: number, + endDateUtc: number, + ): Promise { + this.logger.log( + `Scroll logs between '${startDateUtc}' and '${endDateUtc}'`, + ); + + let scrollPage = 0; + let lastBlockLogs = []; + ({ scrollPage, lastBlockLogs } = await this.getParsedEvents( + startDateUtc, + endDateUtc, + scrollPage, + lastBlockLogs, + )); + + return await this.getTrendingCollections(); + } + + private async getTrendingCollections(): Promise { + const tokensWithPrice = await this.getTokensWithDetails(); + const collections = this.getDataGroupedByCollectionAndToken(); + let trending = []; + for (const collection of collections) { + let priceUsd: BigNumber = new BigNumber(0); + + for (const token of collection.tokens) { + const tokenDetails = tokensWithPrice[token.paymentToken]; + if ( + tokenDetails && + tokenDetails.length > 0 && + tokenDetails[0].usdPrice + ) { + priceUsd = priceUsd.plus( + computeUsd( + tokenDetails[0].usdPrice, + token.sum, + tokenDetails[0].decimals, + ), + ); + } + } + trending.push({ + collection: collection.collection, + volume: priceUsd.toString(), + tokens: collection.tokens, + }); + } + return trending.sortedDescending((x) => parseFloat(x.volume)); + } + + private async getParsedEvents( + startDateUtc: number, + endDateUtc: number, + scrollPage: number, + lastBlockLogs: any[], + ) { + await this.indexerService.getAllEvents( + startDateUtc, + endDateUtc, + analyticsEventsEnum, + this.filterAddresses, + async (logs: any[]) => { + this.logger.log(`Fetched ${logs.length} logs on page ${scrollPage}`); + scrollPage += 1; + + const groupedLogs = logs.groupBy((log) => log.timestamp); + + const blockLogs = Array.from(Object.keys(groupedLogs).sort()).map( + (key) => groupedLogs[key], + ); + + if (blockLogs.length > 0) { + blockLogs[0] = [...lastBlockLogs, ...blockLogs[0]]; + } else { + blockLogs.push(lastBlockLogs); + } + + const blockLogsLength = + blockLogs.length === 0 ? blockLogs.length : blockLogs.length - 1; + for (let i = 0; i < blockLogsLength; i++) { + const eventsRaw = blockLogs[i] + .map((logs: { events: any }) => logs.events) + .flat(); + const events = eventsRaw.filter((event: { identifier: string }) => + analyticsEventsEnum.includes(event.identifier), + ); + + await this.processEvents(events); + } + + if (blockLogs.length > 0) { + lastBlockLogs = blockLogs[blockLogs.length - 1]; + } + }, + ); + return { scrollPage, lastBlockLogs }; + } + + private getDataGroupedByCollectionAndToken() { + return this.data + .groupBy((x) => x.collection, true) + .map((group: { key: any; values: any[] }) => ({ + collection: group.key, + tokens: group.values + .groupBy((g: { paymentToken: any }) => g.paymentToken, true) + .map((group: { key: any; values: any[] }) => ({ + paymentToken: group.key, + sum: group.values + .sumBigInt((x: { value: BigInt }) => BigInt(x.value.toString())) + .toString(), + })), + })); + } + + private async getTokensWithDetails() { + const tokens = [...new Set(this.data.map((item) => item.paymentToken))]; + let tokensWithPrice = []; + for (const token of tokens) { + const tokenData = await this.usdPriceService.getToken(token); + tokensWithPrice.push({ + key: token, + usdPrice: tokenData.priceUsd, + decimals: tokenData.decimals, + }); + } + return tokensWithPrice.groupBy((t) => t.key); + } + + async getFilterAddresses(): Promise { + this.filterAddresses = []; + this.filterAddresses = + await this.marketplacesService.getMarketplacesAddreses(); + } + + private async processEvents(rawEvents: any[]): Promise { + const performanceProfiler = new PerformanceProfiler(); + + const events: any[] = rawEvents.filter( + (rawEvent: { address: string; identifier: string }) => + this.filterAddresses.find( + (filterAddress) => rawEvent.address === filterAddress, + ) !== undefined, + ); + + if (events.length === 0) { + return; + } + + for (const rawEvent of events) { + try { + let parsedEvent = undefined; + if (rawEvent.identifier === 'acceptOffer') { + parsedEvent = await this.acceptEventParser.handle(rawEvent); + } else { + parsedEvent = await this.buyEventHandler.handle(rawEvent, 'hash'); + } + + if (parsedEvent) this.data.push(parsedEvent); + } catch (error) { + if (error?.message?.includes('Cannot create address from')) { + this.logger.log('Invalid event'); + } else { + this.logger.log(`Could not process event:`, rawEvent); + this.logger.log(error); + } + // throw error; + continue; + } + } + performanceProfiler.stop(); + } +} diff --git a/src/modules/analytics/trendingEventsEnum.ts b/src/modules/analytics/trendingEventsEnum.ts deleted file mode 100644 index c69ffae42..000000000 --- a/src/modules/analytics/trendingEventsEnum.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const trendingEventsEnum = [ - 'buy', - 'buyNft', - 'buyFor', - 'buySft', - 'bulkBuy', - 'acceptOffer', - // 'purchase', -]; diff --git a/src/modules/asset-history/assets-history.service.ts b/src/modules/asset-history/assets-history.service.ts index 14b24cb4c..f495fa884 100644 --- a/src/modules/asset-history/assets-history.service.ts +++ b/src/modules/asset-history/assets-history.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { mxConfig } from 'src/config'; import { AuctionEventEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, NftEventEnum, Price, @@ -218,9 +218,7 @@ export class AssetsHistoryService { ]; } if ( - Object.values(ElrondNftsSwapAuctionEventEnum).includes( - eventIdentifier, - ) + Object.values(KroganSwapAuctionEventEnum).includes(eventIdentifier) ) { return [ NftEventTypeEnum.ElrondNftsSwapAuctionEventEnum, diff --git a/src/modules/asset-history/services/assets-history.nfts-swap-auction.service.ts b/src/modules/asset-history/services/assets-history.nfts-swap-auction.service.ts index 1b633853e..aff3f5dc8 100644 --- a/src/modules/asset-history/services/assets-history.nfts-swap-auction.service.ts +++ b/src/modules/asset-history/services/assets-history.nfts-swap-auction.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { AssetActionEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, } from 'src/modules/assets/models'; import { AssetHistoryInput as AssetHistoryLogInput } from '../models/asset-history-log-input'; @@ -14,7 +14,7 @@ export class AssetsHistoryElrondNftsSwapEventsService { mainEvent: any, ): AssetHistoryLogInput { switch (eventType) { - case ElrondNftsSwapAuctionEventEnum.NftSwap: { + case KroganSwapAuctionEventEnum.NftSwap: { return new AssetHistoryLogInput({ event: mainEvent, action: AssetActionEnum.StartedAuction, @@ -23,7 +23,7 @@ export class AssetsHistoryElrondNftsSwapEventsService { sender: mainEvent.events[1].address, }); } - case ElrondNftsSwapAuctionEventEnum.WithdrawSwap: { + case KroganSwapAuctionEventEnum.WithdrawSwap: { const withdrawSwap = mainEvent.events.find( (event) => event.identifier === eventType, ); @@ -37,7 +37,7 @@ export class AssetsHistoryElrondNftsSwapEventsService { sender: txSender, }); } - case ElrondNftsSwapAuctionEventEnum.Purchase: { + case KroganSwapAuctionEventEnum.Purchase: { const purchaseEvent = mainEvent.events.find( (event) => event.identifier === eventType, ); diff --git a/src/modules/assets/models/AuctionEvent.enum.ts b/src/modules/assets/models/AuctionEvent.enum.ts index fe3f9a72b..ae2f6f03d 100644 --- a/src/modules/assets/models/AuctionEvent.enum.ts +++ b/src/modules/assets/models/AuctionEvent.enum.ts @@ -31,7 +31,7 @@ export enum ExternalAuctionEventEnum { UserDeposit = 'user_deposit', } -export enum ElrondNftsSwapAuctionEventEnum { +export enum KroganSwapAuctionEventEnum { NftSwap = 'nftSwap', WithdrawSwap = 'withdrawSwap', NftSwapUpdate = 'nftSwapUpdate', diff --git a/src/modules/common/api-config/api.config.service.ts b/src/modules/common/api-config/api.config.service.ts index 5c4efeb5e..36fc2c3f3 100644 --- a/src/modules/common/api-config/api.config.service.ts +++ b/src/modules/common/api-config/api.config.service.ts @@ -60,6 +60,18 @@ export class ApiConfigService { return this.getGenericConfig('ELROND_TOOLS'); } + getPublicDataApi(): string { + return this.getGenericConfig('MX_PUBLIC_DATA_API'); + } + + getDataUrl(): string { + return this.getGenericConfig('MX_EXTRAS_API'); + } + + getDataToolsApiKey(): string { + return this.configService.get('DATA_API_KEY'); + } + getKeepAliveTimeoutDownstream(): number { return parseInt( this.getGenericConfig('KEEPALIVE_TIMEOUT_DOWNSTREAM'), @@ -116,4 +128,44 @@ export class ApiConfigService { getServerTimeout(): number { return this.getGenericConfig('KEEPALIVE_TIMEOUT_UPSTREAM') ?? 60000; } + + getTimescaleDbHost(): string { + const host = this.configService.get('TIMESCALEDB_URL'); + if (!host) { + throw new Error('No TIMESCALEDB_URL present'); + } + return host; + } + + getTimescaleDbPort(): number { + const port = this.configService.get('TIMESCALEDB_PORT'); + if (!port) { + throw new Error('No TIMESCALEDB_PORT present'); + } + return parseInt(port); + } + + getTimescaleDbDatabase(): string { + const database = this.configService.get('TIMESCALEDB_DATABASE'); + if (!database) { + throw new Error('No TIMESCALEDB_DATABASE present'); + } + return database; + } + + getTimescaleDbUsername(): string { + const username = this.configService.get('TIMESCALEDB_USERNAME'); + if (!username) { + throw new Error('No TIMESCALEDB_USERNAME present'); + } + return username; + } + + getTimescaleDbPassword(): string { + const password = this.configService.get('TIMESCALEDB_PASSWORD'); + if (!password) { + throw new Error('No TIMESCALEDB_PASSWORD present'); + } + return password; + } } diff --git a/src/modules/explore-stats/explore-stats.resolver.ts b/src/modules/explore-stats/explore-stats.resolver.ts index eb14b6da8..5c2d8010e 100644 --- a/src/modules/explore-stats/explore-stats.resolver.ts +++ b/src/modules/explore-stats/explore-stats.resolver.ts @@ -17,7 +17,7 @@ export class ExploreStatsResolver { @Query(() => ExploreNftsStats) async exploreNftsStats(): Promise { - return this.exploreStatsService.getExpoloreNftsStats(); + return this.exploreStatsService.getExploreNftsStats(); } @Query(() => ExploreCollectionsStats) diff --git a/src/modules/explore-stats/explore-stats.service.ts b/src/modules/explore-stats/explore-stats.service.ts index befae6abc..07d57bbb1 100644 --- a/src/modules/explore-stats/explore-stats.service.ts +++ b/src/modules/explore-stats/explore-stats.service.ts @@ -24,7 +24,7 @@ export class ExploreStatsService { private auctionsService: AuctionsGetterService, private apiService: MxApiService, private offersService: OffersService, - ) {} + ) { } async getExploreStats(): Promise { const [, collections] = @@ -50,7 +50,7 @@ export class ExploreStatsService { }); } - async getExpoloreNftsStats(): Promise { + async getExploreNftsStats(): Promise { const allNftsCount = await this.getOrSetTotalNftsCount(); const [, buyNowCount] = diff --git a/src/modules/marketplaces/marketplaces-events-indexing.service.ts b/src/modules/marketplaces/marketplaces-events-indexing.service.ts index 81599b038..8fedad9e3 100644 --- a/src/modules/marketplaces/marketplaces-events-indexing.service.ts +++ b/src/modules/marketplaces/marketplaces-events-indexing.service.ts @@ -13,7 +13,7 @@ import { } from './marketplaces.elastic.queries'; import { AuctionEventEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from '../assets/models'; @@ -271,7 +271,7 @@ export class MarketplaceEventsIndexingService { return ( !Object.values(AuctionEventEnum).includes(eventIdentifier) && !Object.values(ExternalAuctionEventEnum).includes(eventIdentifier) && - !Object.values(ElrondNftsSwapAuctionEventEnum).includes(eventIdentifier) + !Object.values(KroganSwapAuctionEventEnum).includes(eventIdentifier) ); } } diff --git a/src/modules/marketplaces/marketplaces-reindex-events-summary.service.ts b/src/modules/marketplaces/marketplaces-reindex-events-summary.service.ts index a53caae66..f41384a01 100644 --- a/src/modules/marketplaces/marketplaces-reindex-events-summary.service.ts +++ b/src/modules/marketplaces/marketplaces-reindex-events-summary.service.ts @@ -3,7 +3,7 @@ import { MarketplaceEventsEntity } from 'src/db/marketplaces/marketplace-events. import { Marketplace } from './models'; import { AuctionEventEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from '../assets/models'; import { AuctionStartedSummary as AuctionStartedSummary } from './models/marketplaces-reindex-events-summaries/AuctionStartedSummary'; @@ -69,12 +69,12 @@ export class MarketplacesReindexEventsSummaryService { case AuctionEventEnum.AuctionTokenEvent: case ExternalAuctionEventEnum.Listing: case ExternalAuctionEventEnum.ListNftOnMarketplace: - case ElrondNftsSwapAuctionEventEnum.NftSwap: { + case KroganSwapAuctionEventEnum.NftSwap: { return AuctionStartedSummary.fromAuctionTokenEventAndTx(event, txData); } case AuctionEventEnum.WithdrawEvent: case ExternalAuctionEventEnum.ClaimBackNft: - case ElrondNftsSwapAuctionEventEnum.WithdrawSwap: + case KroganSwapAuctionEventEnum.WithdrawSwap: case ExternalAuctionEventEnum.ReturnListing: { return AuctionClosedSummary.fromWithdrawAuctionEventAndTx( event, @@ -88,7 +88,7 @@ export class MarketplacesReindexEventsSummaryService { case ExternalAuctionEventEnum.Buy: case ExternalAuctionEventEnum.BuyFor: case ExternalAuctionEventEnum.BuyNft: - case ElrondNftsSwapAuctionEventEnum.Purchase: + case KroganSwapAuctionEventEnum.Purchase: case ExternalAuctionEventEnum.BulkBuy: { return AuctionBuySummary.fromBuySftEventAndTx( event, @@ -97,7 +97,7 @@ export class MarketplacesReindexEventsSummaryService { ); } case AuctionEventEnum.BidEvent: - case ElrondNftsSwapAuctionEventEnum.Bid: { + case KroganSwapAuctionEventEnum.Bid: { return AuctionBidSummary.fromBidEventAndTx( event, txData, @@ -141,8 +141,8 @@ export class MarketplacesReindexEventsSummaryService { ); } case ExternalAuctionEventEnum.UpdateListing: - case ElrondNftsSwapAuctionEventEnum.NftSwapUpdate: - case ElrondNftsSwapAuctionEventEnum.NftSwapExtend: { + case KroganSwapAuctionEventEnum.NftSwapUpdate: + case KroganSwapAuctionEventEnum.NftSwapExtend: { return AuctionUpdatedSummary.fromUpdateListingEventAndTx(event, txData); } case ExternalAuctionEventEnum.AcceptGlobalOffer: { diff --git a/src/modules/marketplaces/marketplaces.service.ts b/src/modules/marketplaces/marketplaces.service.ts index fb5eeea8c..7f2ae3b2a 100644 --- a/src/modules/marketplaces/marketplaces.service.ts +++ b/src/modules/marketplaces/marketplaces.service.ts @@ -13,12 +13,12 @@ export class MarketplacesService { constructor( private persistenceService: PersistenceService, private cacheService: MarketplacesCachingService, - ) {} + ) { } async getMarketplaces( limit: number = 10, offset: number = 0, - filters: MarketplaceFilters, + filters?: MarketplaceFilters, ): Promise> { let allMarketplaces = await this.getAllMarketplaces(); diff --git a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionBuySummary.ts b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionBuySummary.ts index c4abf956c..ed2caccac 100644 --- a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionBuySummary.ts +++ b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionBuySummary.ts @@ -3,7 +3,7 @@ import { ObjectType } from '@nestjs/graphql'; import { MarketplaceEventsEntity } from 'src/db/marketplaces/marketplace-events.entity'; import { AssetActionEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { BuySftEvent } from 'src/modules/rabbitmq/entities/auction'; @@ -39,7 +39,7 @@ export class AuctionBuySummary extends ReindexGenericSummary { if ( event.hasOneOfEventTopicIdentifiers([ ExternalAuctionEventEnum.UpdateOffer, - ElrondNftsSwapAuctionEventEnum.UpdateListing, + KroganSwapAuctionEventEnum.UpdateListing, ]) ) { return; @@ -82,7 +82,7 @@ export class AuctionBuySummary extends ReindexGenericSummary { return new ClaimEvent(genericEvent).getTopics(); } - if (event.hasEventIdentifier(ElrondNftsSwapAuctionEventEnum.Purchase)) { + if (event.hasEventIdentifier(KroganSwapAuctionEventEnum.Purchase)) { return new ElrondSwapBuyEvent(genericEvent).getTopics(); } diff --git a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionClosedSummary.ts b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionClosedSummary.ts index 5631691f7..6ba193ed4 100644 --- a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionClosedSummary.ts +++ b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionClosedSummary.ts @@ -3,7 +3,7 @@ import { ObjectType } from '@nestjs/graphql'; import { MarketplaceEventsEntity } from 'src/db/marketplaces/marketplace-events.entity'; import { AssetActionEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { AuctionTypeEnum } from 'src/modules/auctions/models'; @@ -63,7 +63,7 @@ export class ReindexAuctionClosedSummary extends ReindexGenericSummary { return new ClaimEvent(genericEvent).getTopics(); } - if (event.hasEventIdentifier(ElrondNftsSwapAuctionEventEnum.WithdrawSwap)) { + if (event.hasEventIdentifier(KroganSwapAuctionEventEnum.WithdrawSwap)) { return new ElrondSwapWithdrawEvent(genericEvent).getTopics(); } diff --git a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionStartedSummary.ts b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionStartedSummary.ts index f412cd81a..3d0ed53aa 100644 --- a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionStartedSummary.ts +++ b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionStartedSummary.ts @@ -3,7 +3,7 @@ import { ObjectType } from '@nestjs/graphql'; import { MarketplaceEventsEntity } from 'src/db/marketplaces/marketplace-events.entity'; import { AssetActionEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { @@ -88,7 +88,7 @@ export class AuctionStartedSummary extends ReindexGenericSummary { ? GenericEvent.fromEventResponse(event.data.eventData) : undefined; - if (event.hasEventIdentifier(ElrondNftsSwapAuctionEventEnum.NftSwap)) { + if (event.hasEventIdentifier(KroganSwapAuctionEventEnum.NftSwap)) { try { const topics = new ElrondSwapAuctionEvent(genericEvent).getTopics(); if (parseInt(topics.auctionType) === ElrondSwapAuctionTypeEnum.Swap) { diff --git a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionUpdatedSummary.ts b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionUpdatedSummary.ts index 59bc98bb2..2b3deed38 100644 --- a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionUpdatedSummary.ts +++ b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/AuctionUpdatedSummary.ts @@ -3,7 +3,7 @@ import { ObjectType } from '@nestjs/graphql'; import { MarketplaceEventsEntity } from 'src/db/marketplaces/marketplace-events.entity'; import { AssetActionEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, } from 'src/modules/assets/models'; import { ElrondSwapUpdateEvent } from 'src/modules/rabbitmq/entities/auction/elrondnftswap/elrondswap-updateAuction.event'; import { UpdateListingEvent } from 'src/modules/rabbitmq/entities/auction/updateListing.event'; @@ -59,8 +59,8 @@ export class AuctionUpdatedSummary extends ReindexGenericSummary { if ( event.hasOneOfEventIdentifiers([ - ElrondNftsSwapAuctionEventEnum.NftSwapUpdate, - ElrondNftsSwapAuctionEventEnum.NftSwapExtend, + KroganSwapAuctionEventEnum.NftSwapUpdate, + KroganSwapAuctionEventEnum.NftSwapExtend, ]) ) { return new ElrondSwapUpdateEvent(genericEvent).getTopics(); diff --git a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/OfferAcceptedSummary.ts b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/OfferAcceptedSummary.ts index 9dc58b757..5db7cb0df 100644 --- a/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/OfferAcceptedSummary.ts +++ b/src/modules/marketplaces/models/marketplaces-reindex-events-summaries/OfferAcceptedSummary.ts @@ -1,6 +1,6 @@ import { ObjectType } from '@nestjs/graphql'; import { MarketplaceEventsEntity } from 'src/db/marketplaces/marketplace-events.entity'; -import { ElrondNftsSwapAuctionEventEnum } from 'src/modules/assets/models'; +import { KroganSwapAuctionEventEnum } from 'src/modules/assets/models'; import { AssetOfferEnum } from 'src/modules/assets/models/AssetOfferEnum'; import { AcceptOfferEvent } from 'src/modules/rabbitmq/entities/auction/acceptOffer.event'; import { AcceptOfferDeadrareEvent } from 'src/modules/rabbitmq/entities/auction/acceptOfferDeadrare.event'; diff --git a/src/modules/nftCollections/collections-getter.service.ts b/src/modules/nftCollections/collections-getter.service.ts index c3c0f89ca..9a5d8596c 100644 --- a/src/modules/nftCollections/collections-getter.service.ts +++ b/src/modules/nftCollections/collections-getter.service.ts @@ -19,7 +19,7 @@ import { randomBetween } from 'src/utils/helpers'; import { CollectionNftTrait } from '../nft-traits/models/collection-traits.model'; import { DocumentDbService } from 'src/document-db/document-db.service'; import { BlacklistedCollectionsService } from '../blacklist/blacklisted-collections.service'; -import { AnalyticsService } from '../analytics/analytics.service'; +import { TrendingCollectionsService } from '../analytics/trending/trending-collections.service'; @Injectable() export class CollectionsGetterService { @@ -31,7 +31,7 @@ export class CollectionsGetterService { private collectionNftsCountRedis: CollectionsNftsCountRedisHandler, private collectionNftsRedis: CollectionsNftsRedisHandler, private cacheService: CachingService, - private analyticsService: AnalyticsService, + private analyticsService: TrendingCollectionsService, private documentDbService: DocumentDbService, private blacklistedCollectionsService: BlacklistedCollectionsService, ) {} @@ -76,6 +76,16 @@ export class CollectionsGetterService { return await this.getFilteredCollections(offset, limit, filters, sorting); } + async getCollectionsByIdentifiers( + identifiers: string[], + ): Promise { + const [collections] = await this.getOrSetFullCollections(); + + return collections?.filter( + (x) => identifiers.includes(x.collection) === true, + ); + } + async getTrendingCollections( offset: number = 0, limit: number = 10, diff --git a/src/modules/nftCollections/models/Collection.dto.ts b/src/modules/nftCollections/models/Collection.dto.ts index 65b24846e..476c64f1d 100644 --- a/src/modules/nftCollections/models/Collection.dto.ts +++ b/src/modules/nftCollections/models/Collection.dto.ts @@ -1,13 +1,13 @@ import { ObjectType, Field, Int } from '@nestjs/graphql'; import { CollectionApi, RolesApi } from 'src/common'; import { Account } from 'src/modules/account-stats/models'; -import { CollectionVolumeLast24 } from 'src/modules/analytics/collection-volume'; import { AssetsResponse } from 'src/modules/assets/models'; import { NftTypeEnum } from 'src/modules/assets/models/NftTypes.enum'; import { ScamInfo } from 'src/modules/assets/models/ScamInfo.dto'; import { CollectionNftTrait } from 'src/modules/nft-traits/models/collection-traits.model'; import { CollectionAsset } from './CollectionAsset.dto'; import { CollectionSocial } from './CollectionSocial.dto'; +import { CollectionVolumeLast24 } from 'src/modules/analytics/models/collection-volume'; @ObjectType() export class Collection { diff --git a/src/modules/rabbitmq/blockchain-events/analytics-events.service.ts b/src/modules/rabbitmq/blockchain-events/analytics-events.service.ts new file mode 100755 index 000000000..e110ba9e8 --- /dev/null +++ b/src/modules/rabbitmq/blockchain-events/analytics-events.service.ts @@ -0,0 +1,19 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { AnalyticsService } from 'src/modules/analytics/analytics.service'; +import { DateUtils } from 'src/utils/date-utils'; + +@Injectable() +export class AnalyticsEventsService { + private readonly logger = new Logger(AnalyticsEventsService.name); + + constructor(private analyticsService: AnalyticsService) { } + + public async handleBuyEvents(auctionEvents: any[]) { + this.analyticsService.processEvents( + auctionEvents, + DateUtils.getCurrentTimestamp(), + DateUtils.getCurrentTimestamp(), + true + ); + } +} diff --git a/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts index 5aa5c1267..99baac84f 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/buy-event.handler.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { AuctionEntity } from 'src/db/auctions'; import { - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { @@ -99,10 +99,10 @@ export class BuyEventHandler { } private getEventAndTopics(event: any, hash: string) { - if (event.identifier === ElrondNftsSwapAuctionEventEnum.Purchase) { + if (event.identifier === KroganSwapAuctionEventEnum.Purchase) { if ( Buffer.from(event.topics[0], 'base64').toString() === - ElrondNftsSwapAuctionEventEnum.UpdateListing + KroganSwapAuctionEventEnum.UpdateListing ) { this.logger.log( `Update Listing event detected for hash '${hash}' at Purchase external marketplace ${event.address}, ignore it for the moment`, diff --git a/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts index 3cf08d5fd..eeab2df68 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/startAuction-event.handler.ts @@ -3,7 +3,7 @@ import { mxConfig } from 'src/config'; import { AuctionEntity } from 'src/db/auctions'; import { AssetByIdentifierService } from 'src/modules/assets'; import { - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { @@ -124,7 +124,7 @@ export class StartAuctionEventHandler { } private getEventAndTopics(event: any) { - if (event.identifier === ElrondNftsSwapAuctionEventEnum.NftSwap) { + if (event.identifier === KroganSwapAuctionEventEnum.NftSwap) { const auctionTokenEvent = new ElrondSwapAuctionEvent(event); const topics = auctionTokenEvent.getTopics(); if (parseInt(topics.auctionType) === ElrondSwapAuctionTypeEnum.Swap) { diff --git a/src/modules/rabbitmq/blockchain-events/handlers/swapUpdate-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/swapUpdate-event.handler.ts index 52dc5363b..ef1855375 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/swapUpdate-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/swapUpdate-event.handler.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { mxConfig } from 'src/config'; import { AuctionEntity } from 'src/db/auctions'; -import { ElrondNftsSwapAuctionEventEnum } from 'src/modules/assets/models'; +import { KroganSwapAuctionEventEnum } from 'src/modules/assets/models'; import { AuctionsGetterService, AuctionsSetterService, @@ -43,7 +43,7 @@ export class SwapUpdateEventHandler { await this.updateAuctionPrice(auction, topics, hash); this.auctionsService.updateAuction( auction, - ElrondNftsSwapAuctionEventEnum.NftSwapUpdate, + KroganSwapAuctionEventEnum.NftSwapUpdate, ); } } diff --git a/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts b/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts index 2e3f5973f..61f59d9f2 100644 --- a/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts +++ b/src/modules/rabbitmq/blockchain-events/handlers/withdrawAuction-event.handler.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { AuctionEntity } from 'src/db/auctions'; import { AuctionEventEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, } from 'src/modules/assets/models'; import { @@ -65,7 +65,7 @@ export class WithdrawAuctionEventHandler { } private getEventAndTopics(event: any) { - if (event.identifier === ElrondNftsSwapAuctionEventEnum.WithdrawSwap) { + if (event.identifier === KroganSwapAuctionEventEnum.WithdrawSwap) { const withdraw = new ElrondSwapWithdrawEvent(event); const topics = withdraw.getTopics(); return { withdraw, topics }; diff --git a/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts b/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts index 8276d1f9e..3a2135502 100644 --- a/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts +++ b/src/modules/rabbitmq/blockchain-events/marketplace-events.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { AuctionEventEnum, - ElrondNftsSwapAuctionEventEnum, + KroganSwapAuctionEventEnum, ExternalAuctionEventEnum, MarketplaceEventEnum, } from 'src/modules/assets/models'; @@ -38,7 +38,7 @@ export class MarketplaceEventsService { private readonly slackReportService: SlackReportService, private sendOfferEventHandler: SendOfferEventHandler, private withdrawOfferEventHandler: WithdrawOfferEventHandler, - ) { } + ) {} public async handleNftAuctionEvents( auctionEvents: any[], @@ -48,7 +48,7 @@ export class MarketplaceEventsService { for (let event of auctionEvents) { switch (event.identifier) { case AuctionEventEnum.BidEvent: - case ElrondNftsSwapAuctionEventEnum.Bid: + case KroganSwapAuctionEventEnum.Bid: await this.bidEventHandler.handle(event, hash, marketplaceType); break; @@ -57,11 +57,11 @@ export class MarketplaceEventsService { case ExternalAuctionEventEnum.BulkBuy: case ExternalAuctionEventEnum.BuyFor: case ExternalAuctionEventEnum.BuyNft: - case ElrondNftsSwapAuctionEventEnum.Purchase: + case KroganSwapAuctionEventEnum.Purchase: const eventName = Buffer.from(event.topics[0], 'base64').toString(); if ( eventName === ExternalAuctionEventEnum.UpdateOffer || - eventName === ElrondNftsSwapAuctionEventEnum.UpdateListing + eventName === KroganSwapAuctionEventEnum.UpdateListing ) { this.logger.log( `${eventName} event detected for hash '${hash}' for marketplace ${event.address}, ignore it for the moment`, @@ -71,7 +71,7 @@ export class MarketplaceEventsService { await this.buyEventHandler.handle(event, hash, marketplaceType); break; case AuctionEventEnum.WithdrawEvent: - case ElrondNftsSwapAuctionEventEnum.WithdrawSwap: + case KroganSwapAuctionEventEnum.WithdrawSwap: case ExternalAuctionEventEnum.ClaimBackNft: case ExternalAuctionEventEnum.ReturnListing: if ( @@ -99,7 +99,7 @@ export class MarketplaceEventsService { case AuctionEventEnum.AuctionTokenEvent: case ExternalAuctionEventEnum.Listing: case ExternalAuctionEventEnum.ListNftOnMarketplace: - case ElrondNftsSwapAuctionEventEnum.NftSwap: + case KroganSwapAuctionEventEnum.NftSwap: await this.startAuctionEventHandler.handle( event, hash, @@ -180,8 +180,8 @@ export class MarketplaceEventsService { marketplaceType, ); break; - case ElrondNftsSwapAuctionEventEnum.NftSwapUpdate: - case ElrondNftsSwapAuctionEventEnum.NftSwapExtend: + case KroganSwapAuctionEventEnum.NftSwapUpdate: + case KroganSwapAuctionEventEnum.NftSwapExtend: await this.swapUpdateEventHandler.handle( event, hash, diff --git a/src/modules/rabbitmq/blockchain-events/nft-events.consumer.ts b/src/modules/rabbitmq/blockchain-events/nft-events.consumer.ts index 50f175f70..955485d71 100644 --- a/src/modules/rabbitmq/blockchain-events/nft-events.consumer.ts +++ b/src/modules/rabbitmq/blockchain-events/nft-events.consumer.ts @@ -8,6 +8,7 @@ import { CompetingRabbitConsumer } from '../rabbitmq.consumers'; import { MarketplaceEventsService } from './marketplace-events.service'; import { MinterEventsService } from './minter-events.service'; import { NftEventsService } from './nft-events.service'; +import { AnalyticsEventsService } from './analytics-events.service'; @Injectable() export class NftEventsConsumer { @@ -18,7 +19,8 @@ export class NftEventsConsumer { private readonly marketplaceEventsIndexingService: MarketplaceEventsIndexingService, private readonly minterEventsService: MinterEventsService, private readonly marketplaceService: MarketplacesService, - ) {} + private readonly analyticsEventsService: AnalyticsEventsService, + ) { } @CompetingRabbitConsumer({ connection: 'default', @@ -42,7 +44,7 @@ export class NftEventsConsumer { externalMarketplaces.includes(e.address) === true, ); - const minters = process.env.MINTERS_ADDRESSES.split(',').map((entry) => { + const minters = process.env.MINTERS_ADDRESSES?.split(',').map((entry) => { return entry.toLowerCase().trim(); }); await this.nftEventsService.handleNftMintEvents( @@ -67,11 +69,16 @@ export class NftEventsConsumer { ); await this.minterEventsService.handleNftMinterEvents( nftAuctionEvents?.events?.filter( - (e: { address: any }) => minters.includes(e.address) === true, + (e: { address: any }) => minters?.includes(e.address) === true, ), nftAuctionEvents.hash, ); + await this.analyticsEventsService.handleBuyEvents( + [...internalMarketplaceEvents, ...externalMarketplaceEvents], + ); + + if (this.apiConfigService.isReindexMarketplaceEventsFlagActive()) { await this.marketplaceEventsIndexingService.reindexLatestMarketplacesEvents( internalMarketplaceEvents.concat(externalMarketplaceEvents), diff --git a/src/modules/rabbitmq/blockchain-events/nft-events.module.ts b/src/modules/rabbitmq/blockchain-events/nft-events.module.ts index 6e25c9d57..56d39e085 100644 --- a/src/modules/rabbitmq/blockchain-events/nft-events.module.ts +++ b/src/modules/rabbitmq/blockchain-events/nft-events.module.ts @@ -42,6 +42,8 @@ import { AcceptOfferEventHandler } from './handlers/acceptOffer-event.handler'; import { WithdrawOfferEventHandler } from './handlers/withdrawOffer-event.handler'; import { UpdateListingEventHandler } from './handlers/updateListing-event.handler'; import { PluginModule } from 'src/plugins/plugin.module'; +import { AnalyticsEventsService } from './analytics-events.service'; +import { AnalyticsModule } from 'src/modules/analytics/analytics.module'; @Module({ imports: [ @@ -58,6 +60,7 @@ import { PluginModule } from 'src/plugins/plugin.module'; UsdPriceModuleGraph, NftRarityModuleGraph, ScamModule, + AnalyticsModule ], providers: [ Logger, @@ -91,6 +94,7 @@ import { PluginModule } from 'src/plugins/plugin.module'; NsfwUpdaterService, FeedEventsSenderService, UsdPriceService, + AnalyticsEventsService ], exports: [NftEventsService], }) diff --git a/src/modules/rabbitmq/entities/auction/auctionToken.event.topics.ts b/src/modules/rabbitmq/entities/auction/auctionToken.event.topics.ts index 3fd618e61..29b79614e 100644 --- a/src/modules/rabbitmq/entities/auction/auctionToken.event.topics.ts +++ b/src/modules/rabbitmq/entities/auction/auctionToken.event.topics.ts @@ -61,6 +61,7 @@ export class AuctionTokenEventsTopics { auctionId: this.auctionId, nrAuctionTokens: this.nrAuctionTokens, minBid: this.minBid, + price: this.minBid, maxBid: this.maxBid, startTime: this.startTime, endTime: this.endTime, diff --git a/src/modules/rabbitmq/entities/auction/buySft.event.topics.ts b/src/modules/rabbitmq/entities/auction/buySft.event.topics.ts index 0aea2ffb0..68b6e2aea 100644 --- a/src/modules/rabbitmq/entities/auction/buySft.event.topics.ts +++ b/src/modules/rabbitmq/entities/auction/buySft.event.topics.ts @@ -1,5 +1,6 @@ import { Address } from '@multiversx/sdk-core'; import '../../../../utils/extensions'; +import { BinaryUtils } from '@multiversx/sdk-nestjs'; export class BuySftEventsTopics { private collection: string; @@ -8,6 +9,7 @@ export class BuySftEventsTopics { private currentWinner: Address; private bid: string; private boughtTokens: string; + private paymentToken: string; constructor(rawTopics: string[]) { this.collection = Buffer.from(rawTopics[1], 'base64').toString(); @@ -20,6 +22,7 @@ export class BuySftEventsTopics { this.bid = Buffer.from(rawTopics[6], 'base64') .toString('hex') .hexBigNumberToString(); + this.paymentToken = BinaryUtils.base64Decode(rawTopics[8] ?? ''); } toPlainObject() { @@ -30,6 +33,7 @@ export class BuySftEventsTopics { auctionId: this.auctionId, bid: this.bid, boughtTokens: this.boughtTokens, + paymentToken: this.paymentToken, }; } } diff --git a/src/modules/rabbitmq/entities/auction/claim.event.topics.ts b/src/modules/rabbitmq/entities/auction/claim.event.topics.ts index a43b1bcb5..cf194cb22 100644 --- a/src/modules/rabbitmq/entities/auction/claim.event.topics.ts +++ b/src/modules/rabbitmq/entities/auction/claim.event.topics.ts @@ -7,6 +7,7 @@ export class ClaimEventsTopics { private bid: string = '0'; private auctionId: string; private boughtTokens: string = '1'; + private paymentToken: string; constructor(rawTopics: string[]) { this.currentWinner = new Address(Buffer.from(rawTopics[1], 'base64')); @@ -22,6 +23,7 @@ export class ClaimEventsTopics { auctionId: this.auctionId, boughtTokens: this.boughtTokens, bid: this.bid, + paymentToken: this.paymentToken, }; } } diff --git a/src/modules/rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event.topics.ts b/src/modules/rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event.topics.ts index 3cc0d5ca1..cbfba92fe 100644 --- a/src/modules/rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event.topics.ts +++ b/src/modules/rabbitmq/entities/auction/elrondnftswap/elrondswap-buy.event.topics.ts @@ -7,6 +7,7 @@ export class ElrondSwapBuyTopics { private boughtTokens: string; private currentWinner: Address; private bid: string; + private paymentToken: string; constructor(rawTopics: string[]) { this.auctionId = Buffer.from(rawTopics[1], 'base64').toString('hex'); @@ -30,6 +31,7 @@ export class ElrondSwapBuyTopics { auctionId: this.auctionId, boughtTokens: this.boughtTokens, bid: this.bid, + paymentToken: this.paymentToken, }; } } diff --git a/src/modules/rabbitmq/entities/generic.event.ts b/src/modules/rabbitmq/entities/generic.event.ts index 99a0d7b13..403e457c9 100644 --- a/src/modules/rabbitmq/entities/generic.event.ts +++ b/src/modules/rabbitmq/entities/generic.event.ts @@ -1,8 +1,9 @@ import { EventResponse } from 'src/common/services/mx-communication/models/elastic-search/event.response'; +import BigNumber from 'bignumber.js'; export class GenericEvent { - private address = ''; - private identifier = ''; + protected address = ''; + protected identifier = ''; protected topics = []; protected data = ''; diff --git a/src/modules/usdPrice/usd-price.service.ts b/src/modules/usdPrice/usd-price.service.ts index 7a3df8184..daa725847 100644 --- a/src/modules/usdPrice/usd-price.service.ts +++ b/src/modules/usdPrice/usd-price.service.ts @@ -41,7 +41,9 @@ export class UsdPriceService { timestamp: number, ): Promise { return await this.cacheService.getOrSetCache( - `${CacheInfo.TokenHistoricalPrice.key}_${token}_${timestamp}`, + `${ + CacheInfo.TokenHistoricalPrice.key + }_${token}_${DateUtils.timestampToIsoStringWithHour(timestamp)}`, async () => await this.getTokenHistoricalPrice(token, timestamp), CacheInfo.TokenHistoricalPrice.ttl, ); @@ -109,16 +111,16 @@ export class UsdPriceService { this.getXexchangeTokens(), ]); - if (cexTokens.includes(tokenId)) { + if (cexTokens?.includes(tokenId)) { { return await this.mxDataApi.getCexPrice( - DateUtils.timestampToIsoStringWithoutTime(timestamp), + DateUtils.timestampToIsoStringWithHour(timestamp), ); } } else if (xExchangeTokens.includes(tokenId)) { return await this.mxDataApi.getXechangeTokenPrice( tokenId, - DateUtils.timestampToIsoStringWithoutTime(timestamp), + DateUtils.timestampToIsoStringWithHour(timestamp), ); } } diff --git a/src/utils/analytics.utils.ts b/src/utils/analytics.utils.ts new file mode 100644 index 000000000..f425a0a8b --- /dev/null +++ b/src/utils/analytics.utils.ts @@ -0,0 +1,24 @@ +import * as moment from 'moment'; + +export const decodeTime = (time: string): [string, moment.unitOfTime.Base] => { + const [timeAmount, timeUnit] = time?.match(/[a-zA-Z]+|[0-9]+/g); + return [timeAmount, timeUnit as moment.unitOfTime.Base]; +}; + +export const computeTimeInterval = ( + time: string, + start?: string, +): [Date, Date] => { + const [timeAmount, timeUnit] = decodeTime(time); + + const startDate1 = moment().utc().subtract(timeAmount, timeUnit); + + const startDate2 = start ? moment.unix(parseInt(start)).utc() : undefined; + + const startDate = startDate2 + ? moment.max(startDate1, startDate2).toDate() + : startDate1?.toDate(); + const endDate = moment().utc().toDate(); + + return [startDate, endDate]; +}; diff --git a/src/utils/date-utils.ts b/src/utils/date-utils.ts index e283b8898..39523965f 100644 --- a/src/utils/date-utils.ts +++ b/src/utils/date-utils.ts @@ -1,3 +1,5 @@ +import * as moment from 'moment'; + export class DateUtils { static getTimestamp(date: Date = new Date()): number { return new Date(date).getTime() / 1000; @@ -39,10 +41,15 @@ export class DateUtils { return new Date(new Date(timestamp * 1000).toUTCString()); } - static timestampToIsoStringWithoutTime(timestamp: number): string { + static getDatewithTimezoneInfo(timestamp: number): Date { + const date = new Date(timestamp * 1000); + return new Date(date.getTime() + date.getTimezoneOffset() * 60000); + } + + static timestampToIsoStringWithHour(timestamp: number): string { let date = new Date(timestamp * 1000); - date.setUTCHours(0, 0, 0, 0); - return date.toISOString(); + date.setUTCMinutes(0, 0, 0) / 1000; + return moment.utc(date).format('yyyy-MM-DD HH:mm:ss'); } static isTimestampToday(timestamp: number): boolean { diff --git a/tsconfig.json b/tsconfig.json index 5edc914c2..eeda91d8c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "incremental": true, "strict": false, "skipLibCheck": true, - "allowJs": true + "allowJs": true, + "lib": ["es2019"] } }