From 19080a67ab7045ed61cec6cf56e839fd06681b66 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 12 Nov 2024 23:08:51 +0200 Subject: [PATCH] feat: wip migrate server to nestjs --- package.json | 1 + packages/server-nest/.env.example | 102 + packages/server-nest/.eslintrc.js | 25 + packages/server-nest/.gitignore | 56 + packages/server-nest/.prettierrc | 4 + packages/server-nest/.todo | 1 + packages/server-nest/README.md | 1 + packages/server-nest/nest-cli.json | 11 + packages/server-nest/package.json | 103 + .../src/common/config/gotenberg.ts | 6 + .../server-nest/src/common/config/index.ts | 21 + .../src/common/config/lemonsqueezy.ts | 7 + .../src/common/config/open-exchange.ts | 5 + .../server-nest/src/common/config/plaid.ts | 8 + .../server-nest/src/common/config/posthog.ts | 6 + packages/server-nest/src/common/config/s3.ts | 9 + .../server-nest/src/common/config/signup.ts | 12 + .../src/common/config/stripe-payment.ts | 9 + .../src/common/config/system-database.ts | 10 + .../src/common/config/tenant-database.ts | 9 + .../server-nest/src/common/events/events.ts | 754 +++++ .../src/common/pipes/ZodValidation.pipe.ts | 20 + .../server-nest/src/constants/accounts.ts | 230 ++ .../server-nest/src/constants/data-types.ts | 7 + packages/server-nest/src/i18n/en/test.json | 21 + .../interceptors/ExcludeNull.interceptor.ts | 15 + .../interceptors/global-prefix.interceptor.ts | 21 + .../src/interceptors/user-ip.interceptor.ts | 21 + .../server-nest/src/interfaces/Account.ts | 174 ++ packages/server-nest/src/interfaces/Item.ts | 165 ++ packages/server-nest/src/interfaces/Model.ts | 192 ++ .../src/interfaces/SubscriptionPlan.ts | 8 + .../libs/accounts-utils/AccountTypesUtils.ts | 101 + .../src/libs/dependency-graph/index.ts | 350 +++ packages/server-nest/src/main.ts | 25 + .../src/middleware/logger.middleware.ts | 13 + packages/server-nest/src/models/Model.ts | 5 + .../src/modules/Accounts/models/Account.ts | 441 +++ .../src/modules/App/App.controller.spec.ts | 22 + .../src/modules/App/App.controller.ts | 12 + .../server-nest/src/modules/App/App.module.ts | 115 + .../src/modules/App/App.service.ts | 24 + .../src/modules/Auth/Auth.constants.ts | 4 + .../modules/Auth/AuthApplication.sevice.ts | 0 .../Auth/AuthForgetPassword.service.ts | 0 .../modules/Auth/AuthResetPassword.service.ts | 0 .../src/modules/Auth/AuthSignin.service.ts | 2 + .../src/modules/Auth/AuthSignup.service.ts | 0 .../server-nest/src/modules/Auth/Jwt.guard.ts | 32 + .../src/modules/Auth/Jwt.strategy.ts | 19 + .../CustomViews/CustomViewBaseModel.ts | 22 + .../src/modules/Items/CreateItem.service.ts | 120 + .../src/modules/Items/DeleteItem.service.ts | 67 + .../src/modules/Items/EditItem.service.ts | 143 + .../src/modules/Items/Item.controller.ts | 44 + .../src/modules/Items/Item.schema.ts | 111 + .../modules/Items/ItemValidator.service.ts | 285 ++ .../src/modules/Items/Items.constants.ts | 141 + .../src/modules/Items/Items.module.ts | 19 + .../src/modules/Items/ServiceError.ts | 13 + .../modules/Items/events/ItemCreated.event.ts | 4 + .../Items/listeners/ItemCreated.listener.ts | 13 + .../src/modules/Items/models/Item.ts | 26 + .../src/modules/Search/SearchableMdel.ts | 22 + .../src/modules/Settings/ModelSettings.ts | 77 + .../Subscription/SubscriptionPeriod.ts | 49 + .../interceptors/Subscription.guard.ts | 46 + .../Subscription/models/PlanSubscription.ts | 246 ++ .../System/SystemDB/SystemDB.constants.ts | 2 + .../System/SystemDB/SystemDB.controller.ts | 12 + .../System/SystemDB/SystemDB.module.ts | 49 + .../SystemModels/SystemModels.constants.ts | 1 + .../SystemModels/SystemModels.module.ts | 34 + .../src/modules/System/models/SystemModel.ts | 3 + .../src/modules/System/models/SystemUser.ts | 14 + .../src/modules/System/models/TenantModel.ts | 12 + .../EnsureTenantIsInitialized.guard.ts | 32 + .../Tenancy/EnsureTenantIsSeeded.guards.ts | 31 + .../TenancyCache/TenancyCache.module.ts | 18 + .../modules/Tenancy/TenancyContext.service.ts | 39 + .../Tenancy/TenancyDB/TenancyDB.constants.ts | 1 + .../Tenancy/TenancyDB/TenancyDB.module.ts | 47 + .../Tenancy/TenancyDB/TransactionsHooks.ts | 58 + .../Tenancy/TenancyDB/UnitOfWork.service.ts | 46 + .../Tenancy/TenancyGlobal.middleware.ts | 25 + .../Tenancy/TenancyIdCls.interceptor.ts | 23 + .../TenancyModels/Tenancy.constants.ts | 1 + .../Tenancy/TenancyModels/Tenancy.module.ts | 24 + .../src/modules/Tenancy/Tenant.controller.ts | 7 + packages/server-nest/test/app.e2e-spec.ts | 24 + packages/server-nest/test/jest-e2e.json | 9 + packages/server-nest/tsconfig.build.json | 4 + packages/server-nest/tsconfig.json | 24 + pnpm-lock.yaml | 2498 ++++++++++++++++- 94 files changed, 7587 insertions(+), 98 deletions(-) create mode 100644 packages/server-nest/.env.example create mode 100644 packages/server-nest/.eslintrc.js create mode 100644 packages/server-nest/.gitignore create mode 100644 packages/server-nest/.prettierrc create mode 100644 packages/server-nest/.todo create mode 100644 packages/server-nest/README.md create mode 100644 packages/server-nest/nest-cli.json create mode 100644 packages/server-nest/package.json create mode 100644 packages/server-nest/src/common/config/gotenberg.ts create mode 100644 packages/server-nest/src/common/config/index.ts create mode 100644 packages/server-nest/src/common/config/lemonsqueezy.ts create mode 100644 packages/server-nest/src/common/config/open-exchange.ts create mode 100644 packages/server-nest/src/common/config/plaid.ts create mode 100644 packages/server-nest/src/common/config/posthog.ts create mode 100644 packages/server-nest/src/common/config/s3.ts create mode 100644 packages/server-nest/src/common/config/signup.ts create mode 100644 packages/server-nest/src/common/config/stripe-payment.ts create mode 100644 packages/server-nest/src/common/config/system-database.ts create mode 100644 packages/server-nest/src/common/config/tenant-database.ts create mode 100644 packages/server-nest/src/common/events/events.ts create mode 100644 packages/server-nest/src/common/pipes/ZodValidation.pipe.ts create mode 100644 packages/server-nest/src/constants/accounts.ts create mode 100644 packages/server-nest/src/constants/data-types.ts create mode 100644 packages/server-nest/src/i18n/en/test.json create mode 100644 packages/server-nest/src/interceptors/ExcludeNull.interceptor.ts create mode 100644 packages/server-nest/src/interceptors/global-prefix.interceptor.ts create mode 100644 packages/server-nest/src/interceptors/user-ip.interceptor.ts create mode 100644 packages/server-nest/src/interfaces/Account.ts create mode 100644 packages/server-nest/src/interfaces/Item.ts create mode 100644 packages/server-nest/src/interfaces/Model.ts create mode 100644 packages/server-nest/src/interfaces/SubscriptionPlan.ts create mode 100644 packages/server-nest/src/libs/accounts-utils/AccountTypesUtils.ts create mode 100644 packages/server-nest/src/libs/dependency-graph/index.ts create mode 100644 packages/server-nest/src/main.ts create mode 100644 packages/server-nest/src/middleware/logger.middleware.ts create mode 100644 packages/server-nest/src/models/Model.ts create mode 100644 packages/server-nest/src/modules/Accounts/models/Account.ts create mode 100644 packages/server-nest/src/modules/App/App.controller.spec.ts create mode 100644 packages/server-nest/src/modules/App/App.controller.ts create mode 100644 packages/server-nest/src/modules/App/App.module.ts create mode 100644 packages/server-nest/src/modules/App/App.service.ts create mode 100644 packages/server-nest/src/modules/Auth/Auth.constants.ts create mode 100644 packages/server-nest/src/modules/Auth/AuthApplication.sevice.ts create mode 100644 packages/server-nest/src/modules/Auth/AuthForgetPassword.service.ts create mode 100644 packages/server-nest/src/modules/Auth/AuthResetPassword.service.ts create mode 100644 packages/server-nest/src/modules/Auth/AuthSignin.service.ts create mode 100644 packages/server-nest/src/modules/Auth/AuthSignup.service.ts create mode 100644 packages/server-nest/src/modules/Auth/Jwt.guard.ts create mode 100644 packages/server-nest/src/modules/Auth/Jwt.strategy.ts create mode 100644 packages/server-nest/src/modules/CustomViews/CustomViewBaseModel.ts create mode 100644 packages/server-nest/src/modules/Items/CreateItem.service.ts create mode 100644 packages/server-nest/src/modules/Items/DeleteItem.service.ts create mode 100644 packages/server-nest/src/modules/Items/EditItem.service.ts create mode 100644 packages/server-nest/src/modules/Items/Item.controller.ts create mode 100644 packages/server-nest/src/modules/Items/Item.schema.ts create mode 100644 packages/server-nest/src/modules/Items/ItemValidator.service.ts create mode 100644 packages/server-nest/src/modules/Items/Items.constants.ts create mode 100644 packages/server-nest/src/modules/Items/Items.module.ts create mode 100644 packages/server-nest/src/modules/Items/ServiceError.ts create mode 100644 packages/server-nest/src/modules/Items/events/ItemCreated.event.ts create mode 100644 packages/server-nest/src/modules/Items/listeners/ItemCreated.listener.ts create mode 100644 packages/server-nest/src/modules/Items/models/Item.ts create mode 100644 packages/server-nest/src/modules/Search/SearchableMdel.ts create mode 100644 packages/server-nest/src/modules/Settings/ModelSettings.ts create mode 100644 packages/server-nest/src/modules/Subscription/SubscriptionPeriod.ts create mode 100644 packages/server-nest/src/modules/Subscription/interceptors/Subscription.guard.ts create mode 100644 packages/server-nest/src/modules/Subscription/models/PlanSubscription.ts create mode 100644 packages/server-nest/src/modules/System/SystemDB/SystemDB.constants.ts create mode 100644 packages/server-nest/src/modules/System/SystemDB/SystemDB.controller.ts create mode 100644 packages/server-nest/src/modules/System/SystemDB/SystemDB.module.ts create mode 100644 packages/server-nest/src/modules/System/SystemModels/SystemModels.constants.ts create mode 100644 packages/server-nest/src/modules/System/SystemModels/SystemModels.module.ts create mode 100644 packages/server-nest/src/modules/System/models/SystemModel.ts create mode 100644 packages/server-nest/src/modules/System/models/SystemUser.ts create mode 100644 packages/server-nest/src/modules/System/models/TenantModel.ts create mode 100644 packages/server-nest/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts create mode 100644 packages/server-nest/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyCache/TenancyCache.module.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyContext.service.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.constants.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.module.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyDB/TransactionsHooks.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyDB/UnitOfWork.service.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyGlobal.middleware.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyIdCls.interceptor.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.constants.ts create mode 100644 packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts create mode 100644 packages/server-nest/src/modules/Tenancy/Tenant.controller.ts create mode 100644 packages/server-nest/test/app.e2e-spec.ts create mode 100644 packages/server-nest/test/jest-e2e.json create mode 100644 packages/server-nest/tsconfig.build.json create mode 100644 packages/server-nest/tsconfig.json diff --git a/package.json b/package.json index fee5828a5..e9343addc 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dev:server": "lerna run dev --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"", "build:server": "lerna run build --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\" --scope \"@bigcapital/pdf-templates\" --scope \"@bigcapital/email-components\"", "serve:server": "lerna run serve --scope \"@bigcapital/server\" --scope \"@bigcapital/utils\"", + "server2:start": "lerna run start:dev --scope \"@bigcapital/server2\"", "test:e2e": "playwright test", "prepare": "husky install" }, diff --git a/packages/server-nest/.env.example b/packages/server-nest/.env.example new file mode 100644 index 000000000..55950fb05 --- /dev/null +++ b/packages/server-nest/.env.example @@ -0,0 +1,102 @@ +# Mail +MAIL_HOST= +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_PORT= +MAIL_SECURE= +MAIL_FROM_NAME= +MAIL_FROM_ADDRESS= + +# Database +DB_HOST=localhost +DB_USER=bigcapital +DB_PASSWORD=bigcapital +DB_ROOT_PASSWORD=root +DB_CHARSET=utf8 + +# System database +SYSTEM_DB_NAME=bigcapital_system +# SYSTEM_DB_USER= +# SYSTEM_DB_PASSWORD= +# SYSTEM_DB_NAME= +# SYSTEM_DB_CHARSET= + +# Tenant databases +TENANT_DB_NAME_PERFIX=bigcapital_tenant_ +# TENANT_DB_HOST= +# TENANT_DB_USER= +# TENANT_DB_PASSWORD= +# TENANT_DB_CHARSET= + +# Application +BASE_URL=http://example.com +JWT_SECRET=b0JDZW56RnV6aEthb0RGPXVEcUI + +# Jobs MongoDB +MONGODB_DATABASE_URL=mongodb://localhost/bigcapital + +# App proxy +PUBLIC_PROXY_PORT=80 +PUBLIC_PROXY_SSL_PORT=443 + +# Agendash +AGENDASH_AUTH_USER=agendash +AGENDASH_AUTH_PASSWORD=123123 + +# Sign-up restrictions +SIGNUP_DISABLED=false +SIGNUP_ALLOWED_DOMAINS= +SIGNUP_ALLOWED_EMAILS= + +# Sign-up Email Confirmation +SIGNUP_EMAIL_CONFIRMATION=false + +# API rate limit (points,duration,block duration). +API_RATE_LIMIT=120,60,600 + +# Gotenberg API for PDF printing - (production). +GOTENBERG_URL=http://gotenberg:3000 +GOTENBERG_DOCS_URL=http://server:3000/public/ + +# Gotenberg API - (development) +# GOTENBERG_URL=http://localhost:9000 +# GOTENBERG_DOCS_URL=http://host.docker.internal:3000/public/ + +# Exchange Rate Service +EXCHANGE_RATE_SERVICE=open-exchange-rate + +# Open Exchange Rate +OPEN_EXCHANGE_RATE_APP_ID= + +# The Plaid environment to use ('sandbox' or 'development'). +# https://plaid.com/docs/#api-host +PLAID_ENV=sandbox + +# Your Plaid keys, which can be found in the Plaid Dashboard. +# https://dashboard.plaid.com/account/keys +PLAID_CLIENT_ID= +PLAID_SECRET= +PLAID_LINK_WEBHOOK= + +# https://docs.lemonsqueezy.com/guides/developer-guide/getting-started#create-an-api-key +LEMONSQUEEZY_API_KEY= +LEMONSQUEEZY_STORE_ID= +LEMONSQUEEZY_WEBHOOK_SECRET= + +# S3 documents and attachments +S3_REGION=US +S3_ACCESS_KEY_ID= +S3_SECRET_ACCESS_KEY= +S3_ENDPOINT= +S3_BUCKET= + +# PostHog +POSTHOG_API_KEY= +POSTHOG_HOST= + +# Stripe Payment +STRIPE_PAYMENT_SECRET_KEY= +STRIPE_PAYMENT_PUBLISHABLE_KEY= +STRIPE_PAYMENT_CLIENT_ID= +STRIPE_PAYMENT_WEBHOOKS_SECRET= +STRIPE_PAYMENT_REDIRECT_URL= \ No newline at end of file diff --git a/packages/server-nest/.eslintrc.js b/packages/server-nest/.eslintrc.js new file mode 100644 index 000000000..259de13c7 --- /dev/null +++ b/packages/server-nest/.eslintrc.js @@ -0,0 +1,25 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/packages/server-nest/.gitignore b/packages/server-nest/.gitignore new file mode 100644 index 000000000..4b56acfbe --- /dev/null +++ b/packages/server-nest/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/packages/server-nest/.prettierrc b/packages/server-nest/.prettierrc new file mode 100644 index 000000000..dcb72794f --- /dev/null +++ b/packages/server-nest/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/packages/server-nest/.todo b/packages/server-nest/.todo new file mode 100644 index 000000000..58bdf18b5 --- /dev/null +++ b/packages/server-nest/.todo @@ -0,0 +1 @@ +- Build authentication services. diff --git a/packages/server-nest/README.md b/packages/server-nest/README.md new file mode 100644 index 000000000..f149ff9af --- /dev/null +++ b/packages/server-nest/README.md @@ -0,0 +1 @@ +## @bigcapitalhq/server \ No newline at end of file diff --git a/packages/server-nest/nest-cli.json b/packages/server-nest/nest-cli.json new file mode 100644 index 000000000..4767fad13 --- /dev/null +++ b/packages/server-nest/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "assets": [ + { "include": "i18n/**/*", "watchAssets": true } + ] + } +} diff --git a/packages/server-nest/package.json b/packages/server-nest/package.json new file mode 100644 index 000000000..d364dd61c --- /dev/null +++ b/packages/server-nest/package.json @@ -0,0 +1,103 @@ +{ + "name": "@bigcapital/server2", + "version": "0.0.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/bull": "^10.2.1", + "@nestjs/bullmq": "^10.2.1", + "@nestjs/cache-manager": "^2.2.2", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.2.3", + "@nestjs/core": "^10.0.0", + "@nestjs/event-emitter": "^2.0.4", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^7.4.2", + "@nestjs/throttler": "^6.2.1", + "@types/passport-local": "^1.0.38", + "@types/ramda": "^0.30.2", + "bull": "^4.16.3", + "bullmq": "^5.21.1", + "cache-manager": "^6.1.1", + "cache-manager-redis-store": "^3.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "express-validator": "^7.2.0", + "fp-ts": "^2.16.9", + "knex": "^3.1.0", + "lamda": "^0.4.1", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "mysql": "^2.18.1", + "mysql2": "^3.11.3", + "nestjs-cls": "^4.4.1", + "nestjs-i18n": "^10.4.9", + "objection": "^3.1.5", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", + "ramda": "^0.30.1", + "redis": "^4.7.0", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^9.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/packages/server-nest/src/common/config/gotenberg.ts b/packages/server-nest/src/common/config/gotenberg.ts new file mode 100644 index 000000000..4f17abe56 --- /dev/null +++ b/packages/server-nest/src/common/config/gotenberg.ts @@ -0,0 +1,6 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('gotenberg', () => ({ + url: process.env.GOTENBERG_URL, + docsUrl: process.env.GOTENBERG_DOCS_URL, +})); diff --git a/packages/server-nest/src/common/config/index.ts b/packages/server-nest/src/common/config/index.ts new file mode 100644 index 000000000..464a4578b --- /dev/null +++ b/packages/server-nest/src/common/config/index.ts @@ -0,0 +1,21 @@ +import systemDatabase from './system-database'; +import tenantDatabase from './tenant-database'; +import signup from './signup'; +import gotenberg from './gotenberg'; +import plaid from './plaid'; +import lemonsqueezy from './lemonsqueezy'; +import s3 from './s3'; +import openExchange from './open-exchange'; +import posthog from './posthog'; + +export const config = [ + systemDatabase, + tenantDatabase, + signup, + gotenberg, + plaid, + lemonsqueezy, + s3, + openExchange, + posthog, +]; diff --git a/packages/server-nest/src/common/config/lemonsqueezy.ts b/packages/server-nest/src/common/config/lemonsqueezy.ts new file mode 100644 index 000000000..cd536ead7 --- /dev/null +++ b/packages/server-nest/src/common/config/lemonsqueezy.ts @@ -0,0 +1,7 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('lemonsqueezy', () => ({ + apiKey: process.env.LEMONSQUEEZY_API_KEY, + storeId: process.env.LEMONSQUEEZY_STORE_ID, + webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET, +})); diff --git a/packages/server-nest/src/common/config/open-exchange.ts b/packages/server-nest/src/common/config/open-exchange.ts new file mode 100644 index 000000000..75ff14077 --- /dev/null +++ b/packages/server-nest/src/common/config/open-exchange.ts @@ -0,0 +1,5 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('openExchange', () => ({ + appId: process.env.OPEN_EXCHANGE_RATE_APP_ID, +})); diff --git a/packages/server-nest/src/common/config/plaid.ts b/packages/server-nest/src/common/config/plaid.ts new file mode 100644 index 000000000..1d6ad9a5b --- /dev/null +++ b/packages/server-nest/src/common/config/plaid.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('plaid', () => ({ + env: process.env.PLAID_ENV || 'sandbox', + clientId: process.env.PLAID_CLIENT_ID, + secret: process.env.PLAID_SECRET, + linkWebhook: process.env.PLAID_LINK_WEBHOOK, +})); diff --git a/packages/server-nest/src/common/config/posthog.ts b/packages/server-nest/src/common/config/posthog.ts new file mode 100644 index 000000000..cccc76b4c --- /dev/null +++ b/packages/server-nest/src/common/config/posthog.ts @@ -0,0 +1,6 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('posthog', () => ({ + apiKey: process.env.POSTHOG_API_KEY, + host: process.env.POSTHOG_HOST || 'https://us.i.posthog.com', +})); diff --git a/packages/server-nest/src/common/config/s3.ts b/packages/server-nest/src/common/config/s3.ts new file mode 100644 index 000000000..dd6ac7ed6 --- /dev/null +++ b/packages/server-nest/src/common/config/s3.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('s3', () => ({ + region: process.env.S3_REGION || 'US', + accessKeyId: process.env.S3_ACCESS_KEY_ID, + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, + endpoint: process.env.S3_ENDPOINT, + bucket: process.env.S3_BUCKET, +})); diff --git a/packages/server-nest/src/common/config/signup.ts b/packages/server-nest/src/common/config/signup.ts new file mode 100644 index 000000000..708f9136a --- /dev/null +++ b/packages/server-nest/src/common/config/signup.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('signup', () => ({ + disabled: process.env.SIGNUP_DISABLED === 'true', + allowedDomains: process.env.SIGNUP_ALLOWED_DOMAINS + ? process.env.SIGNUP_ALLOWED_DOMAINS.split(',') + : [], + allowedEmails: process.env.SIGNUP_ALLOWED_EMAILS + ? process.env.SIGNUP_ALLOWED_EMAILS.split(',') + : [], + emailConfirmation: process.env.SIGNUP_EMAIL_CONFIRMATION === 'true', +})); diff --git a/packages/server-nest/src/common/config/stripe-payment.ts b/packages/server-nest/src/common/config/stripe-payment.ts new file mode 100644 index 000000000..49fd99aee --- /dev/null +++ b/packages/server-nest/src/common/config/stripe-payment.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('stripePayment', () => ({ + secretKey: process.env.STRIPE_PAYMENT_SECRET_KEY, + publishableKey: process.env.STRIPE_PAYMENT_PUBLISHABLE_KEY, + clientId: process.env.STRIPE_PAYMENT_CLIENT_ID, + webhooksSecret: process.env.STRIPE_PAYMENT_WEBHOOKS_SECRET, + redirectUrl: process.env.STRIPE_PAYMENT_REDIRECT_URL, +})); diff --git a/packages/server-nest/src/common/config/system-database.ts b/packages/server-nest/src/common/config/system-database.ts new file mode 100644 index 000000000..0ada1b4b3 --- /dev/null +++ b/packages/server-nest/src/common/config/system-database.ts @@ -0,0 +1,10 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('systemDatabase', () => ({ + client: 'mysql', + host: process.env.SYSTEM_DB_HOST || process.env.DB_HOST, + port: process.env.SYSTEM_DB_PORT || process.env.DB_PORT || 5432, + user: process.env.SYSTEM_DB_USER || process.env.DB_USER, + password: process.env.SYSTEM_DB_PASSWORD || process.env.DB_PASSWORD, + databaseName: process.env.SYSTEM_DB_NAME || process.env.DB_NAME, +})); diff --git a/packages/server-nest/src/common/config/tenant-database.ts b/packages/server-nest/src/common/config/tenant-database.ts new file mode 100644 index 000000000..0885f2383 --- /dev/null +++ b/packages/server-nest/src/common/config/tenant-database.ts @@ -0,0 +1,9 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('tenantDatabase', () => ({ + client: 'mysql', + host: process.env.TENANT_DB_HOST || process.env.DB_HOST, + port: process.env.TENANT_DB_PORT || process.env.DB_PORT || 5432, + user: process.env.TENANT_DB_USER || process.env.DB_USER, + password: process.env.TENANT_DB_PASSWORD || process.env.DB_PASSWORD, +})); diff --git a/packages/server-nest/src/common/events/events.ts b/packages/server-nest/src/common/events/events.ts new file mode 100644 index 000000000..60fda4ca7 --- /dev/null +++ b/packages/server-nest/src/common/events/events.ts @@ -0,0 +1,754 @@ +export const events = { + /** + * Authentication service. + */ + auth: { + signIn: 'onSignIn', + signingIn: 'onSigningIn', + + signUp: 'onSignUp', + signingUp: 'onSigningUp', + + signUpConfirming: 'signUpConfirming', + signUpConfirmed: 'signUpConfirmed', + + sendingResetPassword: 'onSendingResetPassword', + sendResetPassword: 'onSendResetPassword', + + resetPassword: 'onResetPassword', + resetingPassword: 'onResetingPassword', + }, + + /** + * Invite users service. + */ + inviteUser: { + acceptInvite: 'onUserAcceptInvite', + sendInvite: 'onUserSendInvite', + resendInvite: 'onUserInviteResend', + checkInvite: 'onUserCheckInvite', + sendInviteTenantSynced: 'onUserSendInviteTenantSynced', + }, + + /** + * Organization managment service. + */ + organization: { + build: 'onOrganizationBuild', + built: 'onOrganizationBuilt', + + seeded: 'onOrganizationSeeded', + + baseCurrencyUpdated: 'onOrganizationBaseCurrencyUpdated', + }, + + /** + * Organization subscription. + */ + subscription: { + onSubscriptionCancel: 'onSubscriptionCancel', + onSubscriptionCancelled: 'onSubscriptionCancelled', + + onSubscriptionResume: 'onSubscriptionResume', + onSubscriptionResumed: 'onSubscriptionResumed', + + onSubscriptionPlanChange: 'onSubscriptionPlanChange', + onSubscriptionPlanChanged: 'onSubscriptionPlanChanged', + + onSubscriptionSubscribed: 'onSubscriptionSubscribed', + + onSubscriptionPaymentSucceed: 'onSubscriptionPaymentSucceed', + onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed', + }, + + /** + * Tenants managment service. + */ + tenantManager: { + databaseCreated: 'onDatabaseCreated', + tenantMigrated: 'onTenantMigrated', + tenantSeeded: 'onTenantSeeded', + }, + + /** + * Accounts service. + */ + accounts: { + onViewed: 'onAccountViewed', + onListViewed: 'onAccountsListViewed', + + onCreating: 'onAccountCreating', + onCreated: 'onAccountCreated', + + onEditing: 'onAccountEditing', + onEdited: 'onAccountEdited', + + onDelete: 'onAccountDelete', + onDeleted: 'onAccountDeleted', + + onBulkDeleted: 'onBulkDeleted', + onBulkActivated: 'onAccountBulkActivated', + + onActivated: 'onAccountActivated', + }, + + /** + * Manual journals service. + */ + manualJournals: { + onCreating: 'onManualJournalCreating', + onCreated: 'onManualJournalCreated', + + onEditing: 'onManualJournalEditing', + onEdited: 'onManualJournalEdited', + + onDeleting: 'onManualJournalDeleting', + onDeleted: 'onManualJournalDeleted', + + onPublished: 'onManualJournalPublished', + onPublishing: 'onManualJournalPublishing', + }, + + /** + * Expenses service. + */ + expenses: { + onCreating: 'onExpenseCreating', + onCreated: 'onExpenseCreated', + + onEditing: 'onExpenseEditing', + onEdited: 'onExpenseEdited', + + onDeleting: 'onExpenseDeleting', + onDeleted: 'onExpenseDeleted', + + onPublishing: 'onExpensePublishing', + onPublished: 'onExpensePublished', + }, + + /** + * Sales invoices service. + */ + saleInvoice: { + onViewed: 'onSaleInvoiceItemViewed', + onListViewed: 'onSaleInvoiceListViewed', + + onPdfViewed: 'onSaleInvoicePdfViewed', + + onCreate: 'onSaleInvoiceCreate', + onCreating: 'onSaleInvoiceCreating', + onCreated: 'onSaleInvoiceCreated', + + onEdit: 'onSaleInvoiceEdit', + onEditing: 'onSaleInvoiceEditing', + onEdited: 'onSaleInvoiceEdited', + + onDelete: 'onSaleInvoiceDelete', + onDeleting: 'onSaleInvoiceDeleting', + onDeleted: 'onSaleInvoiceDeleted', + + onDelivering: 'onSaleInvoiceDelivering', + onDeliver: 'onSaleInvoiceDeliver', + onDelivered: 'onSaleInvoiceDelivered', + + onPublish: 'onSaleInvoicePublish', + onPublished: 'onSaleInvoicePublished', + + onWriteoff: 'onSaleInvoiceWriteoff', + onWrittenoff: 'onSaleInvoiceWrittenoff', + onWrittenoffCancel: 'onSaleInvoiceWrittenoffCancel', + onWrittenoffCanceled: 'onSaleInvoiceWrittenoffCanceled', + + onNotifySms: 'onSaleInvoiceNotifySms', + onNotifiedSms: 'onSaleInvoiceNotifiedSms', + + onNotifyMail: 'onSaleInvoiceNotifyMail', + onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail', + + onPreMailSend: 'onSaleInvoicePreMailSend', + onMailSend: 'onSaleInvoiceMailSend', + onMailSent: 'onSaleInvoiceMailSent', + + onMailReminderSend: 'onSaleInvoiceMailReminderSend', + onMailReminderSent: 'onSaleInvoiceMailReminderSent', + + onPublicLinkGenerating: 'onPublicSharableLinkGenerating', + onPublicLinkGenerated: 'onPublicSharableLinkGenerated', + }, + + /** + * Sales estimates service. + */ + saleEstimate: { + onPdfViewed: 'onSaleEstimatePdfViewed', + + onCreating: 'onSaleEstimateCreating', + onCreated: 'onSaleEstimateCreated', + + onEditing: 'onSaleEstimateEditing', + onEdited: 'onSaleEstimateEdited', + + onDeleting: 'onSaleEstimatedDeleting', + onDeleted: 'onSaleEstimatedDeleted', + + onPublishing: 'onSaleEstimatedPublishing', + onPublished: 'onSaleEstimatedPublished', + + onNotifySms: 'onSaleEstimateNotifySms', + onNotifiedSms: 'onSaleEstimateNotifiedSms', + + onDelivering: 'onSaleEstimateDelivering', + onDelivered: 'onSaleEstimateDelivered', + + onConvertedToInvoice: 'onSaleEstimateConvertedToInvoice', + + onApproving: 'onSaleEstimateApproving', + onApproved: 'onSaleEstimateApproved', + + onRejecting: 'onSaleEstimateRejecting', + onRejected: 'onSaleEstimateRejected', + + onNotifyMail: 'onSaleEstimateNotifyMail', + + onPreMailSend: 'onSaleEstimatePreMailSend', + onMailSend: 'onSaleEstimateMailSend', + onMailSent: 'onSaleEstimateMailSend', + }, + + /** + * Sales receipts service. + */ + saleReceipt: { + onPdfViewed: 'onSaleReceiptPdfViewed', + + onCreating: 'onSaleReceiptsCreating', + onCreated: 'onSaleReceiptsCreated', + + onEditing: 'onSaleReceiptsEditing', + onEdited: 'onSaleReceiptsEdited', + + onDeleting: 'onSaleReceiptsDeleting', + onDeleted: 'onSaleReceiptsDeleted', + + onPublishing: 'onSaleReceiptPublishing', + onPublished: 'onSaleReceiptPublished', + + onClosed: 'onSaleReceiptClosed', + onClosing: 'onSaleReceiptClosing', + + onNotifySms: 'onSaleReceiptNotifySms', + onNotifiedSms: 'onSaleReceiptNotifiedSms', + + onPreMailSend: 'onSaleReceiptPreMailSend', + onMailSend: 'onSaleReceiptMailSend', + onMailSent: 'onSaleReceiptMailSent', + }, + + /** + * Payment receipts service. + */ + paymentReceive: { + onPdfViewed: 'onPaymentReceivedPdfViewed', + + onCreated: 'onPaymentReceiveCreated', + onCreating: 'onPaymentReceiveCreating', + + onEditing: 'onPaymentReceiveEditing', + onEdited: 'onPaymentReceiveEdited', + + onDeleting: 'onPaymentReceiveDeleting', + onDeleted: 'onPaymentReceiveDeleted', + + onPublishing: 'onPaymentReceivePublishing', + onPublished: 'onPaymentReceivePublished', + + onNotifySms: 'onPaymentReceiveNotifySms', + onNotifiedSms: 'onPaymentReceiveNotifiedSms', + + onPreMailSend: 'onPaymentReceivePreMailSend', + onMailSend: 'onPaymentReceiveMailSend', + onMailSent: 'onPaymentReceiveMailSent', + }, + + /** + * Bills service. + */ + bill: { + onCreating: 'onBillCreating', + onCreated: 'onBillCreated', + + onEditing: 'onBillEditing', + onEdited: 'onBillEdited', + + onDeleting: 'onBillDeleting', + onDeleted: 'onBillDeleted', + + onPublishing: 'onBillPublishing', + onPublished: 'onBillPublished', + + onOpening: 'onBillOpening', + onOpened: 'onBillOpened', + }, + + /** + * Bill payments service. + */ + billPayment: { + onCreating: 'onBillPaymentCreating', + onCreated: 'onBillPaymentCreated', + + onEditing: 'onBillPaymentEditing', + onEdited: 'onBillPaymentEdited', + + onDeleted: 'onBillPaymentDeleted', + onDeleting: 'onBillPaymentDeleting', + + onPublishing: 'onBillPaymentPublishing', + onPublished: 'onBillPaymentPublished', + }, + + /** + * Customers services. + */ + customers: { + onCreating: 'onCustomerCreating', + onCreated: 'onCustomerCreated', + + onEdited: 'onCustomerEdited', + onEditing: 'onCustomerEditing', + + onDeleted: 'onCustomerDeleted', + onDeleting: 'onCustomerDeleting', + onBulkDeleted: 'onBulkDeleted', + + onOpeningBalanceChanging: 'onCustomerOpeningBalanceChanging', + onOpeningBalanceChanged: 'onCustomerOpeingBalanceChanged', + + onActivating: 'onCustomerActivating', + onActivated: 'onCustomerActivated', + }, + + /** + * Vendors services. + */ + vendors: { + onCreated: 'onVendorCreated', + onCreating: 'onVendorCreating', + + onEdited: 'onVendorEdited', + onEditing: 'onVendorEditing', + + onDeleted: 'onVendorDeleted', + onDeleting: 'onVendorDeleting', + + onOpeningBalanceChanging: 'onVendorOpeingBalanceChanging', + onOpeningBalanceChanged: 'onVendorOpeingBalanceChanged', + + onActivating: 'onVendorActivating', + onActivated: 'onVendorActivated', + }, + + /** + * Items service. + */ + item: { + onViewed: 'onItemViewed', + + onCreated: 'onItemCreated', + onCreating: 'onItemCreating', + + onEditing: 'onItemEditing', + onEdited: 'onItemEdited', + + onDeleted: 'onItemDeleted', + onDeleting: 'onItemDeleting', + + onActivating: 'onItemActivating', + onActivated: 'onItemActivated', + + onInactivating: 'onInactivating', + onInactivated: 'onItemInactivated', + }, + + /** + * Item category service. + */ + itemCategory: { + onCreated: 'onItemCategoryCreated', + onEdited: 'onItemCategoryEdited', + onDeleted: 'onItemCategoryDeleted', + onBulkDeleted: 'onItemCategoryBulkDeleted', + }, + + /** + * Inventory service. + */ + inventory: { + onInventoryTransactionsCreated: 'onInventoryTransactionsCreated', + onInventoryTransactionsDeleted: 'onInventoryTransactionsDeleted', + + onComputeItemCostJobScheduled: 'onComputeItemCostJobScheduled', + onComputeItemCostJobStarted: 'onComputeItemCostJobStarted', + onComputeItemCostJobCompleted: 'onComputeItemCostJobCompleted', + + onInventoryCostEntriesWritten: 'onInventoryCostEntriesWritten', + + onCostLotsGLEntriesBeforeWrite: 'onInventoryCostLotsGLEntriesBeforeWrite', + onCostLotsGLEntriesWrite: 'onInventoryCostLotsGLEntriesWrite', + }, + + /** + * Inventory adjustment service. + */ + inventoryAdjustment: { + onQuickCreating: 'onInventoryAdjustmentCreating', + onQuickCreated: 'onInventoryAdjustmentQuickCreated', + + onCreated: 'onInventoryAdjustmentCreated', + + onDeleting: 'onInventoryAdjustmentDeleting', + onDeleted: 'onInventoryAdjustmentDeleted', + + onPublishing: 'onInventoryAdjustmentPublishing', + onPublished: 'onInventoryAdjustmentPublished', + }, + + /** + * Bill landed cost. + */ + billLandedCost: { + onCreate: 'onBillLandedCostCreate', + onCreated: 'onBillLandedCostCreated', + onDelete: 'onBillLandedCostDelete', + onDeleted: 'onBillLandedCostDeleted', + }, + + cashflow: { + onOwnerContributionCreate: 'onCashflowOwnerContributionCreate', + onOwnerContributionCreated: 'onCashflowOwnerContributionCreated', + + onOtherIncomeCreate: 'onCashflowOtherIncomeCreate', + onOtherIncomeCreated: 'onCashflowOtherIncomeCreated', + + onTransactionCreating: 'onCashflowTransactionCreating', + onTransactionCreated: 'onCashflowTransactionCreated', + + onTransactionDeleting: 'onCashflowTransactionDeleting', + onTransactionDeleted: 'onCashflowTransactionDeleted', + + onTransactionCategorizing: 'onTransactionCategorizing', + onTransactionCategorized: 'onCashflowTransactionCategorized', + + onTransactionUncategorizedCreating: 'onTransactionUncategorizedCreating', + onTransactionUncategorizedCreated: 'onTransactionUncategorizedCreated', + + onTransactionUncategorizing: 'onTransactionUncategorizing', + onTransactionUncategorized: 'onTransactionUncategorized', + + onTransactionCategorizingAsExpense: 'onTransactionCategorizingAsExpense', + onTransactionCategorizedAsExpense: 'onTransactionCategorizedAsExpense', + }, + + /** + * Roles service events. + */ + roles: { + onCreate: 'onRoleCreate', + onCreated: 'onRoleCreated', + onEdit: 'onRoleEdit', + onEdited: 'onRoleEdited', + onDelete: 'onRoleDelete', + onDeleted: 'onRoleDeleted', + }, + + tenantUser: { + onEdited: 'onTenantUserEdited', + onDeleted: 'onTenantUserDeleted', + onActivated: 'onTenantUserActivated', + onInactivated: 'onTenantUserInactivated', + }, + + /** + * Credit note service. + */ + creditNote: { + onPdfViewed: 'onCreditNotePdfViewed', + + onCreate: 'onCreditNoteCreate', + onCreating: 'onCreditNoteCreating', + onCreated: 'onCreditNoteCreated', + + onEditing: 'onCreditNoteEditing', + onEdit: 'onCreditNoteEdit', + onEdited: 'onCreditNoteEdited', + + onDelete: 'onCreditNoteDelete', + onDeleting: 'onCreditNoteDeleting', + onDeleted: 'onCreditNoteDeleted', + + onOpen: 'onCreditNoteOpen', + onOpening: 'onCreditNoteOpening', + onOpened: 'onCreditNoteOpened', + + onRefundCreate: 'onCreditNoteRefundCreate', + onRefundCreating: 'onCreditNoteRefundCreating', + onRefundCreated: 'onCreditNoteRefundCreated', + + onRefundDelete: 'onCreditNoteRefundDelete', + onRefundDeleting: 'onCreditNoteRefundDeleting', + onRefundDeleted: 'onCreditNoteRefundDeleted', + + onApplyToInvoicesCreated: 'onCreditNoteApplyToInvoiceCreated', + onApplyToInvoicesCreate: 'onCreditNoteApplyToInvoiceCreate', + onApplyToInvoicesDeleted: 'onCreditNoteApplyToInvoiceDeleted', + }, + + /** + * Vendor credit service. + */ + vendorCredit: { + onCreate: 'onVendorCreditCreate', + onCreating: 'onVendorCreditCreating', + onCreated: 'onVendorCreditCreated', + + onEdit: 'onVendorCreditEdit', + onEditing: 'onVendorCreditEditing', + onEdited: 'onVendorCreditEdited', + + onDelete: 'onVendorCreditDelete', + onDeleting: 'onVendorCreditDeleting', + onDeleted: 'onVendorCreditDeleted', + + onOpen: 'onVendorCreditOpen', + onOpened: 'onVendorCreditOpened', + + onRefundCreating: 'onVendorCreditRefundCreating', + onRefundCreate: 'onVendorCreditRefundCreate', + onRefundCreated: 'onVendorCreditRefundCreated', + + onRefundDelete: 'onVendorCreditRefundDelete', + onRefundDeleting: 'onVendorCreditRefundDeleting', + onRefundDeleted: 'onVendorCreditRefundDeleted', + + onApplyToInvoicesCreated: 'onVendorCreditApplyToInvoiceCreated', + onApplyToInvoicesCreate: 'onVendorCreditApplyToInvoiceCreate', + onApplyToInvoicesDeleted: 'onVendorCreditApplyToInvoiceDeleted', + }, + + transactionsLocking: { + locked: 'onTransactionLockingLocked', + lockCanceled: 'onTransactionLockingLockCanceled', + partialUnlocked: 'onTransactionLockingPartialUnlocked', + partialUnlockCanceled: 'onTransactionLockingPartialUnlockCanceled', + }, + + warehouse: { + onCreate: 'onWarehouseCreate', + onCreated: 'onWarehouseCreated', + + onEdit: 'onWarehouseEdit', + onEdited: 'onWarehouseEdited', + + onDelete: 'onWarehouseDelete', + onDeleted: 'onWarehouseDeleted', + + onActivate: 'onWarehouseActivate', + onActivated: 'onWarehouseActivated', + + onMarkPrimary: 'onWarehouseMarkPrimary', + onMarkedPrimary: 'onWarehouseMarkedPrimary', + }, + + warehouseTransfer: { + onCreate: 'onWarehouseTransferCreate', + onCreated: 'onWarehouseTransferCreated', + + onEdit: 'onWarehouseTransferEdit', + onEdited: 'onWarehouseTransferEdited', + + onDelete: 'onWarehouseTransferDelete', + onDeleted: 'onWarehouseTransferDeleted', + + onInitiate: 'onWarehouseTransferInitiate', + onInitiated: 'onWarehouseTransferInitated', + + onTransfer: 'onWarehouseTransferInitiate', + onTransferred: 'onWarehouseTransferTransferred', + }, + + /** + * Branches. + */ + branch: { + onActivate: 'onBranchActivate', + onActivated: 'onBranchActivated', + + onMarkPrimary: 'onBranchMarkPrimary', + onMarkedPrimary: 'onBranchMarkedPrimary', + }, + + /** + * Projects. + */ + project: { + onCreate: 'onProjectCreate', + onCreating: 'onProjectCreating', + onCreated: 'onProjectCreated', + + onEdit: 'onEditProject', + onEditing: 'onEditingProject', + onEdited: 'onEditedProject', + + onEditStatus: 'onEditStatusProject', + onEditingStatus: 'onEditingStatusProject', + onEditedStatus: 'onEditedStatusProject', + + onDelete: 'onDeleteProject', + onDeleting: 'onDeletingProject', + onDeleted: 'onDeletedProject', + }, + + /** + * Project Tasks. + */ + projectTask: { + onCreate: 'onProjectTaskCreate', + onCreating: 'onProjectTaskCreating', + onCreated: 'onProjectTaskCreated', + + onEdit: 'onProjectTaskEdit', + onEditing: 'onProjectTaskEditing', + onEdited: 'onProjectTaskEdited', + + onDelete: 'onProjectTaskDelete', + onDeleting: 'onProjectTaskDeleting', + onDeleted: 'onProjectTaskDeleted', + }, + + /** + * Project Times. + */ + projectTime: { + onCreate: 'onProjectTimeCreate', + onCreating: 'onProjectTimeCreating', + onCreated: 'onProjectTimeCreated', + + onEdit: 'onProjectTimeEdit', + onEditing: 'onProjectTimeEditing', + onEdited: 'onProjectTimeEdited', + + onDelete: 'onProjectTimeDelete', + onDeleting: 'onProjectTimeDeleting', + onDeleted: 'onProjectTimeDeleted', + }, + + taxRates: { + onCreating: 'onTaxRateCreating', + onCreated: 'onTaxRateCreated', + + onEditing: 'onTaxRateEditing', + onEdited: 'onTaxRateEdited', + + onDeleting: 'onTaxRateDeleting', + onDeleted: 'onTaxRateDeleted', + + onActivating: 'onTaxRateActivating', + onActivated: 'onTaxRateActivated', + + onInactivating: 'onTaxRateInactivating', + onInactivated: 'onTaxRateInactivated', + }, + + plaid: { + onItemCreated: 'onPlaidItemCreated', + onTransactionsSynced: 'onPlaidTransactionsSynced', + }, + + // Bank rules. + bankRules: { + onCreating: 'onBankRuleCreating', + onCreated: 'onBankRuleCreated', + + onEditing: 'onBankRuleEditing', + onEdited: 'onBankRuleEdited', + + onDeleting: 'onBankRuleDeleting', + onDeleted: 'onBankRuleDeleted', + }, + + // Bank matching. + bankMatch: { + onMatching: 'onBankTransactionMatching', + onMatched: 'onBankTransactionMatched', + + onUnmatching: 'onBankTransactionUnmathcing', + onUnmatched: 'onBankTransactionUnmathced', + }, + + bankTransactions: { + onExcluding: 'onBankTransactionExclude', + onExcluded: 'onBankTransactionExcluded', + + onUnexcluding: 'onBankTransactionUnexcluding', + onUnexcluded: 'onBankTransactionUnexcluded', + + onPendingRemoving: 'onBankTransactionPendingRemoving', + onPendingRemoved: 'onBankTransactionPendingRemoved', + }, + + bankAccount: { + onDisconnecting: 'onBankAccountDisconnecting', + onDisconnected: 'onBankAccountDisconnected', + }, + + // Import files. + import: { + onImportCommitted: 'onImportFileCommitted', + }, + + // Branding templates + pdfTemplate: { + onCreating: 'onPdfTemplateCreating', + onCreated: 'onPdfTemplateCreated', + + onEditing: 'onPdfTemplateEditing', + onEdited: 'onPdfTemplatedEdited', + + onDeleting: 'onPdfTemplateDeleting', + onDeleted: 'onPdfTemplateDeleted', + + onAssignedDefault: 'onPdfTemplateAssignedDefault', + onAssigningDefault: 'onPdfTemplateAssigningDefault', + }, + + // Payment method. + paymentMethod: { + onEditing: 'onPaymentMethodEditing', + onEdited: 'onPaymentMethodEdited', + + onDeleted: 'onPaymentMethodDeleted', + }, + + // Payment methods integrations + paymentIntegrationLink: { + onPaymentIntegrationLink: 'onPaymentIntegrationLink', + onPaymentIntegrationDeleteLink: 'onPaymentIntegrationDeleteLink', + }, + + // Stripe Payment Integration + stripeIntegration: { + onAccountCreated: 'onStripeIntegrationAccountCreated', + onAccountDeleted: 'onStripeIntegrationAccountDeleted', + + onPaymentLinkCreated: 'onStripePaymentLinkCreated', + onPaymentLinkInactivated: 'onStripePaymentLinkInactivated', + + onOAuthCodeGranted: 'onStripeOAuthCodeGranted', + }, + + // Stripe Payment Webhooks + stripeWebhooks: { + onCheckoutSessionCompleted: 'onStripeCheckoutSessionCompleted', + onAccountUpdated: 'onStripeAccountUpdated', + }, +}; diff --git a/packages/server-nest/src/common/pipes/ZodValidation.pipe.ts b/packages/server-nest/src/common/pipes/ZodValidation.pipe.ts new file mode 100644 index 000000000..84e0d2302 --- /dev/null +++ b/packages/server-nest/src/common/pipes/ZodValidation.pipe.ts @@ -0,0 +1,20 @@ +import { + PipeTransform, + ArgumentMetadata, + BadRequestException, +} from '@nestjs/common'; +import { ZodSchema } from 'zod'; + +export class ZodValidationPipe implements PipeTransform { + constructor(private schema: ZodSchema) {} + + transform(value: unknown, metadata: ArgumentMetadata) { + try { + const parsedValue = this.schema.parse(value); + return parsedValue; + } catch (error) { + console.log(error); + throw new BadRequestException(error.errors); + } + } +} diff --git a/packages/server-nest/src/constants/accounts.ts b/packages/server-nest/src/constants/accounts.ts new file mode 100644 index 000000000..b6055e852 --- /dev/null +++ b/packages/server-nest/src/constants/accounts.ts @@ -0,0 +1,230 @@ +export const ACCOUNT_TYPE = { + CASH: 'cash', + BANK: 'bank', + ACCOUNTS_RECEIVABLE: 'accounts-receivable', + INVENTORY: 'inventory', + OTHER_CURRENT_ASSET: 'other-current-asset', + FIXED_ASSET: 'fixed-asset', + NON_CURRENT_ASSET: 'none-current-asset', + + ACCOUNTS_PAYABLE: 'accounts-payable', + CREDIT_CARD: 'credit-card', + TAX_PAYABLE: 'tax-payable', + OTHER_CURRENT_LIABILITY: 'other-current-liability', + LOGN_TERM_LIABILITY: 'long-term-liability', + NON_CURRENT_LIABILITY: 'non-current-liability', + + EQUITY: 'equity', + INCOME: 'income', + OTHER_INCOME: 'other-income', + COST_OF_GOODS_SOLD: 'cost-of-goods-sold', + EXPENSE: 'expense', + OTHER_EXPENSE: 'other-expense', +}; + +export const ACCOUNT_PARENT_TYPE = { + CURRENT_ASSET: 'current-asset', + FIXED_ASSET: 'fixed-asset', + NON_CURRENT_ASSET: 'non-current-asset', + + CURRENT_LIABILITY: 'current-liability', + LOGN_TERM_LIABILITY: 'long-term-liability', + NON_CURRENT_LIABILITY: 'non-current-liability', + + EQUITY: 'equity', + EXPENSE: 'expense', + INCOME: 'income', +}; + +export const ACCOUNT_ROOT_TYPE = { + ASSET: 'asset', + LIABILITY: 'liability', + EQUITY: 'equity', + EXPENSE: 'expense', + INCOME: 'income', +}; + +export const ACCOUNT_NORMAL = { + CREDIT: 'credit', + DEBIT: 'debit', +}; +export const ACCOUNT_TYPES = [ + { + label: 'Cash', + key: ACCOUNT_TYPE.CASH, + normal: ACCOUNT_NORMAL.DEBIT, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + multiCurrency: true, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Bank', + key: ACCOUNT_TYPE.BANK, + normal: ACCOUNT_NORMAL.DEBIT, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + multiCurrency: true, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Accounts Receivable', + key: ACCOUNT_TYPE.ACCOUNTS_RECEIVABLE, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Inventory', + key: ACCOUNT_TYPE.INVENTORY, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Other Current Asset', + key: ACCOUNT_TYPE.OTHER_CURRENT_ASSET, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_ASSET, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Fixed Asset', + key: ACCOUNT_TYPE.FIXED_ASSET, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Non-Current Asset', + key: ACCOUNT_TYPE.NON_CURRENT_ASSET, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.ASSET, + parentType: ACCOUNT_PARENT_TYPE.FIXED_ASSET, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Accounts Payable', + key: ACCOUNT_TYPE.ACCOUNTS_PAYABLE, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.LIABILITY, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Credit Card', + key: ACCOUNT_TYPE.CREDIT_CARD, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.LIABILITY, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY, + multiCurrency: true, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Tax Payable', + key: ACCOUNT_TYPE.TAX_PAYABLE, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.LIABILITY, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Other Current Liability', + key: ACCOUNT_TYPE.OTHER_CURRENT_LIABILITY, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.LIABILITY, + parentType: ACCOUNT_PARENT_TYPE.CURRENT_LIABILITY, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Long Term Liability', + key: ACCOUNT_TYPE.LOGN_TERM_LIABILITY, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.LIABILITY, + parentType: ACCOUNT_PARENT_TYPE.LOGN_TERM_LIABILITY, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Non-Current Liability', + key: ACCOUNT_TYPE.NON_CURRENT_LIABILITY, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.LIABILITY, + parentType: ACCOUNT_PARENT_TYPE.NON_CURRENT_LIABILITY, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Equity', + key: ACCOUNT_TYPE.EQUITY, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.EQUITY, + parentType: ACCOUNT_PARENT_TYPE.EQUITY, + balanceSheet: true, + incomeSheet: false, + }, + { + label: 'Income', + key: ACCOUNT_TYPE.INCOME, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.INCOME, + parentType: ACCOUNT_PARENT_TYPE.INCOME, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Other Income', + key: ACCOUNT_TYPE.OTHER_INCOME, + normal: ACCOUNT_NORMAL.CREDIT, + rootType: ACCOUNT_ROOT_TYPE.INCOME, + parentType: ACCOUNT_PARENT_TYPE.INCOME, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Cost of Goods Sold', + key: ACCOUNT_TYPE.COST_OF_GOODS_SOLD, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.EXPENSE, + parentType: ACCOUNT_PARENT_TYPE.EXPENSE, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Expense', + key: ACCOUNT_TYPE.EXPENSE, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.EXPENSE, + parentType: ACCOUNT_PARENT_TYPE.EXPENSE, + balanceSheet: false, + incomeSheet: true, + }, + { + label: 'Other Expense', + key: ACCOUNT_TYPE.OTHER_EXPENSE, + normal: ACCOUNT_NORMAL.DEBIT, + rootType: ACCOUNT_ROOT_TYPE.EXPENSE, + parentType: ACCOUNT_PARENT_TYPE.EXPENSE, + balanceSheet: false, + incomeSheet: true, + }, +]; + +export const getAccountsSupportsMultiCurrency = () => { + return ACCOUNT_TYPES.filter((account) => account.multiCurrency); +}; diff --git a/packages/server-nest/src/constants/data-types.ts b/packages/server-nest/src/constants/data-types.ts new file mode 100644 index 000000000..8a87f87e7 --- /dev/null +++ b/packages/server-nest/src/constants/data-types.ts @@ -0,0 +1,7 @@ +export const DATATYPES_LENGTH = { + STRING: 255, + TEXT: 65535, + INT_10: 4294967295, + DECIMAL_13_3: 9999999999.999, + DECIMAL_15_5: 999999999999.999, +}; diff --git a/packages/server-nest/src/i18n/en/test.json b/packages/server-nest/src/i18n/en/test.json new file mode 100644 index 000000000..4936b38ec --- /dev/null +++ b/packages/server-nest/src/i18n/en/test.json @@ -0,0 +1,21 @@ +{ + "HELLO": "Hello", + "PRODUCT": { + "NEW": "New Product: {name}" + }, + "ENGLISH": "English", + "ARRAY": ["ONE", "TWO", "THREE"], + "cat": "Cat", + "cat_name": "Cat: {name}", + "set-up-password": { + "heading": "Hello, {username}", + "title": "Forgot password", + "followLink": "Please follow the link to set up your password" + }, + "day_interval": { + "one": "Every day", + "other": "Every {count} days", + "zero": "Never" + }, + "nested": "We go shopping: $t(test.day_interval, {{\"count\": {count} }})" +} diff --git a/packages/server-nest/src/interceptors/ExcludeNull.interceptor.ts b/packages/server-nest/src/interceptors/ExcludeNull.interceptor.ts new file mode 100644 index 000000000..2c166abc0 --- /dev/null +++ b/packages/server-nest/src/interceptors/ExcludeNull.interceptor.ts @@ -0,0 +1,15 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class ExcludeNullInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe(map((value) => (value === null ? '' : value))); + } +} diff --git a/packages/server-nest/src/interceptors/global-prefix.interceptor.ts b/packages/server-nest/src/interceptors/global-prefix.interceptor.ts new file mode 100644 index 000000000..010cef5f7 --- /dev/null +++ b/packages/server-nest/src/interceptors/global-prefix.interceptor.ts @@ -0,0 +1,21 @@ +import { + Injectable, + NestInterceptor, + ExecutionContext, + CallHandler, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class GlobalPrefixInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + request.url = `/api${request.url}`; + return next.handle().pipe( + map((data) => { + return data; + }), + ); + } +} diff --git a/packages/server-nest/src/interceptors/user-ip.interceptor.ts b/packages/server-nest/src/interceptors/user-ip.interceptor.ts new file mode 100644 index 000000000..51bce19a7 --- /dev/null +++ b/packages/server-nest/src/interceptors/user-ip.interceptor.ts @@ -0,0 +1,21 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import { Observable } from 'rxjs'; + +@Injectable() +export class UserIpInterceptor implements NestInterceptor { + constructor(private readonly cls: ClsService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const userIp = request.connection.remoteAddress; + this.cls.set('ip', userIp); + + return next.handle(); + } +} diff --git a/packages/server-nest/src/interfaces/Account.ts b/packages/server-nest/src/interfaces/Account.ts new file mode 100644 index 000000000..5124f7967 --- /dev/null +++ b/packages/server-nest/src/interfaces/Account.ts @@ -0,0 +1,174 @@ +import { Knex } from 'knex'; + +export interface IAccountDTO { + name: string; + code: string; + description: string; + accountType: string; + parentAccountId?: number; + active: boolean; + bankBalance?: number; + accountMask?: string; +} + +export interface IAccountCreateDTO extends IAccountDTO { + currencyCode?: string; + plaidAccountId?: string; + plaidItemId?: string; +} + +export interface IAccountEditDTO extends IAccountDTO {} + +export interface IAccount { + id: number; + name: string; + slug: string; + code: string; + index: number; + description: string; + accountType: string; + parentAccountId: number; + active: boolean; + predefined: boolean; + amount: number; + currencyCode: string; + transactions?: any[]; + type?: any[]; + accountNormal: string; + accountParentType: string; + bankBalance: string; + plaidItemId: number | null; + lastFeedsUpdatedAt: Date; +} + +export enum AccountNormal { + DEBIT = 'debit', + CREDIT = 'credit', +} + +export interface IAccountsTransactionsFilter { + accountId?: number; + limit?: number; +} + +export interface IAccountTransaction { + id?: number; + + credit: number; + debit: number; + currencyCode: string; + exchangeRate: number; + + accountId: number; + contactId?: number | null; + date: string | Date; + + referenceType: string; + referenceTypeFormatted: string; + referenceId: number; + + referenceNumber?: string; + + transactionNumber?: string; + transactionType?: string; + + note?: string; + + index: number; + indexGroup?: number; + + costable?: boolean; + + userId?: number; + itemId?: number; + branchId?: number; + projectId?: number; + + account?: IAccount; + + taxRateId?: number; + taxRate?: number; +} +export interface IAccountResponse extends IAccount {} + +export enum IAccountsStructureType { + Tree = 'tree', + Flat = 'flat', +} + +export interface IAccountsFilter { + stringifiedFilterRoles?: string; + onlyInactive: boolean; + structure?: IAccountsStructureType; +} + +export interface IAccountType { + label: string; + key: string; + normal: string; + rootType: string; + childType: string; + balanceSheet: boolean; + incomeSheet: boolean; +} + +export interface IAccountsTypesService { + getAccountsTypes(tenantId: number): Promise; +} + +export interface IAccountEventCreatingPayload { + tenantId: number; + accountDTO: any; + trx: Knex.Transaction; +} +export interface IAccountEventCreatedPayload { + tenantId: number; + account: IAccount; + accountId: number; + trx: Knex.Transaction; +} + +export interface IAccountEventEditedPayload { + tenantId: number; + account: IAccount; + oldAccount: IAccount; + trx: Knex.Transaction; +} + +export interface IAccountEventDeletedPayload { + tenantId: number; + accountId: number; + oldAccount: IAccount; + trx: Knex.Transaction; +} + +export interface IAccountEventDeletePayload { + trx: Knex.Transaction; + oldAccount: IAccount; + tenantId: number; +} + +export interface IAccountEventActivatedPayload { + tenantId: number; + accountId: number; + trx: Knex.Transaction; +} + +export enum AccountAction { + CREATE = 'Create', + EDIT = 'Edit', + DELETE = 'Delete', + VIEW = 'View', + TransactionsLocking = 'TransactionsLocking', +} + +export enum TaxRateAction { + CREATE = 'Create', + EDIT = 'Edit', + DELETE = 'Delete', + VIEW = 'View', +} + +export interface CreateAccountParams { + ignoreUniqueName: boolean; +} diff --git a/packages/server-nest/src/interfaces/Item.ts b/packages/server-nest/src/interfaces/Item.ts new file mode 100644 index 000000000..1881a9f92 --- /dev/null +++ b/packages/server-nest/src/interfaces/Item.ts @@ -0,0 +1,165 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Knex } from 'knex'; +import { Item } from 'src/modules/Items/models/Item'; +// import { AbilitySubject } from '@/interfaces'; +// import { IFilterRole } from '@/interfaces/DynamicFilter'; + +export interface IItem { + id: number; + name: string; + type: string; + code: string; + + sellable: boolean; + purchasable: boolean; + + costPrice: number; + sellPrice: number; + currencyCode: string; + + costAccountId: number; + sellAccountId: number; + inventoryAccountId: number; + + sellDescription: string; + purchaseDescription: string; + + sellTaxRateId: number; + purchaseTaxRateId: number; + + quantityOnHand: number; + + note: string; + active: boolean; + + categoryId: number; + userId: number; + + createdAt: Date; + updatedAt: Date; +} + +export class IItemDTO { + @ApiProperty() + name: string; + + @ApiProperty() + type: string; + + @ApiProperty() + code: string; + + @ApiProperty() + sellable: boolean; + + @ApiProperty() + purchasable: boolean; + + @ApiProperty() + costPrice: number; + + @ApiProperty() + sellPrice: number; + + @ApiProperty() + currencyCode: string; + + @ApiProperty() + costAccountId: number; + + @ApiProperty() + sellAccountId: number; + + @ApiProperty() + inventoryAccountId: number; + + @ApiProperty() + sellDescription: string; + + @ApiProperty() + purchaseDescription: string; + + @ApiProperty() + sellTaxRateId: number; + + @ApiProperty() + purchaseTaxRateId: number; + + @ApiProperty() + quantityOnHand: number; + + @ApiProperty() + note: string; + + @ApiProperty() + active: boolean; + + @ApiProperty() + categoryId: number; +} + +export interface IItemCreateDTO extends IItemDTO {} +export interface IItemEditDTO extends IItemDTO {} + +// export interface IItemsService { +// getItem(tenantId: number, itemId: number): Promise; +// deleteItem(tenantId: number, itemId: number): Promise; +// editItem(tenantId: number, itemId: number, itemDTO: IItemDTO): Promise; +// newItem(tenantId: number, itemDTO: IItemDTO): Promise; +// itemsList( +// tenantId: number, +// itemsFilter: IItemsFilter, +// ): Promise<{ items: IItem[] }>; +// } + +// export interface IItemsFilter extends IDynamicListFilterDTO { +// stringifiedFilterRoles?: string; +// page: number; +// pageSize: number; +// inactiveMode: boolean; +// viewSlug?: string; +// } + +// export interface IItemsAutoCompleteFilter { +// limit: number; +// keyword: string; +// filterRoles?: IFilterRole[]; +// columnSortBy: string; +// sortOrder: string; +// } + +export interface IItemEventCreatedPayload { + tenantId: number; + item: Item; + itemId: number; + trx: Knex.Transaction; +} + +export interface IItemEventEditedPayload { + item: Item; + oldItem: Item; + itemId: number; + trx: Knex.Transaction; +} + +export interface IItemEventDeletingPayload { + tenantId: number; + trx: Knex.Transaction; + oldItem: IItem; +} + +export interface IItemEventDeletedPayload { + tenantId: number; + oldItem: IItem; + itemId: number; + trx: Knex.Transaction; +} + +export enum ItemAction { + CREATE = 'Create', + EDIT = 'Edit', + DELETE = 'Delete', + VIEW = 'View', +} + +// export type ItemAbility = [ItemAction, AbilitySubject.Item]; diff --git a/packages/server-nest/src/interfaces/Model.ts b/packages/server-nest/src/interfaces/Model.ts new file mode 100644 index 000000000..2623ce836 --- /dev/null +++ b/packages/server-nest/src/interfaces/Model.ts @@ -0,0 +1,192 @@ +export interface IModel { + name: string; + tableName: string; + fields: { [key: string]: any }; +} + +export interface IFilterMeta { + sortOrder: string; + sortBy: string; +} + +export interface IPaginationMeta { + pageSize: number; + page: number; +} + +export interface IModelMetaDefaultSort { + sortOrder: ISortOrder; + sortField: string; +} + +export type IModelColumnType = + | 'text' + | 'number' + | 'enumeration' + | 'boolean' + | 'relation'; + +export type ISortOrder = 'DESC' | 'ASC'; + +export interface IModelMetaFieldCommon { + name: string; + column: string; + columnable?: boolean; + customQuery?: Function; + required?: boolean; + importHint?: string; + importableRelationLabel?: string; + order?: number; + unique?: number; + dataTransferObjectKey?: string; +} + +export interface IModelMetaFieldText { + fieldType: 'text'; + minLength?: number; + maxLength?: number; +} +export interface IModelMetaFieldBoolean { + fieldType: 'boolean'; +} +export interface IModelMetaFieldNumber { + fieldType: 'number'; + min?: number; + max?: number; +} +export interface IModelMetaFieldDate { + fieldType: 'date'; +} +export interface IModelMetaFieldUrl { + fieldType: 'url'; +} +export type IModelMetaField = IModelMetaFieldCommon & + ( + | IModelMetaFieldText + | IModelMetaFieldNumber + | IModelMetaFieldBoolean + | IModelMetaFieldDate + | IModelMetaFieldUrl + | IModelMetaEnumerationField + | IModelMetaRelationField + | IModelMetaCollectionField + ); + +export interface IModelMetaEnumerationOption { + key: string; + label: string; +} + +export interface IModelMetaEnumerationField { + fieldType: 'enumeration'; + options: IModelMetaEnumerationOption[]; +} + +export interface IModelMetaRelationFieldCommon { + fieldType: 'relation'; +} + +export interface IModelMetaRelationEnumerationField { + relationType: 'enumeration'; + relationKey: string; + relationEntityLabel: string; + relationEntityKey: string; +} + +export interface IModelMetaFieldWithFields { + fields: IModelMetaFieldCommon2 & + ( + | IModelMetaFieldText + | IModelMetaFieldNumber + | IModelMetaFieldBoolean + | IModelMetaFieldDate + | IModelMetaFieldUrl + | IModelMetaEnumerationField + | IModelMetaRelationField + ); +} + +interface IModelMetaCollectionObjectField extends IModelMetaFieldWithFields { + collectionOf: 'object'; +} + +export interface IModelMetaCollectionFieldCommon { + fieldType: 'collection'; + collectionMinLength?: number; + collectionMaxLength?: number; +} + +export type IModelMetaCollectionField = IModelMetaCollectionFieldCommon & + IModelMetaCollectionObjectField; + +export type IModelMetaRelationField = IModelMetaRelationFieldCommon & + IModelMetaRelationEnumerationField; + +interface IModelPrintMeta { + pageTitle: string; +} + +export interface IModelMeta { + defaultFilterField: string; + defaultSort: IModelMetaDefaultSort; + + exportable?: boolean; + exportFlattenOn?: string; + + importable?: boolean; + importAggregator?: string; + importAggregateOn?: string; + importAggregateBy?: string; + + print?: IModelPrintMeta; + + fields: { [key: string]: IModelMetaField }; + columns: { [key: string]: IModelMetaColumn }; +} + +// ---- +export interface IModelMetaFieldCommon2 { + name: string; + required?: boolean; + importHint?: string; + order?: number; + unique?: number; + features?: Array; +} + +export interface IModelMetaRelationField2 { + fieldType: 'relation'; + relationModel: string; + importableRelationLabel: string | string[]; +} + +export type IModelMetaField2 = IModelMetaFieldCommon2 & + ( + | IModelMetaFieldText + | IModelMetaFieldNumber + | IModelMetaFieldBoolean + | IModelMetaFieldDate + | IModelMetaFieldUrl + | IModelMetaEnumerationField + | IModelMetaRelationField2 + | IModelMetaCollectionField + ); + +export interface ImodelMetaColumnMeta { + name: string; + accessor?: string; + exportable?: boolean; +} + +interface IModelMetaColumnText { + type: 'text;'; +} + +interface IModelMetaColumnCollection { + type: 'collection'; + collectionOf: 'object'; + columns: { [key: string]: ImodelMetaColumnMeta & IModelMetaColumnText }; +} + +export type IModelMetaColumn = ImodelMetaColumnMeta & + (IModelMetaColumnText | IModelMetaColumnCollection); diff --git a/packages/server-nest/src/interfaces/SubscriptionPlan.ts b/packages/server-nest/src/interfaces/SubscriptionPlan.ts new file mode 100644 index 000000000..90afdaf77 --- /dev/null +++ b/packages/server-nest/src/interfaces/SubscriptionPlan.ts @@ -0,0 +1,8 @@ +export interface SubscriptionPayload { + lemonSqueezyId?: string; +} + +export enum SubscriptionPaymentStatus { + Succeed = 'succeed', + Failed = 'failed', +} diff --git a/packages/server-nest/src/libs/accounts-utils/AccountTypesUtils.ts b/packages/server-nest/src/libs/accounts-utils/AccountTypesUtils.ts new file mode 100644 index 000000000..57d3e3484 --- /dev/null +++ b/packages/server-nest/src/libs/accounts-utils/AccountTypesUtils.ts @@ -0,0 +1,101 @@ +import { get } from 'lodash'; +import { ACCOUNT_TYPES } from '@/constants/accounts'; + +export class AccountTypesUtils { + /** + * Retrieve account types list. + */ + static getList() { + return ACCOUNT_TYPES; + } + + /** + * Retrieve accounts types by the given root type. + * @param {string} rootType - + * @return {string} + */ + static getTypesByRootType(rootType: string) { + return ACCOUNT_TYPES.filter((type) => type.rootType === rootType); + } + + /** + * Retrieve account type by the given account type key. + * @param {string} key + * @param {string} accessor + */ + static getType(key: string, accessor?: string) { + const type = ACCOUNT_TYPES.find((type) => type.key === key); + + if (accessor) { + return get(type, accessor); + } + return type; + } + + /** + * Retrieve accounts types by the parent account type. + * @param {string} parentType + */ + static getTypesByParentType(parentType: string) { + return ACCOUNT_TYPES.filter((type) => type.parentType === parentType); + } + + /** + * Retrieve accounts types by the given account normal. + * @param {string} normal + */ + static getTypesByNormal(normal: string) { + return ACCOUNT_TYPES.filter((type) => type.normal === normal); + } + + /** + * Detarmines whether the root type equals the account type. + * @param {string} key + * @param {string} rootType + */ + static isRootTypeEqualsKey(key: string, rootType: string): boolean { + return ACCOUNT_TYPES.some((type) => { + const isType = type.key === key; + const isRootType = type.rootType === rootType; + + return isType && isRootType; + }); + } + + /** + * Detarmines whether the parent account type equals the account type key. + * @param {string} key - Account type key. + * @param {string} parentType - Account parent type. + */ + static isParentTypeEqualsKey(key: string, parentType: string): boolean { + return ACCOUNT_TYPES.some((type) => { + const isType = type.key === key; + const isParentType = type.parentType === parentType; + + return isType && isParentType; + }); + } + + /** + * Detarmines whether account type has balance sheet. + * @param {string} key - Account type key. + * + */ + static isTypeBalanceSheet(key: string): boolean { + return ACCOUNT_TYPES.some((type) => { + const isType = type.key === key; + return isType && type.balanceSheet; + }); + } + + /** + * Detarmines whether account type has profit/loss sheet. + * @param {string} key - Account type key. + */ + static isTypePLSheet(key: string): boolean { + return ACCOUNT_TYPES.some((type) => { + const isType = type.key === key; + return isType && type.incomeSheet; + }); + } +} \ No newline at end of file diff --git a/packages/server-nest/src/libs/dependency-graph/index.ts b/packages/server-nest/src/libs/dependency-graph/index.ts new file mode 100644 index 000000000..4cd8780fb --- /dev/null +++ b/packages/server-nest/src/libs/dependency-graph/index.ts @@ -0,0 +1,350 @@ +// @ts-nocheck +/** + * A simple dependency graph + */ +/** + * Helper for creating a Topological Sort using Depth-First-Search on a set of edges. + * + * Detects cycles and throws an Error if one is detected (unless the "circular" + * parameter is "true" in which case it ignores them). + * + * @param edges The set of edges to DFS through + * @param leavesOnly Whether to only return "leaf" nodes (ones who have no edges) + * @param result An array in which the results will be populated + * @param circular A boolean to allow circular dependencies + */ +function createDFS(edges, leavesOnly, result, circular) { + var visited = {}; + return function (start) { + if (visited[start]) { + return; + } + var inCurrentPath = {}; + var currentPath = []; + var todo = []; // used as a stack + todo.push({ node: start, processed: false }); + while (todo.length > 0) { + var current = todo[todo.length - 1]; // peek at the todo stack + var processed = current.processed; + var node = current.node; + if (!processed) { + // Haven't visited edges yet (visiting phase) + if (visited[node]) { + todo.pop(); + continue; + } else if (inCurrentPath[node]) { + // It's not a DAG + if (circular) { + todo.pop(); + // If we're tolerating cycles, don't revisit the node + continue; + } + currentPath.push(node); + throw new DepGraphCycleError(currentPath); + } + + inCurrentPath[node] = true; + currentPath.push(node); + var nodeEdges = edges[node]; + // (push edges onto the todo stack in reverse order to be order-compatible with the old DFS implementation) + for (var i = nodeEdges.length - 1; i >= 0; i--) { + todo.push({ node: nodeEdges[i], processed: false }); + } + current.processed = true; + } else { + // Have visited edges (stack unrolling phase) + todo.pop(); + currentPath.pop(); + inCurrentPath[node] = false; + visited[node] = true; + if (!leavesOnly || edges[node].length === 0) { + result.push(node); + } + } + } + }; + } + + /** + * Simple Dependency Graph + */ + var DepGraph = (DepGraph = function DepGraph(opts) { + this.nodes = {}; // Node -> Node/Data (treated like a Set) + this.outgoingEdges = {}; // Node -> [Dependency Node] + this.incomingEdges = {}; // Node -> [Dependant Node] + this.circular = opts && !!opts.circular; // Allows circular deps + }); + + DepGraph.fromArray = ( + items, + options = { itemId: 'id', parentItemId: 'parent_id' } + ) => { + const depGraph = new DepGraph(); + + items.forEach((item) => { + depGraph.addNode(item[options.itemId], item); + }); + items.forEach((item) => { + if (item[options.parentItemId]) { + depGraph.addDependency(item[options.parentItemId], item[options.itemId]); + } + }); + return depGraph; + }; + + DepGraph.prototype = { + /** + * The number of nodes in the graph. + */ + size: function () { + return Object.keys(this.nodes).length; + }, + /** + * Add a node to the dependency graph. If a node already exists, this method will do nothing. + */ + addNode: function (node, data) { + if (!this.hasNode(node)) { + // Checking the arguments length allows the user to add a node with undefined data + if (arguments.length === 2) { + this.nodes[node] = data; + } else { + this.nodes[node] = node; + } + this.outgoingEdges[node] = []; + this.incomingEdges[node] = []; + } + }, + /** + * Remove a node from the dependency graph. If a node does not exist, this method will do nothing. + */ + removeNode: function (node) { + if (this.hasNode(node)) { + delete this.nodes[node]; + delete this.outgoingEdges[node]; + delete this.incomingEdges[node]; + [this.incomingEdges, this.outgoingEdges].forEach(function (edgeList) { + Object.keys(edgeList).forEach(function (key) { + var idx = edgeList[key].indexOf(node); + if (idx >= 0) { + edgeList[key].splice(idx, 1); + } + }, this); + }); + } + }, + /** + * Check if a node exists in the graph + */ + hasNode: function (node) { + return this.nodes.hasOwnProperty(node); + }, + /** + * Get the data associated with a node name + */ + getNodeData: function (node) { + if (this.hasNode(node)) { + return this.nodes[node]; + } else { + throw new Error('Node does not exist: ' + node); + } + }, + + /** + * Set the associated data for a given node name. If the node does not exist, this method will throw an error + */ + setNodeData: function (node, data) { + if (this.hasNode(node)) { + this.nodes[node] = data; + } else { + throw new Error('Node does not exist: ' + node); + } + }, + /** + * Add a dependency between two nodes. If either of the nodes does not exist, + * an Error will be thrown. + */ + addDependency: function (from, to) { + if (!this.hasNode(from)) { + throw new Error('Node does not exist: ' + from); + } + if (!this.hasNode(to)) { + throw new Error('Node does not exist: ' + to); + } + if (this.outgoingEdges[from].indexOf(to) === -1) { + this.outgoingEdges[from].push(to); + } + if (this.incomingEdges[to].indexOf(from) === -1) { + this.incomingEdges[to].push(from); + } + return true; + }, + /** + * Remove a dependency between two nodes. + */ + removeDependency: function (from, to) { + var idx; + if (this.hasNode(from)) { + idx = this.outgoingEdges[from].indexOf(to); + if (idx >= 0) { + this.outgoingEdges[from].splice(idx, 1); + } + } + + if (this.hasNode(to)) { + idx = this.incomingEdges[to].indexOf(from); + if (idx >= 0) { + this.incomingEdges[to].splice(idx, 1); + } + } + }, + /** + * Return a clone of the dependency graph. If any custom data is attached + * to the nodes, it will only be shallow copied. + */ + clone: function () { + var source = this; + var result = new DepGraph(); + var keys = Object.keys(source.nodes); + keys.forEach(function (n) { + result.nodes[n] = source.nodes[n]; + result.outgoingEdges[n] = source.outgoingEdges[n].slice(0); + result.incomingEdges[n] = source.incomingEdges[n].slice(0); + }); + return result; + }, + /** + * Get an array containing the nodes that the specified node depends on (transitively). + * + * Throws an Error if the graph has a cycle, or the specified node does not exist. + * + * If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned + * in the array. + */ + dependenciesOf: function (node, leavesOnly) { + if (this.hasNode(node)) { + var result = []; + var DFS = createDFS( + this.outgoingEdges, + leavesOnly, + result, + this.circular + ); + DFS(node); + var idx = result.indexOf(node); + if (idx >= 0) { + result.splice(idx, 1); + } + return result; + } else { + throw new Error('Node does not exist: ' + node); + } + }, + /** + * get an array containing the nodes that depend on the specified node (transitively). + * + * Throws an Error if the graph has a cycle, or the specified node does not exist. + * + * If `leavesOnly` is true, only nodes that do not have any dependants will be returned in the array. + */ + dependantsOf: function (node, leavesOnly) { + if (this.hasNode(node)) { + var result = []; + var DFS = createDFS( + this.incomingEdges, + leavesOnly, + result, + this.circular + ); + DFS(node); + var idx = result.indexOf(node); + if (idx >= 0) { + result.splice(idx, 1); + } + return result; + } else { + throw new Error('Node does not exist: ' + node); + } + }, + /** + * Construct the overall processing order for the dependency graph. + * + * Throws an Error if the graph has a cycle. + * + * If `leavesOnly` is true, only nodes that do not depend on any other nodes will be returned. + */ + overallOrder: function (leavesOnly) { + var self = this; + var result = []; + var keys = Object.keys(this.nodes); + if (keys.length === 0) { + return result; // Empty graph + } else { + if (!this.circular) { + // Look for cycles - we run the DFS starting at all the nodes in case there + // are several disconnected subgraphs inside this dependency graph. + var CycleDFS = createDFS(this.outgoingEdges, false, [], this.circular); + keys.forEach(function (n) { + CycleDFS(n); + }); + } + + var DFS = createDFS( + this.outgoingEdges, + leavesOnly, + result, + this.circular + ); + // Find all potential starting points (nodes with nothing depending on them) an + // run a DFS starting at these points to get the order + keys + .filter(function (node) { + return self.incomingEdges[node].length === 0; + }) + .forEach(function (n) { + DFS(n); + }); + + // If we're allowing cycles - we need to run the DFS against any remaining + // nodes that did not end up in the initial result (as they are part of a + // subgraph that does not have a clear starting point) + if (this.circular) { + keys + .filter(function (node) { + return result.indexOf(node) === -1; + }) + .forEach(function (n) { + DFS(n); + }); + } + + return result; + } + }, + + mapNodes(mapper) {}, + }; + + /** + * Cycle error, including the path of the cycle. + */ + var DepGraphCycleError = (exports.DepGraphCycleError = function (cyclePath) { + var message = 'Dependency Cycle Found: ' + cyclePath.join(' -> '); + var instance = new Error(message); + instance.cyclePath = cyclePath; + Object.setPrototypeOf(instance, Object.getPrototypeOf(this)); + if (Error.captureStackTrace) { + Error.captureStackTrace(instance, DepGraphCycleError); + } + return instance; + }); + DepGraphCycleError.prototype = Object.create(Error.prototype, { + constructor: { + value: Error, + enumerable: false, + writable: true, + configurable: true, + }, + }); + Object.setPrototypeOf(DepGraphCycleError, Error); + + export default DepGraph; \ No newline at end of file diff --git a/packages/server-nest/src/main.ts b/packages/server-nest/src/main.ts new file mode 100644 index 000000000..7c411ec8f --- /dev/null +++ b/packages/server-nest/src/main.ts @@ -0,0 +1,25 @@ +import { NestFactory } from '@nestjs/core'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { AppModule } from './modules/App/App.module'; +import { ClsMiddleware } from 'nestjs-cls'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + app.setGlobalPrefix('/api'); + + // create and mount the middleware manually here + app.use(new ClsMiddleware({}).use); + + const config = new DocumentBuilder() + .setTitle('Bigcapital') + .setDescription('Financial accounting software') + .setVersion('1.0') + .addTag('cats') + .build(); + + const documentFactory = () => SwaggerModule.createDocument(app, config); + SwaggerModule.setup('swagger', app, documentFactory); + + await app.listen(process.env.PORT ?? 3000); +} +bootstrap(); diff --git a/packages/server-nest/src/middleware/logger.middleware.ts b/packages/server-nest/src/middleware/logger.middleware.ts new file mode 100644 index 000000000..c89beb2c5 --- /dev/null +++ b/packages/server-nest/src/middleware/logger.middleware.ts @@ -0,0 +1,13 @@ +import { Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +@Injectable() +export class LoggerMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction) { + console.log(`Request...`); + + // @ts-expect-error + req.test = 'test'; + next(); + } +} diff --git a/packages/server-nest/src/models/Model.ts b/packages/server-nest/src/models/Model.ts new file mode 100644 index 000000000..5afc6ba77 --- /dev/null +++ b/packages/server-nest/src/models/Model.ts @@ -0,0 +1,5 @@ +import { Model } from 'objection'; + +export class BaseModel extends Model { + public readonly id: number; +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/Accounts/models/Account.ts b/packages/server-nest/src/modules/Accounts/models/Account.ts new file mode 100644 index 000000000..7948b4bc7 --- /dev/null +++ b/packages/server-nest/src/modules/Accounts/models/Account.ts @@ -0,0 +1,441 @@ +/* eslint-disable global-require */ +import { mixin, Model } from 'objection'; +import { castArray } from 'lodash'; +import DependencyGraph from '@/libs/dependency-graph'; +import { + ACCOUNT_TYPES, + getAccountsSupportsMultiCurrency, +} from 'src/constants/accounts'; +import { TenantModel } from '@/modules/System/models/TenantModel'; +import { SearchableModel } from '@/modules/Search/SearchableMdel'; +import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel'; +import { ModelSettings } from '@/modules/Settings/ModelSettings'; +import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils'; +// import AccountSettings from './Account.Settings'; +// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants'; +// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder'; +// import { flatToNestedArray } from 'utils'; + +// @ts-expect-error +export class Account extends mixin(TenantModel, [ + ModelSettings, + CustomViewBaseModel, + SearchableModel, +]) { + accountType: string; + + /** + * Table name. + */ + static get tableName() { + return 'accounts'; + } + + /** + * Timestamps columns. + */ + static get timestamps() { + return ['createdAt', 'updatedAt']; + } + + /** + * Virtual attributes. + */ + static get virtualAttributes() { + return [ + 'accountTypeLabel', + 'accountParentType', + 'accountRootType', + 'accountNormal', + 'accountNormalFormatted', + 'isBalanceSheetAccount', + 'isPLSheet', + ]; + } + + /** + * Account normal. + */ + get accountNormal() { + return AccountTypesUtils.getType(this.accountType, 'normal'); + } + + get accountNormalFormatted() { + const paris = { + credit: 'Credit', + debit: 'Debit', + }; + return paris[this.accountNormal] || ''; + } + + /** + * Retrieve account type label. + */ + get accountTypeLabel() { + return AccountTypesUtils.getType(this.accountType, 'label'); + } + + /** + * Retrieve account parent type. + */ + get accountParentType() { + return AccountTypesUtils.getType(this.accountType, 'parentType'); + } + + /** + * Retrieve account root type. + */ + get accountRootType() { + return AccountTypesUtils.getType(this.accountType, 'rootType'); + } + + /** + * Retrieve whether the account is balance sheet account. + */ + get isBalanceSheetAccount() { + return this.isBalanceSheet(); + } + + /** + * Retrieve whether the account is profit/loss sheet account. + */ + get isPLSheet() { + return this.isProfitLossSheet(); + } + /** + * Allows to mark model as resourceable to viewable and filterable. + */ + static get resourceable() { + return true; + } + + /** + * Model modifiers. + */ + static get modifiers() { + const TABLE_NAME = Account.tableName; + + return { + /** + * Inactive/Active mode. + */ + inactiveMode(query, active = false) { + query.where('accounts.active', !active); + }, + + filterAccounts(query, accountIds) { + if (accountIds.length > 0) { + query.whereIn(`${TABLE_NAME}.id`, accountIds); + } + }, + filterAccountTypes(query, typesIds) { + if (typesIds.length > 0) { + query.whereIn('account_types.account_type_id', typesIds); + } + }, + viewRolesBuilder(query, conditionals, expression) { + // buildFilterQuery(Account.tableName, conditionals, expression)(query); + }, + sortColumnBuilder(query, columnKey, direction) { + // buildSortColumnQuery(Account.tableName, columnKey, direction)(query); + }, + + /** + * Filter by root type. + */ + filterByRootType(query, rootType) { + const filterTypes = ACCOUNT_TYPES.filter( + (accountType) => accountType.rootType === rootType, + ).map((accountType) => accountType.key); + + query.whereIn('account_type', filterTypes); + }, + + /** + * Filter by account normal + */ + filterByAccountNormal(query, accountNormal) { + const filterTypes = ACCOUNT_TYPES.filter( + (accountType) => accountType.normal === accountNormal, + ).map((accountType) => accountType.key); + + query.whereIn('account_type', filterTypes); + }, + + /** + * Finds account by the given slug. + * @param {*} query + * @param {*} slug + */ + findBySlug(query, slug) { + query.where('slug', slug).first(); + }, + + /** + * + * @param {*} query + * @param {*} baseCyrrency + */ + preventMutateBaseCurrency(query) { + const accountsTypes = getAccountsSupportsMultiCurrency(); + const accountsTypesKeys = accountsTypes.map((type) => type.key); + + query + .whereIn('accountType', accountsTypesKeys) + .where('seededAt', null) + .first(); + }, + }; + } + + /** + * Relationship mapping. + */ + static get relationMappings() { + // const AccountTransaction = require('models/AccountTransaction'); + // const Item = require('models/Item'); + // const InventoryAdjustment = require('models/InventoryAdjustment'); + // const ManualJournalEntry = require('models/ManualJournalEntry'); + // const Expense = require('models/Expense'); + // const ExpenseEntry = require('models/ExpenseCategory'); + // const ItemEntry = require('models/ItemEntry'); + // const UncategorizedTransaction = require('models/UncategorizedCashflowTransaction'); + // const PlaidItem = require('models/PlaidItem'); + + return { + // /** + // * Account model may has many transactions. + // */ + // transactions: { + // relation: Model.HasManyRelation, + // modelClass: AccountTransaction.default, + // join: { + // from: 'accounts.id', + // to: 'accounts_transactions.accountId', + // }, + // }, + // /** + // * + // */ + // itemsCostAccount: { + // relation: Model.HasManyRelation, + // modelClass: Item.default, + // join: { + // from: 'accounts.id', + // to: 'items.costAccountId', + // }, + // }, + // /** + // * + // */ + // itemsSellAccount: { + // relation: Model.HasManyRelation, + // modelClass: Item.default, + // join: { + // from: 'accounts.id', + // to: 'items.sellAccountId', + // }, + // }, + // /** + // * + // */ + // inventoryAdjustments: { + // relation: Model.HasManyRelation, + // modelClass: InventoryAdjustment.default, + // join: { + // from: 'accounts.id', + // to: 'inventory_adjustments.adjustmentAccountId', + // }, + // }, + // /** + // * + // */ + // manualJournalEntries: { + // relation: Model.HasManyRelation, + // modelClass: ManualJournalEntry.default, + // join: { + // from: 'accounts.id', + // to: 'manual_journals_entries.accountId', + // }, + // }, + // /** + // * + // */ + // expensePayments: { + // relation: Model.HasManyRelation, + // modelClass: Expense.default, + // join: { + // from: 'accounts.id', + // to: 'expenses_transactions.paymentAccountId', + // }, + // }, + // /** + // * + // */ + // expenseEntries: { + // relation: Model.HasManyRelation, + // modelClass: ExpenseEntry.default, + // join: { + // from: 'accounts.id', + // to: 'expense_transaction_categories.expenseAccountId', + // }, + // }, + // /** + // * + // */ + // entriesCostAccount: { + // relation: Model.HasManyRelation, + // modelClass: ItemEntry.default, + // join: { + // from: 'accounts.id', + // to: 'items_entries.costAccountId', + // }, + // }, + // /** + // * + // */ + // entriesSellAccount: { + // relation: Model.HasManyRelation, + // modelClass: ItemEntry.default, + // join: { + // from: 'accounts.id', + // to: 'items_entries.sellAccountId', + // }, + // }, + // /** + // * Associated uncategorized transactions. + // */ + // uncategorizedTransactions: { + // relation: Model.HasManyRelation, + // modelClass: UncategorizedTransaction.default, + // join: { + // from: 'accounts.id', + // to: 'uncategorized_cashflow_transactions.accountId', + // }, + // filter: (query) => { + // query.where('categorized', false); + // }, + // }, + // /** + // * Account model may belongs to a Plaid item. + // */ + // plaidItem: { + // relation: Model.BelongsToOneRelation, + // modelClass: PlaidItem.default, + // join: { + // from: 'accounts.plaidItemId', + // to: 'plaid_items.plaidItemId', + // }, + // }, + }; + } + + /** + * Detarmines whether the given type equals the account type. + * @param {string} accountType + * @return {boolean} + */ + isAccountType(accountType) { + const types = castArray(accountType); + return types.indexOf(this.accountType) !== -1; + } + + /** + * Detarmines whether the given root type equals the account type. + * @param {string} rootType + * @return {boolean} + */ + isRootType(rootType) { + return AccountTypesUtils.isRootTypeEqualsKey(this.accountType, rootType); + } + + /** + * Detarmine whether the given parent type equals the account type. + * @param {string} parentType + * @return {boolean} + */ + isParentType(parentType) { + return AccountTypesUtils.isParentTypeEqualsKey( + this.accountType, + parentType, + ); + } + + /** + * Detarmines whether the account is balance sheet account. + * @return {boolean} + */ + isBalanceSheet() { + return AccountTypesUtils.isTypeBalanceSheet(this.accountType); + } + + /** + * Detarmines whether the account is profit/loss account. + * @return {boolean} + */ + isProfitLossSheet() { + return AccountTypesUtils.isTypePLSheet(this.accountType); + } + + /** + * Detarmines whether the account is income statement account + * @return {boolean} + */ + isIncomeSheet() { + return this.isProfitLossSheet(); + } + + /** + * Converts flatten accounts list to nested array. + * @param {Array} accounts + * @param {Object} options + */ + static toNestedArray(accounts, options = { children: 'children' }) { + // return flatToNestedArray(accounts, { + // id: 'id', + // parentId: 'parentAccountId', + // }); + } + + /** + * Transformes the accounts list to depenedency graph structure. + * @param {IAccount[]} accounts + */ + static toDependencyGraph(accounts) { + return DependencyGraph.fromArray(accounts, { + itemId: 'id', + parentItemId: 'parentAccountId', + }); + } + + /** + * Model settings. + */ + // static get meta() { + // return AccountSettings; + // } + + /** + * Retrieve the default custom views, roles and columns. + */ + // static get defaultViews() { + // return DEFAULT_VIEWS; + // } + + /** + * Model search roles. + */ + static get searchRoles() { + return [ + { condition: 'or', fieldKey: 'name', comparator: 'contains' }, + { condition: 'or', fieldKey: 'code', comparator: 'like' }, + ]; + } + + /** + * Prevents mutate base currency since the model is not empty. + */ + static get preventMutateBaseCurrency() { + return true; + } +} diff --git a/packages/server-nest/src/modules/App/App.controller.spec.ts b/packages/server-nest/src/modules/App/App.controller.spec.ts new file mode 100644 index 000000000..fb487f202 --- /dev/null +++ b/packages/server-nest/src/modules/App/App.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './App.controller'; +import { AppService } from './App.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/packages/server-nest/src/modules/App/App.controller.ts b/packages/server-nest/src/modules/App/App.controller.ts new file mode 100644 index 000000000..6e79e7576 --- /dev/null +++ b/packages/server-nest/src/modules/App/App.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './App.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getHello(): string { + return this.appService.getHello(); + } +} diff --git a/packages/server-nest/src/modules/App/App.module.ts b/packages/server-nest/src/modules/App/App.module.ts new file mode 100644 index 000000000..9123140eb --- /dev/null +++ b/packages/server-nest/src/modules/App/App.module.ts @@ -0,0 +1,115 @@ +import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { join } from 'path'; +import { + AcceptLanguageResolver, + CookieResolver, + HeaderResolver, + I18nModule, + QueryResolver, +} from 'nestjs-i18n'; +import { BullModule } from '@nestjs/bullmq'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { ClsModule } from 'nestjs-cls'; +import { AppController } from './App.controller'; +import { AppService } from './App.service'; +import { ItemsModule } from '../Items/items.module'; +import { config } from '../../common/config'; +import { SystemDatabaseModule } from '../System/SystemDB/SystemDB.module'; +import { SystemModelsModule } from '../System/SystemModels/SystemModels.module'; +import { JwtStrategy } from '../Auth/Jwt.strategy'; +import { jwtConstants } from '../Auth/Auth.constants'; +import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module'; +import { TenancyModelsModule } from '../Tenancy/TenancyModels/Tenancy.module'; +import { LoggerMiddleware } from '@/middleware/logger.middleware'; +import { ExcludeNullInterceptor } from '@/interceptors/ExcludeNull.interceptor'; +import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; +import { JwtAuthGuard } from '../Auth/Jwt.guard'; +import { UserIpInterceptor } from '@/interceptors/user-ip.interceptor'; +import { TenancyGlobalMiddleware } from '../Tenancy/TenancyGlobal.middleware'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + envFilePath: '.env', + load: config, + isGlobal: true, + }), + SystemDatabaseModule, + SystemModelsModule, + EventEmitterModule.forRoot(), + I18nModule.forRootAsync({ + useFactory: () => ({ + fallbackLanguage: 'en', + loaderOptions: { + path: join(__dirname, '/../../i18n/'), + watch: true, + }, + }), + resolvers: [ + new QueryResolver(), + new HeaderResolver(), + new CookieResolver(), + AcceptLanguageResolver, + ], + }), + PassportModule, + JwtModule.register({ + secret: jwtConstants.secret, + signOptions: { expiresIn: '60s' }, + }), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + connection: { + host: configService.get('QUEUE_HOST'), + port: configService.get('QUEUE_PORT'), + }, + }), + inject: [ConfigService], + }), + ClsModule.forRoot({ + global: true, + middleware: { + mount: true, + setup: (cls, req: Request, res: Response) => { + cls.set('organizationId', req.headers['organization-id']); + cls.set('userId', 1); + }, + }, + }), + TenancyDatabaseModule, + TenancyModelsModule, + ItemsModule, + ], + controllers: [AppController], + providers: [ + { + provide: APP_GUARD, + useClass: JwtAuthGuard, + }, + { + provide: APP_INTERCEPTOR, + useClass: UserIpInterceptor, + }, + { + provide: APP_INTERCEPTOR, + useClass: ExcludeNullInterceptor, + }, + AppService, + JwtStrategy, + ], +}) +export class AppModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply(LoggerMiddleware) + .forRoutes({ path: '*', method: RequestMethod.ALL }); + + consumer + .apply(TenancyGlobalMiddleware) + .forRoutes({ path: '*', method: RequestMethod.ALL }); + } +} diff --git a/packages/server-nest/src/modules/App/App.service.ts b/packages/server-nest/src/modules/App/App.service.ts new file mode 100644 index 000000000..df40128b2 --- /dev/null +++ b/packages/server-nest/src/modules/App/App.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { JwtService } from '@nestjs/jwt'; + +@Injectable() +export class AppService { + // configService: ConfigService; + + constructor( + private configService: ConfigService, + private jwtService: JwtService, + ) {} + + getHello(): string { + console.log(this.configService.get('DATABASE_PORT')); + const payload = {}; + + const accessToken = this.jwtService.sign(payload); + + console.log(accessToken); + + return accessToken; + } +} diff --git a/packages/server-nest/src/modules/Auth/Auth.constants.ts b/packages/server-nest/src/modules/Auth/Auth.constants.ts new file mode 100644 index 000000000..9e8c4061f --- /dev/null +++ b/packages/server-nest/src/modules/Auth/Auth.constants.ts @@ -0,0 +1,4 @@ +export const jwtConstants = { + secret: + 'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.', +}; diff --git a/packages/server-nest/src/modules/Auth/AuthApplication.sevice.ts b/packages/server-nest/src/modules/Auth/AuthApplication.sevice.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server-nest/src/modules/Auth/AuthForgetPassword.service.ts b/packages/server-nest/src/modules/Auth/AuthForgetPassword.service.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server-nest/src/modules/Auth/AuthResetPassword.service.ts b/packages/server-nest/src/modules/Auth/AuthResetPassword.service.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server-nest/src/modules/Auth/AuthSignin.service.ts b/packages/server-nest/src/modules/Auth/AuthSignin.service.ts new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/packages/server-nest/src/modules/Auth/AuthSignin.service.ts @@ -0,0 +1,2 @@ + + diff --git a/packages/server-nest/src/modules/Auth/AuthSignup.service.ts b/packages/server-nest/src/modules/Auth/AuthSignup.service.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server-nest/src/modules/Auth/Jwt.guard.ts b/packages/server-nest/src/modules/Auth/Jwt.guard.ts new file mode 100644 index 000000000..2ea9918bc --- /dev/null +++ b/packages/server-nest/src/modules/Auth/Jwt.guard.ts @@ -0,0 +1,32 @@ +import { + ExecutionContext, + Injectable, + Scope, + SetMetadata, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; +import { ClsService } from 'nestjs-cls'; + +export const IS_PUBLIC_KEY = 'isPublic'; +export const PublicRoute = () => SetMetadata(IS_PUBLIC_KEY, true); + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + constructor( + private reflector: Reflector, + private readonly cls: ClsService, + ) { + super(); + } + canActivate(context: ExecutionContext) { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (isPublic) { + return true; + } + return super.canActivate(context); + } +} diff --git a/packages/server-nest/src/modules/Auth/Jwt.strategy.ts b/packages/server-nest/src/modules/Auth/Jwt.strategy.ts new file mode 100644 index 000000000..e7b6e0b77 --- /dev/null +++ b/packages/server-nest/src/modules/Auth/Jwt.strategy.ts @@ -0,0 +1,19 @@ +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; +import { jwtConstants } from './Auth.constants'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwtConstants.secret, + }); + } + + async validate(payload: any) { + return { userId: payload.sub, username: payload.username }; + } +} diff --git a/packages/server-nest/src/modules/CustomViews/CustomViewBaseModel.ts b/packages/server-nest/src/modules/CustomViews/CustomViewBaseModel.ts new file mode 100644 index 000000000..f7f1eda63 --- /dev/null +++ b/packages/server-nest/src/modules/CustomViews/CustomViewBaseModel.ts @@ -0,0 +1,22 @@ +import { Model as ObjectionModel } from 'objection'; + +export const CustomViewBaseModel = (Model) => + class extends Model { + /** + * Retrieve the default custom views, roles and columns. + */ + static get defaultViews() { + return []; + } + + /** + * Retrieve the default view by the given slug. + */ + static getDefaultViewBySlug(viewSlug) { + return this.defaultViews.find((view) => view.slug === viewSlug) || null; + } + + static getDefaultViews() { + return this.defaultViews; + } + }; diff --git a/packages/server-nest/src/modules/Items/CreateItem.service.ts b/packages/server-nest/src/modules/Items/CreateItem.service.ts new file mode 100644 index 000000000..cc86d7f54 --- /dev/null +++ b/packages/server-nest/src/modules/Items/CreateItem.service.ts @@ -0,0 +1,120 @@ +import { Knex } from 'knex'; +import { defaultTo } from 'lodash'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { IItemDTO, IItemEventCreatedPayload } from '@/interfaces/Item'; +import { events } from '@/common/events/events'; +import { ItemsValidators } from './ItemValidator.service'; +import { Item } from './models/Item'; +import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; +import { TenancyContext } from '../Tenancy/TenancyContext.service'; + +@Injectable({ scope: Scope.REQUEST }) +export class CreateItemService { + /** + * Constructor for the CreateItemService class. + * @param {EventEmitter2} eventEmitter - Event emitter for publishing item creation events. + * @param {UnitOfWork} uow - Unit of Work for tenant database transactions. + * @param {ItemsValidators} validators - Service for validating item data. + * @param {typeof Item} itemModel - The Item model class for database operations. + */ + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: ItemsValidators, + + @Inject(Item.name) + private readonly itemModel: typeof Item, + ) {} + + /** + * Authorize the creating item. + * @param {number} tenantId + * @param {IItemDTO} itemDTO + */ + async authorize(itemDTO: IItemDTO) { + // Validate whether the given item name already exists on the storage. + await this.validators.validateItemNameUniquiness(itemDTO.name); + + if (itemDTO.categoryId) { + await this.validators.validateItemCategoryExistance(itemDTO.categoryId); + } + if (itemDTO.sellAccountId) { + await this.validators.validateItemSellAccountExistance( + itemDTO.sellAccountId, + ); + } + // Validate the income account id existance if the item is sellable. + this.validators.validateIncomeAccountExistance( + itemDTO.sellable, + itemDTO.sellAccountId, + ); + if (itemDTO.costAccountId) { + await this.validators.validateItemCostAccountExistance( + itemDTO.costAccountId, + ); + } + // Validate the cost account id existance if the item is purchasable. + this.validators.validateCostAccountExistance( + itemDTO.purchasable, + itemDTO.costAccountId, + ); + if (itemDTO.inventoryAccountId) { + await this.validators.validateItemInventoryAccountExistance( + itemDTO.inventoryAccountId, + ); + } + if (itemDTO.purchaseTaxRateId) { + await this.validators.validatePurchaseTaxRateExistance( + itemDTO.purchaseTaxRateId, + ); + } + if (itemDTO.sellTaxRateId) { + await this.validators.validateSellTaxRateExistance(itemDTO.sellTaxRateId); + } + } + + /** + * Transforms the item DTO to model. + * @param {IItemDTO} itemDTO - Item DTO. + * @return {IItem} + */ + private transformNewItemDTOToModel(itemDTO: IItemDTO) { + return { + ...itemDTO, + active: defaultTo(itemDTO.active, 1), + quantityOnHand: itemDTO.type === 'inventory' ? 0 : null, + }; + } + + /** + * Creates a new item. + * @param {IItemDTO} itemDTO + * @return {Promise} + */ + public async createItem( + itemDTO: IItemDTO, + trx?: Knex.Transaction, + ): Promise { + // Authorize the item before creating. + await this.authorize(itemDTO); + + // Creates a new item with associated transactions under unit-of-work envirement. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + const itemInsert = this.transformNewItemDTOToModel(itemDTO); + + // Inserts a new item and fetch the created item. + const item = await this.itemModel.query(trx).insertAndFetch({ + ...itemInsert, + }); + // Triggers `onItemCreated` event. + await this.eventEmitter.emitAsync(events.item.onCreated, { + item, + itemId: item.id, + trx, + } as IItemEventCreatedPayload); + + return item; + }, trx); + } +} diff --git a/packages/server-nest/src/modules/Items/DeleteItem.service.ts b/packages/server-nest/src/modules/Items/DeleteItem.service.ts new file mode 100644 index 000000000..832d0efac --- /dev/null +++ b/packages/server-nest/src/modules/Items/DeleteItem.service.ts @@ -0,0 +1,67 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { Knex } from 'knex'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { + IItemEventDeletedPayload, + IItemEventDeletingPayload, +} from 'src/interfaces/Item'; +import { events } from 'src/common/events/events'; +import { Item } from './models/Item'; +import { ERRORS } from './Items.constants'; +import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; + +@Injectable() +export class DeleteItemService { + /** + * Constructor for the class. + * @param {EventEmitter2} eventEmitter - Event emitter for publishing item deletion events. + * @param {UnitOfWork} uow - Unit of Work for tenant database transactions. + * @param {typeof Item} itemModel - The Item model class for database operations. + */ + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + + @Inject(Item.name) + private readonly itemModel: typeof Item, + ) {} + + /** + * Delete the given item from the storage. + * @param {number} itemId - Item id. + * @return {Promise} + */ + public async deleteItem( + itemId: number, + trx?: Knex.Transaction, + ): Promise { + // Retrieve the given item or throw not found service error. + const oldItem = await this.itemModel + .query() + .findById(itemId) + .throwIfNotFound() + // @ts-expect-error + .queryAndThrowIfHasRelations({ + type: ERRORS.ITEM_HAS_ASSOCIATED_TRANSACTIONS, + }); + + // Delete item in unit of work. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Triggers `onItemDeleting` event. + await this.eventEmitter.emitAsync(events.item.onDeleting, { + trx, + oldItem, + } as IItemEventDeletingPayload); + + // Deletes the item. + await this.itemModel.query(trx).findById(itemId).delete(); + + // Triggers `onItemDeleted` event. + await this.eventEmitter.emitAsync(events.item.onDeleted, { + itemId, + oldItem, + trx, + } as IItemEventDeletedPayload); + }, trx); + } +} diff --git a/packages/server-nest/src/modules/Items/EditItem.service.ts b/packages/server-nest/src/modules/Items/EditItem.service.ts new file mode 100644 index 000000000..4be6cc7d4 --- /dev/null +++ b/packages/server-nest/src/modules/Items/EditItem.service.ts @@ -0,0 +1,143 @@ +import { Knex } from 'knex'; +import { Injectable, Inject } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { IItemDTO, IItemEventEditedPayload } from 'src/interfaces/Item'; +import { events } from 'src/common/events/events'; +import { ItemsValidators } from './ItemValidator.service'; +import { Item } from './models/Item'; +import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; + +@Injectable() +export class EditItemService { + /** + * Constructor for the class. + * @param {EventEmitter2} eventEmitter - Event emitter for publishing item edit events. + * @param {UnitOfWork} uow - Unit of Work for tenant database transactions. + * @param {ItemsValidators} validators - Service for validating item data. + * @param {typeof Item} itemModel - The Item model class for database operations. + */ + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: ItemsValidators, + + @Inject(Item.name) + private readonly itemModel: typeof Item, + ) {} + + /** + * Authorize the editing item. + * @param {IItemDTO} itemDTO + * @param {Item} oldItem + */ + async authorize(itemDTO: IItemDTO, oldItem: Item) { + // Validate edit item type from inventory type. + this.validators.validateEditItemFromInventory(itemDTO, oldItem); + + // Validate edit item type to inventory type. + await this.validators.validateEditItemTypeToInventory(oldItem, itemDTO); + + // Validate whether the given item name already exists on the storage. + await this.validators.validateItemNameUniquiness(itemDTO.name, oldItem.id); + + if (itemDTO.categoryId) { + await this.validators.validateItemCategoryExistance(itemDTO.categoryId); + } + if (itemDTO.sellAccountId) { + await this.validators.validateItemSellAccountExistance( + itemDTO.sellAccountId, + ); + } + // Validate the income account id existance if the item is sellable. + this.validators.validateIncomeAccountExistance( + itemDTO.sellable, + itemDTO.sellAccountId, + ); + if (itemDTO.costAccountId) { + await this.validators.validateItemCostAccountExistance( + itemDTO.costAccountId, + ); + } + // Validate the cost account id existance if the item is purchasable. + this.validators.validateCostAccountExistance( + itemDTO.purchasable, + itemDTO.costAccountId, + ); + if (itemDTO.inventoryAccountId) { + await this.validators.validateItemInventoryAccountExistance( + itemDTO.inventoryAccountId, + ); + } + if (itemDTO.purchaseTaxRateId) { + await this.validators.validatePurchaseTaxRateExistance( + itemDTO.purchaseTaxRateId, + ); + } + if (itemDTO.sellTaxRateId) { + await this.validators.validateSellTaxRateExistance(itemDTO.sellTaxRateId); + } + } + + /** + * Transforms the edit item DTO to model. + * @param {IItemDTO} itemDTO - Item DTO. + * @param {Item} oldItem - Old item. + * @return {Partial} + */ + private transformEditItemDTOToModel( + itemDTO: IItemDTO, + oldItem: Item, + ): Partial { + return { + ...itemDTO, + ...(itemDTO.type === 'inventory' && oldItem.type !== 'inventory' + ? { + quantityOnHand: 0, + } + : {}), + }; + } + + /** + * Edits the item metadata. + * @param {number} itemId + * @param {IItemDTO} itemDTO + */ + public async editItem( + itemId: number, + itemDTO: IItemDTO, + trx?: Knex.Transaction, + ): Promise { + // Validates the given item existance on the storage. + const oldItem = await this.itemModel + .query() + .findById(itemId) + .throwIfNotFound(); + + // Authorize before editing item. + await this.authorize(itemDTO, oldItem); + + // Transform the edit item DTO to model. + const itemModel = this.transformEditItemDTOToModel(itemDTO, oldItem); + + // Edits the item with associated transactions under unit-of-work environment. + return this.uow.withTransaction(async (trx: Knex.Transaction) => { + // Updates the item on the storage and fetches the updated one. + const newItem = await this.itemModel + .query(trx) + .patchAndFetchById(itemId, itemModel); + + // Edit event payload. + const eventPayload: IItemEventEditedPayload = { + item: newItem, + oldItem, + itemId: newItem.id, + trx, + }; + // Triggers `onItemEdited` event. + await this.eventEmitter.emitAsync(events.item.onEdited, eventPayload); + + return newItem; + }, trx); + } +} diff --git a/packages/server-nest/src/modules/Items/Item.controller.ts b/packages/server-nest/src/modules/Items/Item.controller.ts new file mode 100644 index 000000000..1641835ed --- /dev/null +++ b/packages/server-nest/src/modules/Items/Item.controller.ts @@ -0,0 +1,44 @@ +import { + Body, + Controller, + Delete, + Param, + Post, + UsePipes, + UseInterceptors, + UseGuards, +} from '@nestjs/common'; +import { ZodValidationPipe } from 'src/common/pipes/ZodValidation.pipe'; +import { createItemSchema } from './Item.schema'; +import { CreateItemService } from './CreateItem.service'; +import { Item } from './models/Item'; +import { DeleteItemService } from './DeleteItem.service'; +import { TenantController } from '../Tenancy/Tenant.controller'; +import { SubscriptionGuard } from '../Subscription/interceptors/Subscription.guard'; +import { ClsService } from 'nestjs-cls'; +import { PublicRoute } from '../Auth/Jwt.guard'; + +@Controller('/items') +@UseGuards(SubscriptionGuard) +@PublicRoute() +export class ItemsController extends TenantController { + constructor( + private readonly createItemService: CreateItemService, + private readonly deleteItemService: DeleteItemService, + private readonly cls: ClsService, + ) { + super(); + } + + @Post() + @UsePipes(new ZodValidationPipe(createItemSchema)) + async createItem(@Body() createItemDto: any): Promise { + return this.createItemService.createItem(createItemDto); + } + + @Delete(':id') + async deleteItem(@Param('id') id: string): Promise { + const itemId = parseInt(id, 10); + return this.deleteItemService.deleteItem(itemId); + } +} diff --git a/packages/server-nest/src/modules/Items/Item.schema.ts b/packages/server-nest/src/modules/Items/Item.schema.ts new file mode 100644 index 000000000..24cb3f149 --- /dev/null +++ b/packages/server-nest/src/modules/Items/Item.schema.ts @@ -0,0 +1,111 @@ +import { DATATYPES_LENGTH } from 'src/constants/data-types'; +import z from 'zod'; + +export const createItemSchema = z + .object({ + name: z.string().max(DATATYPES_LENGTH.STRING), + type: z.enum(['service', 'non-inventory', 'inventory']), + code: z.string().max(DATATYPES_LENGTH.STRING).nullable().optional(), + purchasable: z.boolean().optional(), + cost_price: z + .number() + .min(0) + .max(DATATYPES_LENGTH.DECIMAL_13_3) + .nullable() + .optional(), + cost_account_id: z + .number() + .int() + .min(0) + .max(DATATYPES_LENGTH.INT_10) + .nullable() + .optional(), + sellable: z.boolean().optional(), + sell_price: z + .number() + .min(0) + .max(DATATYPES_LENGTH.DECIMAL_13_3) + .nullable() + .optional(), + sell_account_id: z + .number() + .int() + .min(0) + .max(DATATYPES_LENGTH.INT_10) + .nullable() + .optional(), + inventory_account_id: z + .number() + .int() + .min(0) + .max(DATATYPES_LENGTH.INT_10) + .nullable() + .optional(), + sell_description: z + .string() + .max(DATATYPES_LENGTH.TEXT) + .nullable() + .optional(), + purchase_description: z + .string() + .max(DATATYPES_LENGTH.TEXT) + .nullable() + .optional(), + sell_tax_rate_id: z.number().int().nullable().optional(), + purchase_tax_rate_id: z.number().int().nullable().optional(), + category_id: z + .number() + .int() + .min(0) + .max(DATATYPES_LENGTH.INT_10) + .nullable() + .optional(), + note: z.string().max(DATATYPES_LENGTH.TEXT).optional(), + active: z.boolean().optional(), + media_ids: z.array(z.number().int()).optional(), + }) + .refine( + (data) => { + if (data.purchasable) { + return ( + data.cost_price !== undefined && data.cost_account_id !== undefined + ); + } + return true; + }, + { + message: + 'Cost price and cost account ID are required when item is purchasable', + path: ['cost_price', 'cost_account_id'], + }, + ) + .refine( + (data) => { + if (data.sellable) { + return ( + data.sell_price !== undefined && data.sell_account_id !== undefined + ); + } + return true; + }, + { + message: + 'Sell price and sell account ID are required when item is sellable', + path: ['sell_price', 'sell_account_id'], + }, + ) + .refine( + (data) => { + if (data.type === 'inventory') { + return data.inventory_account_id !== undefined; + } + return true; + }, + { + message: 'Inventory account ID is required for inventory items', + path: ['inventory_account_id'], + }, + ); + + +export type createItemDTO = z.infer; \ No newline at end of file diff --git a/packages/server-nest/src/modules/Items/ItemValidator.service.ts b/packages/server-nest/src/modules/Items/ItemValidator.service.ts new file mode 100644 index 000000000..942c2a791 --- /dev/null +++ b/packages/server-nest/src/modules/Items/ItemValidator.service.ts @@ -0,0 +1,285 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + ACCOUNT_PARENT_TYPE, + ACCOUNT_ROOT_TYPE, + ACCOUNT_TYPE, +} from 'src/constants/accounts'; +import { ServiceError } from './ServiceError'; +import { IItem, IItemDTO } from 'src/interfaces/Item'; +import { ERRORS } from './Items.constants'; +import { Item } from './models/Item'; +import { Account } from '../Accounts/models/Account'; + +@Injectable() +export class ItemsValidators { + constructor( + @Inject(Item.name) private itemModel: typeof Item, + @Inject(Account.name) private accountModel: typeof Account, + @Inject(Item.name) private taxRateModel: typeof Item, + @Inject(Item.name) private itemEntryModel: typeof Item, + @Inject(Item.name) private itemCategoryModel: typeof Item, + @Inject(Item.name) private accountTransactionModel: typeof Item, + @Inject(Item.name) private inventoryAdjustmentEntryModel: typeof Item, + ) {} + + /** + * Validate wether the given item name already exists on the storage. + * @param {string} itemName + * @param {number} notItemId + * @return {Promise} + */ + public async validateItemNameUniquiness( + itemName: string, + notItemId?: number, + ): Promise { + const foundItems = await this.itemModel.query().onBuild((builder: any) => { + builder.where('name', itemName); + if (notItemId) { + builder.whereNot('id', notItemId); + } + }); + + if (foundItems.length > 0) { + throw new ServiceError( + ERRORS.ITEM_NAME_EXISTS, + 'The item name is already exist.', + ); + } + } + + /** + * Validate item COGS account existance and type. + * @param {number} costAccountId + * @return {Promise} + */ + public async validateItemCostAccountExistance( + costAccountId: number, + ): Promise { + const foundAccount = await this.accountModel + .query() + .findById(costAccountId); + + if (!foundAccount) { + throw new ServiceError(ERRORS.COST_ACCOUNT_NOT_FOUMD); + + // Detarmines the cost of goods sold account. + } else if (!foundAccount.isParentType(ACCOUNT_PARENT_TYPE.EXPENSE)) { + throw new ServiceError(ERRORS.COST_ACCOUNT_NOT_COGS); + } + } + + /** + * Validate item sell account existance and type. + * @param {number} sellAccountId - Sell account id. + */ + public async validateItemSellAccountExistance(sellAccountId: number) { + const foundAccount = await this.accountModel + .query() + .findById(sellAccountId); + + if (!foundAccount) { + throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_FOUND); + } else if (!foundAccount.isParentType(ACCOUNT_ROOT_TYPE.INCOME)) { + throw new ServiceError(ERRORS.SELL_ACCOUNT_NOT_INCOME); + } + } + + /** + * Validates income account existance. + * @param {number|null} sellable - Detarmines if the item sellable. + * @param {number|null} incomeAccountId - Income account id. + * @throws {ServiceError(ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM)} + */ + public validateIncomeAccountExistance( + sellable?: boolean, + incomeAccountId?: number, + ) { + if (sellable && !incomeAccountId) { + throw new ServiceError( + ERRORS.INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM, + 'Income account is require with sellable item.', + ); + } + } + + /** + * Validates the cost account existance. + * @param {boolean|null} purchasable - Detarmines if the item purchasble. + * @param {number|null} costAccountId - Cost account id. + * @throws {ServiceError(ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM)} + */ + public validateCostAccountExistance( + purchasable: boolean, + costAccountId?: number, + ) { + if (purchasable && !costAccountId) { + throw new ServiceError( + ERRORS.COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM, + 'The cost account is required with purchasable item.', + ); + } + } + + /** + * Validate item inventory account existance and type. + * @param {number} inventoryAccountId + */ + public async validateItemInventoryAccountExistance( + inventoryAccountId: number, + ) { + const foundAccount = await this.accountModel + .query() + .findById(inventoryAccountId); + + if (!foundAccount) { + throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_FOUND); + } else if (!foundAccount.isAccountType(ACCOUNT_TYPE.INVENTORY)) { + throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_NOT_INVENTORY); + } + } + + /** + * Validate item category existance. + * @param {number} itemCategoryId + */ + public async validateItemCategoryExistance(itemCategoryId: number) { + const foundCategory = await this.itemCategoryModel + .query() + .findById(itemCategoryId); + + if (!foundCategory) { + throw new ServiceError(ERRORS.ITEM_CATEOGRY_NOT_FOUND); + } + } + + /** + * Validates the given item or items have no associated invoices or bills. + * @param {number|number[]} itemId - Item id. + * @throws {ServiceError} + */ + public async validateHasNoInvoicesOrBills(itemId: number[] | number) { + const ids = Array.isArray(itemId) ? itemId : [itemId]; + const foundItemEntries = await this.itemEntryModel + .query() + .whereIn('item_id', ids); + + if (foundItemEntries.length > 0) { + throw new ServiceError( + ids.length > 1 + ? ERRORS.ITEMS_HAVE_ASSOCIATED_TRANSACTIONS + : ERRORS.ITEM_HAS_ASSOCIATED_TRANSACTINS, + ); + } + } + + /** + * Validates the given item has no associated inventory adjustment transactions. + * @param {number} itemId - + */ + public async validateHasNoInventoryAdjustments( + itemId: number[] | number, + ): Promise { + const itemsIds = Array.isArray(itemId) ? itemId : [itemId]; + const inventoryAdjEntries = await this.inventoryAdjustmentEntryModel + .query() + .whereIn('item_id', itemsIds); + + if (inventoryAdjEntries.length > 0) { + throw new ServiceError(ERRORS.ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT); + } + } + + /** + * Validates edit item type from service/non-inventory to inventory. + * Should item has no any relations with accounts transactions. + * @param {number} itemId - Item id. + */ + public async validateEditItemTypeToInventory( + oldItem: Item, + newItemDTO: IItemDTO, + ) { + // We have no problem in case the item type not modified. + if (newItemDTO.type === oldItem.type || oldItem.type === 'inventory') { + return; + } + // Retrieve all transactions that associated to the given item id. + const itemTransactionsCount = await this.accountTransactionModel + .query() + .where('item_id', oldItem.id) + .count('item_id', { as: 'transactions' }) + .first(); + + // @ts-ignore + if (itemTransactionsCount.transactions > 0) { + throw new ServiceError( + ERRORS.TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS, + ); + } + } + + /** + * Validate the item inventory account whether modified and item + * has associated inventory transactions. + * @param {Item} oldItem + * @param {IItemDTO} newItemDTO + * @returns + */ + async validateItemInvnetoryAccountModified( + oldItem: Item, + newItemDTO: IItemDTO, + ) { + if ( + newItemDTO.type !== 'inventory' || + oldItem.inventoryAccountId === newItemDTO.inventoryAccountId + ) { + return; + } + // Inventory transactions associated to the given item id. + const transactions = await this.accountTransactionModel.query().where({ + itemId: oldItem.id, + }); + // Throw the service error in case item has associated inventory transactions. + if (transactions.length > 0) { + throw new ServiceError(ERRORS.INVENTORY_ACCOUNT_CANNOT_MODIFIED); + } + } + + /** + * Validate edit item type from inventory to another type that not allowed. + * @param {IItemDTO} itemDTO - Item DTO. + * @param {IItem} oldItem - Old item. + */ + public validateEditItemFromInventory(itemDTO: IItemDTO, oldItem: Item) { + if ( + itemDTO.type && + oldItem.type === 'inventory' && + itemDTO.type !== oldItem.type + ) { + throw new ServiceError(ERRORS.ITEM_CANNOT_CHANGE_INVENTORY_TYPE); + } + } + + /** + * Validate the purchase tax rate id existance. + * @param {number} taxRateId - + */ + public async validatePurchaseTaxRateExistance(taxRateId: number) { + const foundTaxRate = await this.taxRateModel.query().findById(taxRateId); + + if (!foundTaxRate) { + throw new ServiceError(ERRORS.PURCHASE_TAX_RATE_NOT_FOUND); + } + } + + /** + * Validate the sell tax rate id existance. + * @param {number} taxRateId + */ + public async validateSellTaxRateExistance(taxRateId: number) { + const foundTaxRate = await this.taxRateModel.query().findById(taxRateId); + + if (!foundTaxRate) { + throw new ServiceError(ERRORS.SELL_TAX_RATE_NOT_FOUND); + } + } +} diff --git a/packages/server-nest/src/modules/Items/Items.constants.ts b/packages/server-nest/src/modules/Items/Items.constants.ts new file mode 100644 index 000000000..9b4089687 --- /dev/null +++ b/packages/server-nest/src/modules/Items/Items.constants.ts @@ -0,0 +1,141 @@ +export const ERRORS = { + NOT_FOUND: 'NOT_FOUND', + ITEMS_NOT_FOUND: 'ITEMS_NOT_FOUND', + + ITEM_NAME_EXISTS: 'ITEM_NAME_EXISTS', + ITEM_CATEOGRY_NOT_FOUND: 'ITEM_CATEOGRY_NOT_FOUND', + COST_ACCOUNT_NOT_COGS: 'COST_ACCOUNT_NOT_COGS', + COST_ACCOUNT_NOT_FOUMD: 'COST_ACCOUNT_NOT_FOUMD', + SELL_ACCOUNT_NOT_FOUND: 'SELL_ACCOUNT_NOT_FOUND', + SELL_ACCOUNT_NOT_INCOME: 'SELL_ACCOUNT_NOT_INCOME', + + INVENTORY_ACCOUNT_NOT_FOUND: 'INVENTORY_ACCOUNT_NOT_FOUND', + INVENTORY_ACCOUNT_NOT_INVENTORY: 'INVENTORY_ACCOUNT_NOT_INVENTORY', + + ITEMS_HAVE_ASSOCIATED_TRANSACTIONS: 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS', + ITEM_HAS_ASSOCIATED_TRANSACTINS: 'ITEM_HAS_ASSOCIATED_TRANSACTINS', + + ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT: + 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT', + ITEM_CANNOT_CHANGE_INVENTORY_TYPE: 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE', + TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: + 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS', + INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED', + + ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS', + + PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND', + SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND', + + INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM: + 'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM', + COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM: + 'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM', +}; + +export const DEFAULT_VIEW_COLUMNS = []; +export const DEFAULT_VIEWS = [ + { + name: 'Services', + slug: 'services', + rolesLogicExpression: '1', + roles: [ + { index: 1, fieldKey: 'type', comparator: 'equals', value: 'service' }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'Inventory', + slug: 'inventory', + rolesLogicExpression: '1', + roles: [ + { index: 1, fieldKey: 'type', comparator: 'equals', value: 'inventory' }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, + { + name: 'Non Inventory', + slug: 'non-inventory', + rolesLogicExpression: '1', + roles: [ + { + index: 1, + fieldKey: 'type', + comparator: 'equals', + value: 'non-inventory', + }, + ], + columns: DEFAULT_VIEW_COLUMNS, + }, +]; + +export const ItemsSampleData = [ + { + 'Item Type': 'Inventory', + 'Item Name': 'Hettinger, Schumm and Bartoletti', + 'Item Code': '1000', + Sellable: 'T', + Purchasable: 'T', + 'Cost Price': '10000', + 'Sell Price': '1000', + 'Cost Account': 'Cost of Goods Sold', + 'Sell Account': 'Other Income', + 'Inventory Account': 'Inventory Asset', + 'Sell Description': 'Description ....', + 'Purchase Description': 'Description ....', + Category: 'sdafasdfsadf', + Note: 'At dolor est non tempore et quisquam.', + Active: 'TRUE', + }, + { + 'Item Type': 'Inventory', + 'Item Name': 'Schmitt Group', + 'Item Code': '1001', + Sellable: 'T', + Purchasable: 'T', + 'Cost Price': '10000', + 'Sell Price': '1000', + 'Cost Account': 'Cost of Goods Sold', + 'Sell Account': 'Other Income', + 'Inventory Account': 'Inventory Asset', + 'Sell Description': 'Description ....', + 'Purchase Description': 'Description ....', + Category: 'sdafasdfsadf', + Note: 'Id perspiciatis at adipisci minus accusamus dolor iure dolore.', + Active: 'TRUE', + }, + { + 'Item Type': 'Inventory', + 'Item Name': 'Marks - Carroll', + 'Item Code': '1002', + Sellable: 'T', + Purchasable: 'T', + 'Cost Price': '10000', + 'Sell Price': '1000', + 'Cost Account': 'Cost of Goods Sold', + 'Sell Account': 'Other Income', + 'Inventory Account': 'Inventory Asset', + 'Sell Description': 'Description ....', + 'Purchase Description': 'Description ....', + Category: 'sdafasdfsadf', + Note: 'Odio odio minus similique.', + Active: 'TRUE', + }, + { + 'Item Type': 'Inventory', + 'Item Name': 'VonRueden, Ruecker and Hettinger', + 'Item Code': '1003', + Sellable: 'T', + Purchasable: 'T', + 'Cost Price': '10000', + 'Sell Price': '1000', + 'Cost Account': 'Cost of Goods Sold', + 'Sell Account': 'Other Income', + 'Inventory Account': 'Inventory Asset', + 'Sell Description': 'Description ....', + 'Purchase Description': 'Description ....', + Category: 'sdafasdfsadf', + Note: 'Quibusdam dolores illo.', + Active: 'TRUE', + }, +]; diff --git a/packages/server-nest/src/modules/Items/Items.module.ts b/packages/server-nest/src/modules/Items/Items.module.ts new file mode 100644 index 000000000..68713d6fb --- /dev/null +++ b/packages/server-nest/src/modules/Items/Items.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { ItemsController } from './Item.controller'; +import { CreateItemService } from './CreateItem.service'; +import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module'; +import { ItemsValidators } from './ItemValidator.service'; +import { DeleteItemService } from './DeleteItem.service'; +import { TenancyContext } from '../Tenancy/TenancyContext.service'; + +@Module({ + imports: [TenancyDatabaseModule], + controllers: [ItemsController], + providers: [ + ItemsValidators, + CreateItemService, + DeleteItemService, + TenancyContext, + ], +}) +export class ItemsModule {} diff --git a/packages/server-nest/src/modules/Items/ServiceError.ts b/packages/server-nest/src/modules/Items/ServiceError.ts new file mode 100644 index 000000000..1c2efd9bc --- /dev/null +++ b/packages/server-nest/src/modules/Items/ServiceError.ts @@ -0,0 +1,13 @@ + +export class ServiceError { + errorType: string; + message: string; + payload: any; + + constructor(errorType: string, message?: string, payload?: any) { + this.errorType = errorType; + this.message = message || null; + + this.payload = payload; + } + } \ No newline at end of file diff --git a/packages/server-nest/src/modules/Items/events/ItemCreated.event.ts b/packages/server-nest/src/modules/Items/events/ItemCreated.event.ts new file mode 100644 index 000000000..1b8312143 --- /dev/null +++ b/packages/server-nest/src/modules/Items/events/ItemCreated.event.ts @@ -0,0 +1,4 @@ +export class ItemCreatedEvent { + name: string; + description: string; +} diff --git a/packages/server-nest/src/modules/Items/listeners/ItemCreated.listener.ts b/packages/server-nest/src/modules/Items/listeners/ItemCreated.listener.ts new file mode 100644 index 000000000..679f80b97 --- /dev/null +++ b/packages/server-nest/src/modules/Items/listeners/ItemCreated.listener.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { ItemCreatedEvent } from '../events/ItemCreated.event'; + +@Injectable() +export class ItemCreatedListener { + + @OnEvent('order.created') + handleItemCreatedEvent(event: ItemCreatedEvent) { + // handle and process "OrderCreatedEvent" event + console.log(event); + } +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/Items/models/Item.ts b/packages/server-nest/src/modules/Items/models/Item.ts new file mode 100644 index 000000000..226c13dc9 --- /dev/null +++ b/packages/server-nest/src/modules/Items/models/Item.ts @@ -0,0 +1,26 @@ +import * as F from 'fp-ts/function'; +import * as R from 'ramda'; +import { SearchableModel } from '@/modules/Search/SearchableMdel'; +import { TenantModel } from '@/modules/System/models/TenantModel'; + +const Extend = R.compose(SearchableModel)(TenantModel); + +export class Item extends Extend { + public readonly quantityOnHand: number; + public readonly name: string; + public readonly active: boolean; + public readonly type: string; + public readonly code: string; + public readonly sellable: boolean; + public readonly purchasable: boolean; + public readonly costPrice: number; + public readonly sellPrice: number; + public readonly currencyCode: string; + public readonly costAccountId: number; + public readonly inventoryAccountId: number; + public readonly categoryId: number; + + static get tableName() { + return 'items'; + } +} diff --git a/packages/server-nest/src/modules/Search/SearchableMdel.ts b/packages/server-nest/src/modules/Search/SearchableMdel.ts new file mode 100644 index 000000000..238316bce --- /dev/null +++ b/packages/server-nest/src/modules/Search/SearchableMdel.ts @@ -0,0 +1,22 @@ +import * as O from 'objection'; +import { IModelMeta, } from '@/interfaces/Model'; + +export const SearchableModel: O.Plugin = (Model) => + // @ts-ignore + class extends Model { + additionalProperty: string; + + /** + * Searchable model. + */ + static get searchable(): IModelMeta { + throw true; + } + + /** + * Search roles. + */ + // static get searchRoles(): ISearchRole[] { + // return []; + // } + }; \ No newline at end of file diff --git a/packages/server-nest/src/modules/Settings/ModelSettings.ts b/packages/server-nest/src/modules/Settings/ModelSettings.ts new file mode 100644 index 000000000..7029c748d --- /dev/null +++ b/packages/server-nest/src/modules/Settings/ModelSettings.ts @@ -0,0 +1,77 @@ +import { get } from 'lodash'; +import { + IModelMeta, + IModelMetaField, + IModelMetaDefaultSort, +} from '@/interfaces/Model'; + +const defaultModelMeta = { + fields: {}, + fields2: {}, +}; + +export const ModelSettings = (Model) => + class extends Model { + /** + * + * @returns {IModelMeta} + */ + static get meta(): IModelMeta { + throw new Error(''); + } + + /** + * Parsed meta merged with default emta. + * @returns {IModelMeta} + */ + static get parsedMeta(): IModelMeta { + return { + ...defaultModelMeta, + ...this.meta, + }; + } + + /** + * Retrieve specific model field meta of the given field key. + * @param {string} key + * @returns {IModelMetaField} + */ + public static getField(key: string, attribute?: string): IModelMetaField { + const field = get(this.meta.fields, key); + + return attribute ? get(field, attribute) : field; + } + + /** + * Retrieves the specific model meta. + * @param {string} key + * @returns + */ + public static getMeta(key?: string) { + return key ? get(this.parsedMeta, key) : this.parsedMeta; + } + + /** + * Retrieve the model meta fields. + * @return {{ [key: string]: IModelMetaField }} + */ + public static get fields(): { [key: string]: IModelMetaField } { + return this.getMeta('fields'); + } + + /** + * Retrieve the model default sort settings. + * @return {IModelMetaDefaultSort} + */ + public static get defaultSort(): IModelMetaDefaultSort { + return this.getMeta('defaultSort'); + } + + /** + * Retrieve the default filter field key. + * @return {string} + */ + public static get defaultFilterField(): string { + return this.getMeta('defaultFilterField'); + } + }; diff --git a/packages/server-nest/src/modules/Subscription/SubscriptionPeriod.ts b/packages/server-nest/src/modules/Subscription/SubscriptionPeriod.ts new file mode 100644 index 000000000..0cdf46804 --- /dev/null +++ b/packages/server-nest/src/modules/Subscription/SubscriptionPeriod.ts @@ -0,0 +1,49 @@ +import moment, { unitOfTime } from 'moment'; + +export class SubscriptionPeriod { + private start: Date; + private end: Date; + private interval: string; + private count: number; + + /** + * Constructor method. + * @param {string} interval - + * @param {number} count - + * @param {Date} start - + */ + constructor( + interval: unitOfTime.DurationConstructor = 'month', + count: number, + start?: Date + ) { + this.interval = interval; + this.count = count; + this.start = start; + + if (!start) { + this.start = moment().toDate(); + } + if (count === Infinity) { + this.end = null; + } else { + this.end = moment(start).add(count, interval).toDate(); + } + } + + getStartDate() { + return this.start; + } + + getEndDate() { + return this.end; + } + + getInterval() { + return this.interval; + } + + getIntervalCount() { + return this.count; + } +} \ No newline at end of file diff --git a/packages/server-nest/src/modules/Subscription/interceptors/Subscription.guard.ts b/packages/server-nest/src/modules/Subscription/interceptors/Subscription.guard.ts new file mode 100644 index 000000000..8d67ca8c2 --- /dev/null +++ b/packages/server-nest/src/modules/Subscription/interceptors/Subscription.guard.ts @@ -0,0 +1,46 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + Inject, + UnauthorizedException, +} from '@nestjs/common'; +import { PlanSubscription } from '../models/PlanSubscription'; +import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; + +@Injectable() +export class SubscriptionGuard implements CanActivate { + constructor( + @Inject(PlanSubscription.name) + private readonly planSubscriptionModel: typeof PlanSubscription, + private readonly tenancyContext: TenancyContext, + ) {} + + /** + * Validates the tenant's subscription is exists and not inactive + * @param {ExecutionContext} context + * @param {string} subscriptionSlug + * @returns {Promise} + */ + async canActivate( + context: ExecutionContext, + subscriptionSlug: string = 'main', // Default value + ): Promise { + const tenant = await this.tenancyContext.getTenant(); + const subscription = await this.planSubscriptionModel + .query() + .findOne('slug', subscriptionSlug) + .where('tenant_id', tenant.id); + + if (!subscription) { + throw new UnauthorizedException('Tenant has no subscription.'); + } + + const isSubscriptionInactive = subscription.inactive(); + + if (isSubscriptionInactive) { + throw new UnauthorizedException('Organization subscription is inactive.'); + } + return true; + } +} diff --git a/packages/server-nest/src/modules/Subscription/models/PlanSubscription.ts b/packages/server-nest/src/modules/Subscription/models/PlanSubscription.ts new file mode 100644 index 000000000..f71953914 --- /dev/null +++ b/packages/server-nest/src/modules/Subscription/models/PlanSubscription.ts @@ -0,0 +1,246 @@ +import { Model, mixin } from 'objection'; +import moment from 'moment'; +import { SubscriptionPeriod } from '../SubscriptionPeriod'; +import { SystemModel } from '@/modules/System/models/SystemModel'; +import { SubscriptionPaymentStatus } from '@/interfaces/SubscriptionPlan'; + +export class PlanSubscription extends mixin(SystemModel) { + public readonly lemonSubscriptionId: number; + public readonly endsAt: Date; + public readonly startsAt: Date; + public readonly canceledAt: Date; + public readonly trialEndsAt: Date; + public readonly paymentStatus: SubscriptionPaymentStatus; + + /** + * Table name. + */ + static get tableName() { + return 'subscription_plan_subscriptions'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return ['createdAt', 'updatedAt']; + } + + /** + * Defined virtual attributes. + */ + static get virtualAttributes() { + return [ + 'active', + 'inactive', + 'ended', + 'canceled', + 'onTrial', + 'status', + 'isPaymentFailed', + 'isPaymentSucceed', + ]; + } + + /** + * Modifiers queries. + */ + static get modifiers() { + return { + activeSubscriptions(builder) { + const dateFormat = 'YYYY-MM-DD HH:mm:ss'; + const now = moment().format(dateFormat); + + builder.where('ends_at', '>', now); + builder.where('trial_ends_at', '>', now); + }, + + inactiveSubscriptions(builder) { + builder.modify('endedTrial'); + builder.modify('endedPeriod'); + }, + + subscriptionBySlug(builder, subscriptionSlug) { + builder.where('slug', subscriptionSlug); + }, + + endedTrial(builder) { + const dateFormat = 'YYYY-MM-DD HH:mm:ss'; + const endDate = moment().format(dateFormat); + + builder.where('ends_at', '<=', endDate); + }, + + endedPeriod(builder) { + const dateFormat = 'YYYY-MM-DD HH:mm:ss'; + const endDate = moment().format(dateFormat); + + builder.where('trial_ends_at', '<=', endDate); + }, + + /** + * Filter the failed payment. + * @param builder + */ + failedPayment(builder) { + builder.where('payment_status', SubscriptionPaymentStatus.Failed); + }, + + /** + * Filter the succeed payment. + * @param builder + */ + succeedPayment(builder) { + builder.where('payment_status', SubscriptionPaymentStatus.Succeed); + }, + }; + } + + /** + * Relations mappings. + */ + static get relationMappings() { + const Tenant = require('system/models/Tenant'); + const Plan = require('system/models/Subscriptions/Plan'); + + return { + /** + * Plan subscription belongs to tenant. + */ + tenant: { + relation: Model.BelongsToOneRelation, + modelClass: Tenant.default, + join: { + from: 'subscription_plan_subscriptions.tenantId', + to: 'tenants.id', + }, + }, + + /** + * Plan description belongs to plan. + */ + plan: { + relation: Model.BelongsToOneRelation, + modelClass: Plan.default, + join: { + from: 'subscription_plan_subscriptions.planId', + to: 'subscription_plans.id', + }, + }, + }; + } + + /** + * Check if the subscription is active. + * Crtiria should be active: + * - During the trial period should NOT be canceled. + * - Out of trial period should NOT be ended. + * @return {Boolean} + */ + public active() { + return this.onTrial() ? !this.canceled() : !this.ended(); + } + + /** + * Check if the subscription is inactive. + * @return {Boolean} + */ + public inactive() { + return !this.active(); + } + + /** + * Check if paid subscription period has ended. + * @return {Boolean} + */ + public ended() { + return this.endsAt ? moment().isAfter(this.endsAt) : false; + } + + /** + * Check if the paid subscription has started. + * @returns {Boolean} + */ + public started() { + return this.startsAt ? moment().isAfter(this.startsAt) : false; + } + + /** + * Check if subscription is currently on trial. + * @return {Boolean} + */ + public onTrial() { + return this.trialEndsAt ? moment().isBefore(this.trialEndsAt) : false; + } + + /** + * Check if the subscription is canceled. + * @returns {boolean} + */ + public canceled() { + return !!this.canceledAt; + } + + /** + * Retrieves the subscription status. + * @returns {string} + */ + public status() { + return this.canceled() + ? 'canceled' + : this.onTrial() + ? 'on_trial' + : this.active() + ? 'active' + : 'inactive'; + } + + /** + * Set new period from the given details. + * @param {string} invoiceInterval + * @param {number} invoicePeriod + * @param {string} start + * + * @return {Object} + */ + static setNewPeriod(invoiceInterval: any, invoicePeriod: any, start?: any) { + const period = new SubscriptionPeriod( + invoiceInterval, + invoicePeriod, + start, + ); + + const startsAt = period.getStartDate(); + const endsAt = period.getEndDate(); + + return { startsAt, endsAt }; + } + + /** + * Renews subscription period. + * @Promise + */ + renew(invoiceInterval, invoicePeriod) { + const { startsAt, endsAt } = PlanSubscription.setNewPeriod( + invoiceInterval, + invoicePeriod, + ); + return this.$query().update({ startsAt, endsAt }); + } + + /** + * Detarmines the subscription payment whether is failed. + * @returns {boolean} + */ + public isPaymentFailed() { + return this.paymentStatus === SubscriptionPaymentStatus.Failed; + } + + /** + * Detarmines the subscription payment whether is succeed. + * @returns {boolean} + */ + public isPaymentSucceed() { + return this.paymentStatus === SubscriptionPaymentStatus.Succeed; + } +} diff --git a/packages/server-nest/src/modules/System/SystemDB/SystemDB.constants.ts b/packages/server-nest/src/modules/System/SystemDB/SystemDB.constants.ts new file mode 100644 index 000000000..7365644a6 --- /dev/null +++ b/packages/server-nest/src/modules/System/SystemDB/SystemDB.constants.ts @@ -0,0 +1,2 @@ +export const SystemKnexConnection ='SystemKnexConnection'; +export const SystemKnexConnectionConfigure = 'SystemKnexConnectionConfigure'; \ No newline at end of file diff --git a/packages/server-nest/src/modules/System/SystemDB/SystemDB.controller.ts b/packages/server-nest/src/modules/System/SystemDB/SystemDB.controller.ts new file mode 100644 index 000000000..ca010fd99 --- /dev/null +++ b/packages/server-nest/src/modules/System/SystemDB/SystemDB.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get, Post } from '@nestjs/common'; + +@Controller('/system_db') +export class SystemDatabaseController { + constructor() {} + + @Post() + @Get() + ping(){ + + } +} diff --git a/packages/server-nest/src/modules/System/SystemDB/SystemDB.module.ts b/packages/server-nest/src/modules/System/SystemDB/SystemDB.module.ts new file mode 100644 index 000000000..2c9ade2c4 --- /dev/null +++ b/packages/server-nest/src/modules/System/SystemDB/SystemDB.module.ts @@ -0,0 +1,49 @@ +import Knex from 'knex'; +import { Global, Module } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { + SystemKnexConnection, + SystemKnexConnectionConfigure, +} from './SystemDB.constants'; +import { SystemDatabaseController } from './SystemDB.controller'; +import { knexSnakeCaseMappers } from 'objection'; + +const providers = [ + { + provide: SystemKnexConnectionConfigure, + inject: [ConfigService], + useFactory: (configService: ConfigService) => ({ + client: configService.get('systemDatabase.client'), + connection: { + host: configService.get('systemDatabase.host'), + user: configService.get('systemDatabase.user'), + password: configService.get('systemDatabase.password'), + database: configService.get('systemDatabase.databaseName'), + charset: 'utf8', + }, + migrations: { + directory: configService.get('systemDatabase.migrationDir'), + }, + seeds: { + directory: configService.get('systemDatabase.seedsDir'), + }, + pool: { min: 0, max: 7 }, + ...knexSnakeCaseMappers({ upperCase: true }), + }), + }, + { + provide: SystemKnexConnection, + inject: [SystemKnexConnectionConfigure], + useFactory: (knexConfig) => { + return Knex(knexConfig); + }, + }, +]; + +@Global() +@Module({ + providers: [...providers], + exports: [...providers], + controllers: [SystemDatabaseController], +}) +export class SystemDatabaseModule {} diff --git a/packages/server-nest/src/modules/System/SystemModels/SystemModels.constants.ts b/packages/server-nest/src/modules/System/SystemModels/SystemModels.constants.ts new file mode 100644 index 000000000..d270c008a --- /dev/null +++ b/packages/server-nest/src/modules/System/SystemModels/SystemModels.constants.ts @@ -0,0 +1 @@ +export const SystemModelsConnection = 'SystemModelsConnection'; \ No newline at end of file diff --git a/packages/server-nest/src/modules/System/SystemModels/SystemModels.module.ts b/packages/server-nest/src/modules/System/SystemModels/SystemModels.module.ts new file mode 100644 index 000000000..02f5742f8 --- /dev/null +++ b/packages/server-nest/src/modules/System/SystemModels/SystemModels.module.ts @@ -0,0 +1,34 @@ +import { Knex } from 'knex'; +import { Model } from 'objection'; +import { Global, Module } from '@nestjs/common'; +import { PlanSubscription } from '@/modules/Subscription/models/PlanSubscription'; +import { TenantModel } from '@/modules/System/models/TenantModel'; +import { SystemKnexConnection } from '../SystemDB/SystemDB.constants'; +import { SystemModelsConnection } from './SystemModels.constants'; +import { SystemUser } from '../models/SystemUser'; + +const models = [SystemUser, PlanSubscription, TenantModel]; +const modelProviders = models.map((model) => { + return { + provide: model.name, + useValue: model, + }; +}); + +const providers = [ + ...modelProviders, + { + provide: SystemModelsConnection, + inject: [SystemKnexConnection], + useFactory: async (systemKnex: Knex) => { + Model.knex(systemKnex); + }, + }, +]; + +@Global() +@Module({ + providers: [...providers], + exports: [...providers], +}) +export class SystemModelsModule {} diff --git a/packages/server-nest/src/modules/System/models/SystemModel.ts b/packages/server-nest/src/modules/System/models/SystemModel.ts new file mode 100644 index 000000000..b074eb366 --- /dev/null +++ b/packages/server-nest/src/modules/System/models/SystemModel.ts @@ -0,0 +1,3 @@ +import { BaseModel } from 'src/models/Model'; + +export class SystemModel extends BaseModel {} diff --git a/packages/server-nest/src/modules/System/models/SystemUser.ts b/packages/server-nest/src/modules/System/models/SystemUser.ts new file mode 100644 index 000000000..cf3417d0a --- /dev/null +++ b/packages/server-nest/src/modules/System/models/SystemUser.ts @@ -0,0 +1,14 @@ +import { BaseModel } from 'src/models/Model'; + +export class SystemUser extends BaseModel { + public readonly firstName: string; + public readonly lastName: string; + public readonly active: boolean; + public readonly password: string; + public readonly email: string; + public readonly tenantId: number; + + static get tableName() { + return 'users'; + } +} diff --git a/packages/server-nest/src/modules/System/models/TenantModel.ts b/packages/server-nest/src/modules/System/models/TenantModel.ts new file mode 100644 index 000000000..6a82e8a89 --- /dev/null +++ b/packages/server-nest/src/modules/System/models/TenantModel.ts @@ -0,0 +1,12 @@ +import { BaseModel } from 'src/models/Model'; + +export class TenantModel extends BaseModel { + public readonly organizationId: string; + public readonly initializedAt: string; + public readonly seededAt: boolean; + public readonly builtAt: string; + + static get tableName() { + return 'tenants'; + } +} diff --git a/packages/server-nest/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts b/packages/server-nest/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts new file mode 100644 index 000000000..587094cd9 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts @@ -0,0 +1,32 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { TenancyContext } from './TenancyContext.service'; + +@Injectable() +export class EnsureTenantIsInitializedGuard implements CanActivate { + constructor(private readonly tenancyContext: TenancyContext) {} + + /** + * Validate the tenant of the current request is initialized.. + * @param {ExecutionContext} context + * @returns {Promise} + */ + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const tenant = await this.tenancyContext.getTenant(); + + if (!tenant?.initializedAt) { + throw new UnauthorizedException({ + statusCode: 400, + error: 'Bad Request', + message: 'Tenant database is not migrated with application schema yet.', + errors: [{ type: 'TENANT.DATABASE.NOT.INITALIZED' }], + }); + } + return true; + } +} diff --git a/packages/server-nest/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts b/packages/server-nest/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts new file mode 100644 index 000000000..725aaf2bf --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts @@ -0,0 +1,31 @@ +import { + CanActivate, + ExecutionContext, + Inject, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { TenancyContext } from './TenancyContext.service'; + +@Injectable() +export class EnsureTenantIsSeededGuard implements CanActivate { + constructor(private readonly tenancyContext: TenancyContext) {} + + /** + * Validate the tenant of the current request is seeded. + * @param {ExecutionContext} context + * @returns {Promise} + */ + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const tenant = await this.tenancyContext.getTenant(); + + if (!tenant.seededAt) { + throw new UnauthorizedException({ + message: 'Tenant database is not seeded with initial data yet.', + errors: [{ type: 'TENANT.DATABASE.NOT.SEED' }], + }); + } + return true; + } +} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyCache/TenancyCache.module.ts b/packages/server-nest/src/modules/Tenancy/TenancyCache/TenancyCache.module.ts new file mode 100644 index 000000000..d5df8d863 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyCache/TenancyCache.module.ts @@ -0,0 +1,18 @@ +import type { RedisClientOptions } from 'redis'; +import { DynamicModule, Module } from '@nestjs/common'; +import { CacheModule } from '@nestjs/cache-manager'; + +interface TenancyCacheModuleConfig { + tenantId: number; +} + +@Module({}) +export class TenancyCacheModule { + static register(config: TenancyCacheModuleConfig): DynamicModule { + return { + module: TenancyCacheModule, + imports: [CacheModule.register({})], + }; + } +} + diff --git a/packages/server-nest/src/modules/Tenancy/TenancyContext.service.ts b/packages/server-nest/src/modules/Tenancy/TenancyContext.service.ts new file mode 100644 index 000000000..91a5e987e --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyContext.service.ts @@ -0,0 +1,39 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import { SystemUser } from '../System/models/SystemUser'; +import { TenantModel } from '../System/models/TenantModel'; + +@Injectable() +export class TenancyContext { + constructor( + private readonly cls: ClsService, + + @Inject(SystemUser.name) + private readonly systemUserModel: typeof SystemUser, + + @Inject(TenantModel.name) + private readonly systemTenantModel: typeof TenantModel, + ) {} + + /** + * Get the current tenant. + * @returns + */ + getTenant() { + // Get the tenant from the request headers. + const organizationId = this.cls.get('organizationId'); + + return this.systemTenantModel.query().findOne({ organizationId }); + } + + /** + * + * @returns + */ + getSystemUser() { + // Get the user from the request headers. + const userId = this.cls.get('userId'); + + return this.systemUserModel.query().findOne({ id: userId }); + } +} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.constants.ts b/packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.constants.ts new file mode 100644 index 000000000..2981627bd --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.constants.ts @@ -0,0 +1 @@ +export const TENANCY_DB_CONNECTION = 'TENANCY_DB_CONNECTION'; diff --git a/packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.module.ts b/packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.module.ts new file mode 100644 index 000000000..e752a0891 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyDB/TenancyDB.module.ts @@ -0,0 +1,47 @@ +import knex from 'knex'; +import { Global, Module, Scope } from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; +import { TENANCY_DB_CONNECTION } from './TenancyDB.constants'; +import { UnitOfWork } from './UnitOfWork.service'; +import { knexSnakeCaseMappers } from 'objection'; +import { ClsService } from 'nestjs-cls'; + +const connectionFactory = { + provide: TENANCY_DB_CONNECTION, + scope: Scope.REQUEST, + useFactory: async ( + request: Request, + configService: ConfigService, + cls: ClsService, + ) => { + const organizationId = cls.get('organizationId'); + + return knex({ + client: configService.get('tenantDatabase.client'), + connection: { + host: configService.get('tenantDatabase.host'), + user: configService.get('tenantDatabase.user'), + password: configService.get('tenantDatabase.password'), + database: `bigcapital_tenant_${organizationId}`, + charset: 'utf8', + }, + migrations: { + directory: configService.get('tenantDatabase.migrationDir'), + }, + seeds: { + directory: configService.get('tenantDatabase.seedsDir'), + }, + pool: { min: 0, max: 7 }, + ...knexSnakeCaseMappers({ upperCase: true }), + }); + }, + inject: [REQUEST, ConfigService, ClsService], +}; + +@Global() +@Module({ + providers: [connectionFactory, UnitOfWork], + exports: [TENANCY_DB_CONNECTION, UnitOfWork], +}) +export class TenancyDatabaseModule {} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyDB/TransactionsHooks.ts b/packages/server-nest/src/modules/Tenancy/TenancyDB/TransactionsHooks.ts new file mode 100644 index 000000000..95d5b96f7 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyDB/TransactionsHooks.ts @@ -0,0 +1,58 @@ +/** + * Enumeration that represents transaction isolation levels for use with the {@link Transactional} annotation + */ +export enum IsolationLevel { + /** + * A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur. + */ + READ_UNCOMMITTED = 'read uncommitted', + /** + * A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur. + */ + READ_COMMITTED = 'read committed', + /** + * A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur. + */ + REPEATABLE_READ = 'repeatable read', + /** + * A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented. + */ + SERIALIZABLE = 'serializable', +} + +/** + * @param {any} maybeTrx + * @returns {maybeTrx is import('objection').TransactionOrKnex & { executionPromise: Promise }} + */ +function checkIsTransaction(maybeTrx) { + return Boolean(maybeTrx && maybeTrx.executionPromise); +} + +/** + * Wait for a transaction to be complete. + * @param {import('objection').TransactionOrKnex} [trx] + */ +export async function waitForTransaction(trx) { + return Promise.resolve(checkIsTransaction(trx) ? trx.executionPromise : null); +} + +/** + * Run a callback when the transaction is done. + * @param {import('objection').TransactionOrKnex | undefined} trx + * @param {Function} callback + */ +export function runAfterTransaction(trx, callback) { + waitForTransaction(trx).then( + () => { + // If transaction success, then run action + return Promise.resolve(callback()).catch((error) => { + setTimeout(() => { + throw error; + }); + }); + }, + () => { + // Ignore transaction error + }, + ); +} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyDB/UnitOfWork.service.ts b/packages/server-nest/src/modules/Tenancy/TenancyDB/UnitOfWork.service.ts new file mode 100644 index 000000000..135a43429 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyDB/UnitOfWork.service.ts @@ -0,0 +1,46 @@ +import { Transaction } from 'objection'; +import { Knex } from 'knex'; +import { Inject, Injectable } from '@nestjs/common'; +import { IsolationLevel } from './TransactionsHooks'; +import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants'; + +@Injectable() +export class UnitOfWork { + constructor( + @Inject(TENANCY_DB_CONNECTION) + private readonly tenantKex: Knex, + ) {} + + /** + * + * @param {number} tenantId + * @param {} work + * @param {IsolationLevel} isolationLevel + * @returns {} + */ + public withTransaction = async ( + work: (knex: Knex.Transaction) => Promise | T, + trx?: Transaction, + isolationLevel: IsolationLevel = IsolationLevel.READ_UNCOMMITTED, + ): Promise => { + const knex = this.tenantKex; + let _trx = trx; + + if (!_trx) { + _trx = await knex.transaction({ isolationLevel }); + } + try { + const result = await work(_trx); + + if (!trx) { + _trx.commit(); + } + return result; + } catch (error) { + if (!trx) { + _trx.rollback(); + } + throw error; + } + }; +} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyGlobal.middleware.ts b/packages/server-nest/src/modules/Tenancy/TenancyGlobal.middleware.ts new file mode 100644 index 000000000..db9558bb5 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyGlobal.middleware.ts @@ -0,0 +1,25 @@ +import { + Injectable, + NestMiddleware, + UnauthorizedException, +} from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import { ClsService, ClsServiceManager } from 'nestjs-cls'; + +export class TenancyGlobalMiddleware implements NestMiddleware { + constructor(private readonly cls: ClsService) {} + /** + * Validates the organization ID in the request headers. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + public use(req: Request, res: Response, next: NextFunction) { + const organizationId = req.headers['organization-id']; + + if (!organizationId) { + throw new UnauthorizedException('Organization ID is required.'); + } + next(); + } +} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyIdCls.interceptor.ts b/packages/server-nest/src/modules/Tenancy/TenancyIdCls.interceptor.ts new file mode 100644 index 000000000..86474d2a6 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyIdCls.interceptor.ts @@ -0,0 +1,23 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { ClsService } from 'nestjs-cls'; +import { Observable } from 'rxjs'; + +@Injectable() +export class TenancyIdClsInterceptor implements NestInterceptor { + constructor(private readonly cls: ClsService) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const organizationId = request.headers['organization-id']; + // this.cls.get('organizationId'); + + // console.log(organizationId, 'organizationId22'); + + return next.handle(); + } +} diff --git a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.constants.ts b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.constants.ts new file mode 100644 index 000000000..3b33a5a7a --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.constants.ts @@ -0,0 +1 @@ +export const TenancyModelsConnection = 'TenancyModelsConnection'; diff --git a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts new file mode 100644 index 000000000..6cad22635 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts @@ -0,0 +1,24 @@ +import { Knex } from 'knex'; +import { Global, Module, Scope } from '@nestjs/common'; +import { TENANCY_DB_CONNECTION } from '../TenancyDB/TenancyDB.constants'; +import { Item } from '../../../modules/Items/models/Item'; +import { Account } from '@/modules/Accounts/models/Account'; + +const models = [Item, Account]; +const modelProviders = models.map((model) => { + return { + provide: model.name, + inject: [TENANCY_DB_CONNECTION], + scope: Scope.REQUEST, + useFactory: async (tenantKnex: Knex) => { + return model.bindKnex(tenantKnex); + }, + }; +}); + +@Global() +@Module({ + providers: [...modelProviders], + exports: [...modelProviders], +}) +export class TenancyModelsModule {} diff --git a/packages/server-nest/src/modules/Tenancy/Tenant.controller.ts b/packages/server-nest/src/modules/Tenancy/Tenant.controller.ts new file mode 100644 index 000000000..c85918e30 --- /dev/null +++ b/packages/server-nest/src/modules/Tenancy/Tenant.controller.ts @@ -0,0 +1,7 @@ +import { UseGuards } from '@nestjs/common'; +import { EnsureTenantIsSeededGuard } from '../Tenancy/EnsureTenantIsSeeded.guards'; +import { EnsureTenantIsInitializedGuard } from '../Tenancy/EnsureTenantIsInitialized.guard'; + +@UseGuards(EnsureTenantIsInitializedGuard) +@UseGuards(EnsureTenantIsSeededGuard) +export class TenantController {} diff --git a/packages/server-nest/test/app.e2e-spec.ts b/packages/server-nest/test/app.e2e-spec.ts new file mode 100644 index 000000000..a9147ca2c --- /dev/null +++ b/packages/server-nest/test/app.e2e-spec.ts @@ -0,0 +1,24 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from '../src/modules/App/App.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/packages/server-nest/test/jest-e2e.json b/packages/server-nest/test/jest-e2e.json new file mode 100644 index 000000000..e9d912f3e --- /dev/null +++ b/packages/server-nest/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/packages/server-nest/tsconfig.build.json b/packages/server-nest/tsconfig.build.json new file mode 100644 index 000000000..64f86c6bd --- /dev/null +++ b/packages/server-nest/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/packages/server-nest/tsconfig.json b/packages/server-nest/tsconfig.json new file mode 100644 index 000000000..7e1ddff21 --- /dev/null +++ b/packages/server-nest/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + "paths": { + "@/*": ["src/*"], + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34598c1cd..3d2689f0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -482,6 +482,190 @@ importers: specifier: ^1.0.0 version: 1.0.0 + packages/server-nest: + dependencies: + '@nestjs/bull': + specifier: ^10.2.1 + version: 10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(bull@4.16.4) + '@nestjs/bullmq': + specifier: ^10.2.1 + version: 10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(bullmq@5.25.6) + '@nestjs/cache-manager': + specifier: ^2.2.2 + version: 2.3.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(cache-manager@6.1.3)(rxjs@7.8.1) + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^3.2.3 + version: 3.3.0(@nestjs/common@10.4.7)(rxjs@7.8.1) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/event-emitter': + specifier: ^2.0.4 + version: 2.1.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.4.7) + '@nestjs/passport': + specifier: ^10.0.3 + version: 10.0.3(@nestjs/common@10.4.7)(passport@0.7.0) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.4.7(@nestjs/common@10.4.7)(@nestjs/core@10.4.7) + '@nestjs/swagger': + specifier: ^7.4.2 + version: 7.4.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + '@nestjs/throttler': + specifier: ^6.2.1 + version: 6.2.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2) + '@types/passport-local': + specifier: ^1.0.38 + version: 1.0.38 + '@types/ramda': + specifier: ^0.30.2 + version: 0.30.2 + bull: + specifier: ^4.16.3 + version: 4.16.4 + bullmq: + specifier: ^5.21.1 + version: 5.25.6 + cache-manager: + specifier: ^6.1.1 + version: 6.1.3 + cache-manager-redis-store: + specifier: ^3.0.1 + version: 3.0.1 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + express-validator: + specifier: ^7.2.0 + version: 7.2.0 + fp-ts: + specifier: ^2.16.9 + version: 2.16.9 + knex: + specifier: ^3.1.0 + version: 3.1.0(mysql2@3.11.4)(mysql@2.18.1) + lamda: + specifier: ^0.4.1 + version: 0.4.1 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + moment: + specifier: ^2.30.1 + version: 2.30.1 + mysql: + specifier: ^2.18.1 + version: 2.18.1 + mysql2: + specifier: ^3.11.3 + version: 3.11.4 + nestjs-cls: + specifier: ^4.4.1 + version: 4.4.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + nestjs-i18n: + specifier: ^10.4.9 + version: 10.5.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(class-validator@0.14.1)(rxjs@7.8.1) + objection: + specifier: ^3.1.5 + version: 3.1.5(knex@3.1.0) + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + passport-local: + specifier: ^1.0.0 + version: 1.0.0 + ramda: + specifier: ^0.30.1 + version: 0.30.1 + redis: + specifier: ^4.7.0 + version: 4.7.0 + reflect-metadata: + specifier: ^0.2.0 + version: 0.2.2 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.4.7(esbuild@0.23.1) + '@nestjs/schematics': + specifier: ^10.0.0 + version: 10.2.3(chokidar@3.6.0)(typescript@5.6.3) + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.4.7(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7) + '@types/express': + specifier: ^5.0.0 + version: 5.0.0 + '@types/jest': + specifier: ^29.5.2 + version: 29.5.14 + '@types/node': + specifier: ^20.3.1 + version: 20.5.1 + '@types/supertest': + specifier: ^6.0.0 + version: 6.0.2 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.11.0(@typescript-eslint/parser@8.11.0)(eslint@9.13.0)(typescript@5.6.3) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.11.0(eslint@9.13.0)(typescript@5.6.3) + eslint: + specifier: ^9.0.0 + version: 9.13.0 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.1.0(eslint@9.13.0) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.2.1(eslint-config-prettier@9.1.0)(eslint@9.13.0)(prettier@3.3.3) + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + prettier: + specifier: ^3.0.0 + version: 3.3.3 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.0.0 + ts-jest: + specifier: ^29.1.0 + version: 29.2.5(@babel/core@7.26.0)(esbuild@0.23.1)(jest@29.7.0)(typescript@5.6.3) + ts-loader: + specifier: ^9.4.3 + version: 9.5.1(typescript@5.6.3)(webpack@5.91.0) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.5.1)(typescript@5.6.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.1.3 + version: 5.6.3 + packages/webapp: dependencies: '@bigcapital/pdf-templates': @@ -1088,6 +1272,52 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + /@angular-devkit/core@17.3.11(chokidar@3.6.0): + resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + chokidar: 3.6.0 + jsonc-parser: 3.2.1 + picomatch: 4.0.1 + rxjs: 7.8.1 + source-map: 0.7.4 + dev: true + + /@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0): + resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + ansi-colors: 4.1.3 + inquirer: 9.2.15 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - chokidar + dev: true + + /@angular-devkit/schematics@17.3.11(chokidar@3.6.0): + resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + jsonc-parser: 3.2.1 + magic-string: 0.30.8 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + dev: true + /@apideck/better-ajv-errors@0.3.6(ajv@8.13.0): resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} engines: {node: '>=10'} @@ -2488,7 +2718,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-plugin-utils': 7.25.9 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.5) dev: false @@ -2582,7 +2812,6 @@ packages: dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.24.5 - dev: false /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.5): resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} @@ -6172,6 +6401,10 @@ packages: warning: 4.0.3 dev: false + /@ioredis/commands@1.2.0: + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -6211,7 +6444,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 @@ -6223,13 +6456,25 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 jest-message-util: 28.1.3 jest-util: 28.1.3 slash: 3.0.0 dev: false + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + dev: true + /@jest/core@24.9.0: resolution: {integrity: sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==} engines: {node: '>= 6'} @@ -6282,7 +6527,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 @@ -6313,6 +6558,49 @@ packages: - utf-8-validate dev: false + /@jest/core@29.7.0(ts-node@10.9.2): + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.7 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /@jest/environment@24.9.0: resolution: {integrity: sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==} engines: {node: '>= 6'} @@ -6331,10 +6619,37 @@ packages: dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 jest-mock: 27.5.1 dev: false + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + jest-mock: 29.7.0 + dev: true + + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + dev: true + + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/fake-timers@24.9.0: resolution: {integrity: sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==} engines: {node: '>= 6'} @@ -6352,12 +6667,24 @@ packages: dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 - '@types/node': 14.18.63 + '@types/node': 20.5.1 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 dev: false + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.5.1 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + /@jest/globals@27.5.1: resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -6367,6 +6694,18 @@ packages: expect: 27.5.1 dev: false + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/reporters@24.9.0: resolution: {integrity: sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==} engines: {node: '>= 6'} @@ -6412,7 +6751,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -6436,6 +6775,43 @@ packages: - supports-color dev: false + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.5.1 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /@jest/schemas@28.1.3: resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -6468,6 +6844,15 @@ packages: source-map: 0.6.1 dev: false + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + /@jest/test-result@24.9.0: resolution: {integrity: sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==} engines: {node: '>= 6'} @@ -6497,6 +6882,16 @@ packages: collect-v8-coverage: 1.0.2 dev: false + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + dev: true + /@jest/test-sequencer@24.9.0: resolution: {integrity: sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==} engines: {node: '>= 6'} @@ -6523,6 +6918,16 @@ packages: - supports-color dev: false + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + dev: true + /@jest/transform@24.9.0: resolution: {integrity: sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==} engines: {node: '>= 6'} @@ -6618,7 +7023,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 14.18.63 + '@types/node': 20.5.1 '@types/yargs': 15.0.19 chalk: 4.1.2 dev: false @@ -6629,7 +7034,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 14.18.63 + '@types/node': 20.5.1 '@types/yargs': 16.0.9 chalk: 4.1.2 @@ -6640,7 +7045,7 @@ packages: '@jest/schemas': 28.1.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 14.18.63 + '@types/node': 20.5.1 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: false @@ -6652,7 +7057,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 14.18.63 + '@types/node': 20.5.1 '@types/yargs': 17.0.32 chalk: 4.1.2 dev: true @@ -6721,6 +7126,12 @@ packages: /@juggle/resize-observer@3.4.0: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} + /@keyv/serialize@1.0.1: + resolution: {integrity: sha512-kKXeynfORDGPUEEl2PvTExM2zs+IldC6ZD8jPcfvI351MDNtfMlw9V9s4XZXuJNDK2qR5gbEKxRyoYx3quHUVQ==} + dependencies: + buffer: 6.0.3 + dev: false + /@leichtgewicht/ip-codec@2.0.5: resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} dev: false @@ -6846,6 +7257,17 @@ packages: npmlog: 6.0.2 dev: true + /@ljharb/through@2.3.13: + resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + dev: true + + /@lukeed/csprng@1.1.0: + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + /@mdx-js/react@2.3.0(react@18.3.1): resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} peerDependencies: @@ -6914,6 +7336,54 @@ packages: glob-to-regexp: 0.3.0 dev: false + /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3: + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3: + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3: + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3: + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3: + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3: + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@ndelangen/get-tarball@3.0.9: resolution: {integrity: sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==} dependencies: @@ -6922,6 +7392,304 @@ packages: tar-fs: 2.1.1 dev: true + /@nestjs/bull-shared@10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7): + resolution: {integrity: sha512-bMIEILYYovQWfdz6fCSTgqb/zuKyGmNSc7guB56MiZVW84JloUHb8330nNh3VWaamJKGtUzawbEoG2VR3uVeOg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + tslib: 2.8.0 + dev: false + + /@nestjs/bull@10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(bull@4.16.4): + resolution: {integrity: sha512-5UW1X82C03EUm4DH/Cj+zeN2YdcfWUJnNji4tIXHMJplYKLKQA3QXIiwtttLBDO0omYx5WL0MyteRc69CDjoNg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + bull: ^3.3 || ^4.0.0 + dependencies: + '@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7) + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + bull: 4.16.4 + tslib: 2.8.0 + dev: false + + /@nestjs/bullmq@10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(bullmq@5.25.6): + resolution: {integrity: sha512-1RXhR7+XK6uXaw9uNH5hP9bcW5Vzkpc4lX7t7sUC23N9XH2CMH6uUm0I14T5KkvMKkj0VXj0GY+Ulh3pCtdwbA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + bullmq: ^3.0.0 || ^4.0.0 || ^5.0.0 + dependencies: + '@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7) + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + bullmq: 5.25.6 + tslib: 2.8.0 + dev: false + + /@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(cache-manager@6.1.3)(rxjs@7.8.1): + resolution: {integrity: sha512-pxeBp9w/s99HaW2+pezM1P3fLiWmUEnTUoUMLa9UYViCtjj0E0A19W/vaT5JFACCzFIeNrwH4/16jkpAhQ25Vw==} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + cache-manager: <=5 + rxjs: ^7.0.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + cache-manager: 6.1.3 + rxjs: 7.8.1 + dev: false + + /@nestjs/cli@10.4.7(esbuild@0.23.1): + resolution: {integrity: sha512-4wJTtBJsbvjLIzXl+Qj6DYHv4J7abotuXyk7bes5erL79y+KBT61LulL56SqilzmNnHOAVbXcSXOn9S2aWUn6A==} + engines: {node: '>= 16.14'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) + '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.6.3) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.6.3)(webpack@5.96.1) + glob: 10.4.2 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.1.0 + typescript: 5.6.3 + webpack: 5.96.1(esbuild@0.23.1) + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - esbuild + - uglify-js + - webpack-cli + dev: true + + /@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1): + resolution: {integrity: sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 + iterare: 1.2.1 + reflect-metadata: 0.2.2 + rxjs: 7.8.1 + tslib: 2.7.0 + uid: 2.0.2 + + /@nestjs/config@3.3.0(@nestjs/common@10.4.7)(rxjs@7.8.1): + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + lodash: 4.17.21 + rxjs: 7.8.1 + dev: false + + /@nestjs/core@10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1): + resolution: {integrity: sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig==} + requiresBuild: true + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7)(@nestjs/core@10.4.7) + '@nuxtjs/opencollective': 0.3.2 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.1 + tslib: 2.7.0 + uid: 2.0.2 + transitivePeerDependencies: + - encoding + + /@nestjs/event-emitter@2.1.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7): + resolution: {integrity: sha512-6L6fBOZTyfFlL7Ih/JDdqlCzZeCW0RjCX28wnzGyg/ncv5F/EOeT1dfopQr1loBRQ3LTgu8OWM7n4zLN4xigsg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + eventemitter2: 6.4.9 + dev: false + + /@nestjs/jwt@10.2.0(@nestjs/common@10.4.7): + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + dev: false + + /@nestjs/mapped-types@2.0.5(@nestjs/common@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2): + resolution: {integrity: sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + class-transformer: 0.5.1 + class-validator: 0.14.1 + reflect-metadata: 0.2.2 + dev: false + + /@nestjs/passport@10.0.3(@nestjs/common@10.4.7)(passport@0.7.0): + resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + passport: 0.7.0 + dev: false + + /@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7)(@nestjs/core@10.4.7): + resolution: {integrity: sha512-q6XDOxZPTZ9cxALcVuKUlRBk+cVEv6dW2S8p2yVre22kpEQxq53/OI8EseDvzObGb6hepZ8+yBY04qoYqSlXNQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + body-parser: 1.20.3 + cors: 2.8.5 + express: 4.21.1 + multer: 1.4.4-lts.1 + tslib: 2.7.0 + transitivePeerDependencies: + - supports-color + + /@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.6.3): + resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} + peerDependencies: + typescript: '>=4.8.2' + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.6.3 + transitivePeerDependencies: + - chokidar + dev: true + + /@nestjs/swagger@7.4.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2): + resolution: {integrity: sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + class-transformer: 0.5.1 + class-validator: 0.14.1 + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.17.14 + dev: false + + /@nestjs/testing@10.4.7(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7): + resolution: {integrity: sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7)(@nestjs/core@10.4.7) + tslib: 2.7.0 + dev: true + + /@nestjs/throttler@6.2.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2): + resolution: {integrity: sha512-vdt6VjhKC6vcLBJRUb97IuR6Htykn5kokZzmT8+S5XFOLLjUF7rzRpr+nUOhK9pi1L0hhbzSf2v2FJl4v64EJA==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + reflect-metadata: 0.2.2 + dev: false + /@newrelic/native-metrics@10.1.1: resolution: {integrity: sha512-BvdTMAqS3d94ZwJ6u70dWqZVkX8ev3dybkxRInHMbKV2DE1koQR3nzH2ut3hf1MaRQh4SF6SpUNTUznzCZZtjw==} engines: {node: '>=16', npm: '>=6'} @@ -7101,6 +7869,17 @@ packages: - debug dev: true + /@nuxtjs/opencollective@0.3.2: + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + consola: 2.15.3 + node-fetch: 2.6.7 + transitivePeerDependencies: + - encoding + /@nx/devkit@19.0.7(nx@19.0.7): resolution: {integrity: sha512-yIIQHS1gl+dKLJXqzT7SN+Uo7wQeWT/fG9vbti4Lmc6b6gOV0vFOZiARHq3rDxNqHRCWjJbZlM9ME3GiN7UNkA==} peerDependencies: @@ -7346,6 +8125,11 @@ packages: requiresBuild: true optional: true + /@pkgr/core@0.1.1: + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dev: true + /@playwright/test@1.44.1: resolution: {integrity: sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==} engines: {node: '>=16'} @@ -8370,6 +9154,55 @@ packages: react: 18.3.1 dev: false + /@redis/bloom@1.2.0(@redis/client@1.6.0): + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.6.0 + dev: false + + /@redis/client@1.6.0: + resolution: {integrity: sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==} + engines: {node: '>=14'} + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + dev: false + + /@redis/graph@1.1.1(@redis/client@1.6.0): + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.6.0 + dev: false + + /@redis/json@1.0.7(@redis/client@1.6.0): + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.6.0 + dev: false + + /@redis/search@1.2.0(@redis/client@1.6.0): + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.6.0 + dev: false + + /@redis/time-series@1.1.0(@redis/client@1.6.0): + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + dependencies: + '@redis/client': 1.6.0 + dev: false + /@reduxjs/toolkit@1.9.7(react-redux@7.2.9)(react@18.3.1): resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==} peerDependencies: @@ -8735,6 +9568,18 @@ packages: dependencies: type-detect: 4.0.8 + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + dependencies: + '@sinonjs/commons': 3.0.1 + dev: true + /@sinonjs/fake-timers@8.1.0: resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} dependencies: @@ -11180,18 +12025,18 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 14.18.63 + '@types/node': 20.5.1 /@types/bonjour@3.5.13: resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/bson@4.0.5: resolution: {integrity: sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/chai@4.3.16: @@ -11201,20 +12046,20 @@ packages: /@types/concat-stream@1.6.1: resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/connect-history-api-fallback@1.5.4: resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} dependencies: '@types/express-serve-static-core': 4.19.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -11227,13 +12072,13 @@ packages: /@types/cors@2.8.17: resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/cross-spawn@6.0.6: resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: true /@types/detect-port@1.3.5: @@ -11293,7 +12138,15 @@ packages: /@types/express-serve-static-core@4.19.1: resolution: {integrity: sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 + '@types/qs': 6.9.15 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + + /@types/express-serve-static-core@5.0.1: + resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} + dependencies: + '@types/node': 20.5.1 '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -11306,6 +12159,14 @@ packages: '@types/qs': 6.9.15 '@types/serve-static': 1.15.7 + /@types/express@5.0.0: + resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 5.0.1 + '@types/qs': 6.9.15 + '@types/serve-static': 1.15.7 + /@types/find-cache-dir@3.2.1: resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} dev: true @@ -11313,19 +12174,19 @@ packages: /@types/form-data@0.0.33: resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 14.18.63 + '@types/node': 20.5.1 /@types/graceful-fs@4.1.9: resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 /@types/history@4.7.11: resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} @@ -11348,7 +12209,7 @@ packages: /@types/http-proxy@1.17.14: resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/i18n@0.8.8: @@ -11382,6 +12243,13 @@ packages: pretty-format: 26.6.2 dev: false + /@types/jest@29.5.14: + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + dev: true + /@types/js-cookie@2.2.5: resolution: {integrity: sha512-cpmwBRcHJmmZx0OGU7aPVwGWGbs4iKwVYchk9iuMtxNCA2zorwdaTz4GkLgs2WGxiRZRFKnV1k6tRUHX7tBMxg==} dev: false @@ -11396,10 +12264,16 @@ packages: /@types/json5@0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + /@types/jsonwebtoken@9.0.5: + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + dependencies: + '@types/node': 20.5.1 + dev: false + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/knex@0.16.1(mysql2@1.7.0)(mysql@2.18.1): @@ -11434,6 +12308,10 @@ packages: resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} dev: true + /@types/methods@1.1.4: + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + dev: true + /@types/mime-types@2.1.4: resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} dev: true @@ -11455,7 +12333,7 @@ packages: resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} dependencies: '@types/bson': 4.0.5 - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/multer@1.4.11: @@ -11467,14 +12345,14 @@ packages: /@types/node-fetch@2.6.11: resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 form-data: 4.0.0 dev: true /@types/node-forge@1.3.11: resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/node@10.17.60: @@ -11483,6 +12361,7 @@ packages: /@types/node@14.18.63: resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + dev: false /@types/node@16.18.115: resolution: {integrity: sha512-NF5ajYn+dq0tRfswdyp8Df75h7D9z+L8TCIwrXoh46ZLK6KZVXkRhf/luXaZytvm/keUo9vU4m1Bg39St91a5w==} @@ -11507,6 +12386,27 @@ packages: /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + /@types/passport-local@1.0.38: + resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==} + dependencies: + '@types/express': 5.0.0 + '@types/passport': 1.0.17 + '@types/passport-strategy': 0.2.38 + dev: false + + /@types/passport-strategy@0.2.38: + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + dependencies: + '@types/express': 5.0.0 + '@types/passport': 1.0.17 + dev: false + + /@types/passport@1.0.17: + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + dependencies: + '@types/express': 5.0.0 + dev: false + /@types/prettier@2.7.3: resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} dev: false @@ -11537,6 +12437,12 @@ packages: ts-toolbelt: 6.15.5 dev: false + /@types/ramda@0.30.2: + resolution: {integrity: sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==} + dependencies: + types-ramda: 0.30.1 + dev: false + /@types/range-parser@1.2.7: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -11596,13 +12502,13 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/retry@0.12.0: @@ -11616,7 +12522,7 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 14.18.63 + '@types/node': 20.5.1 /@types/serve-index@1.9.4: resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} @@ -11628,7 +12534,7 @@ packages: resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 14.18.63 + '@types/node': 20.5.1 '@types/send': 0.17.4 /@types/socket.io-client@3.0.0: @@ -11645,7 +12551,7 @@ packages: /@types/sockjs@0.3.36: resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/sortablejs@1.15.8: @@ -11658,7 +12564,6 @@ packages: /@types/stack-utils@2.0.3: resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - dev: false /@types/styled-components@5.1.34: resolution: {integrity: sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==} @@ -11678,7 +12583,23 @@ packages: resolution: {integrity: sha512-YIGelp3ZyMiH0/A09PMAORO0EBGlF5xIKfDpK74wdYvWUs2o96b5CItJcWPdH409b7SAXIIG6p8NdU/4U2Maww==} dependencies: '@types/cookiejar': 2.1.5 - '@types/node': 14.18.63 + '@types/node': 20.5.1 + dev: true + + /@types/superagent@8.1.9: + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 20.5.1 + form-data: 4.0.0 + dev: true + + /@types/supertest@6.0.2: + resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.9 dev: true /@types/testing-library__dom@6.14.0: @@ -11714,6 +12635,9 @@ packages: resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} dev: true + /@types/validator@13.12.2: + resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + /@types/webidl-conversions@7.0.3: resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} dev: false @@ -11727,14 +12651,14 @@ packages: /@types/whatwg-url@8.2.2: resolution: {integrity: sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 '@types/webidl-conversions': 7.0.3 dev: false /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false /@types/yargs-parser@21.0.3: @@ -11766,7 +12690,7 @@ packages: resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} requiresBuild: true dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 dev: false optional: true @@ -12724,6 +13648,10 @@ packages: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + /accept-language-parser@1.5.0: + resolution: {integrity: sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw==} + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -12834,6 +13762,12 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + /acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /add-dom-event-listener@1.1.0: resolution: {integrity: sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==} dependencies: @@ -12959,6 +13893,17 @@ packages: ajv: 6.12.6 dev: false + /ajv-formats@2.1.1(ajv@8.12.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + dev: true + /ajv-formats@2.1.1(ajv@8.13.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -12970,6 +13915,17 @@ packages: ajv: 8.13.0 dev: false + /ajv-formats@2.1.1(ajv@8.17.1): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.17.1 + dev: false + /ajv-formats@3.0.1(ajv@8.13.0): resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -13012,7 +13968,6 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 - dev: false /ajv@8.13.0: resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} @@ -13022,6 +13977,15 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 + /ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + dev: false + /ansi-align@2.0.0: resolution: {integrity: sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==} dependencies: @@ -13146,7 +14110,6 @@ packages: /append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - dev: false /append-transform@1.0.0: resolution: {integrity: sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==} @@ -13318,6 +14281,10 @@ packages: kind-of: 5.1.0 dev: false + /array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + dev: true + /array-union@1.0.2: resolution: {integrity: sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==} engines: {node: '>=0.10.0'} @@ -13588,6 +14555,11 @@ packages: resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} dev: false + /aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + dev: false + /aws4@1.13.0: resolution: {integrity: sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==} dev: false @@ -13694,6 +14666,24 @@ packages: - supports-color dev: false + /babel-jest@29.7.0(@babel/core@7.26.0): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /babel-loader@8.3.0(@babel/core@7.24.5)(webpack@5.91.0): resolution: {integrity: sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==} engines: {node: '>= 8.9'} @@ -13780,6 +14770,16 @@ packages: '@types/babel__traverse': 7.20.6 dev: false + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + dev: true + /babel-plugin-macros@2.8.0: resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} dependencies: @@ -13936,7 +14936,6 @@ packages: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) - dev: false /babel-preset-jest@24.9.0(@babel/core@7.24.5): resolution: {integrity: sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==} @@ -13971,6 +14970,17 @@ packages: babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.0) dev: false + /babel-preset-jest@29.6.3(@babel/core@7.26.0): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.0) + dev: true + /babel-preset-react-app@10.0.1: resolution: {integrity: sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==} dependencies: @@ -14245,6 +15255,25 @@ packages: transitivePeerDependencies: - supports-color + /body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9(supports-color@5.5.0) + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + /bonjour-service@1.2.1: resolution: {integrity: sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==} dependencies: @@ -14455,6 +15484,13 @@ packages: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -14521,6 +15557,13 @@ packages: base64-js: 1.5.1 ieee754: 1.2.1 + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -14540,6 +15583,35 @@ packages: semver: 7.6.2 dev: true + /bull@4.16.4: + resolution: {integrity: sha512-CF+nGsJyfsCC9MJL8hFxqXzbwq+jGBXhaz1j15G+5N/XtKIPFUUy5O1mfWWKbKunfuH/x+UV4NYRQDHSkjCOgA==} + engines: {node: '>=12'} + dependencies: + cron-parser: 4.9.0 + get-port: 5.1.1 + ioredis: 5.4.1 + lodash: 4.17.21 + msgpackr: 1.11.2 + semver: 7.6.2 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + dev: false + + /bullmq@5.25.6: + resolution: {integrity: sha512-jxpa/DB02V20CqBAgyqpQazT630CJm0r4fky8EchH3mcJAomRtKXLS6tRA0J8tb29BDGlr/LXhlUuZwdBJBSdA==} + dependencies: + cron-parser: 4.9.0 + ioredis: 5.4.1 + msgpackr: 1.11.2 + node-abort-controller: 3.1.1 + semver: 7.6.2 + tslib: 2.6.2 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + dev: false + /bundle-require@5.0.0(esbuild@0.23.1): resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -14555,7 +15627,6 @@ packages: engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - dev: false /byte-size@8.1.1: resolution: {integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==} @@ -14644,6 +15715,19 @@ packages: unset-value: 1.0.0 dev: false + /cache-manager-redis-store@3.0.1: + resolution: {integrity: sha512-o560kw+dFqusC9lQJhcm6L2F2fMKobJ5af+FoR2PdnMVdpQ3f3Bz6qzvObTGyvoazQJxjQNWgMQeChP4vRTuXQ==} + engines: {node: '>= 16.18.0'} + dependencies: + redis: 4.7.0 + dev: false + + /cache-manager@6.1.3: + resolution: {integrity: sha512-IcBseSv1GquLxlTb1nH5KhOQQwwOjMC5hkBras+8zTYD/bRSCgT9bIah1DZ+4eKc3vcqqYtfUCI5pYvOHmDXtw==} + dependencies: + keyv: 5.2.1 + dev: false + /caching-transform@3.0.2: resolution: {integrity: sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==} engines: {node: '>=6'} @@ -14709,7 +15793,6 @@ packages: /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - dev: false /camelize@1.0.0: resolution: {integrity: sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==} @@ -14845,6 +15928,11 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /change-case@4.1.2: resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} dependencies: @@ -14865,7 +15953,6 @@ packages: /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} - dev: false /char-regex@2.0.1: resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==} @@ -14980,7 +16067,9 @@ packages: /cjs-module-lexer@1.3.1: resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} - dev: false + + /class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} /class-utils@0.3.6: resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==} @@ -14992,6 +16081,13 @@ packages: static-extend: 0.1.2 dev: false + /class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + dependencies: + '@types/validator': 13.12.2 + libphonenumber-js: 1.11.1 + validator: 13.12.0 + /classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} dev: false @@ -15043,6 +16139,11 @@ packages: engines: {node: '>= 10'} dev: true + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + dev: true + /cliui@3.2.0: resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==} dependencies: @@ -15113,6 +16214,11 @@ packages: engines: {node: '>=6'} dev: false + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + dev: false + /cmd-shim@6.0.1: resolution: {integrity: sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -15138,7 +16244,6 @@ packages: /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: false /coa@2.0.2: resolution: {integrity: sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==} @@ -15165,7 +16270,6 @@ packages: /collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - dev: false /collection-map@1.0.0: resolution: {integrity: sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==} @@ -15273,7 +16377,6 @@ packages: /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - dev: false /commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} @@ -15294,6 +16397,17 @@ packages: engines: {node: '>= 12'} dev: false + /comment-json@4.2.5: + resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + engines: {node: '>= 6'} + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + dev: true + /common-path-prefix@3.0.0: resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} dev: false @@ -15409,6 +16523,9 @@ packages: engines: {node: '>=0.8'} dev: false + /consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + /consola@3.2.3: resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} @@ -15569,10 +16686,19 @@ packages: engines: {node: '>= 0.6'} dev: false + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + /cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + /cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + /cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} dev: true @@ -15634,7 +16760,6 @@ packages: dependencies: object-assign: 4.1.1 vary: 1.1.2 - dev: false /cosmiconfig-typescript-loader@4.4.0(@types/node@20.5.1)(cosmiconfig@8.3.6)(ts-node@10.9.2)(typescript@5.6.3): resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==} @@ -15781,6 +16906,25 @@ packages: sha.js: 2.4.11 dev: true + /create-jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -15796,6 +16940,13 @@ packages: luxon: 1.28.1 dev: false + /cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + dependencies: + luxon: 3.5.0 + dev: false + /cross-env@5.2.1: resolution: {integrity: sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==} engines: {node: '>=4.0'} @@ -16381,6 +17532,15 @@ packages: /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true + /deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} @@ -16460,7 +17620,6 @@ packages: /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: false /default-browser-id@3.0.0: resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} @@ -16575,6 +17734,11 @@ packages: engines: {node: '>=0.10'} dev: false + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -16623,6 +17787,13 @@ packages: engines: {node: '>=8'} dev: true + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + requiresBuild: true + dev: false + optional: true + /detect-newline@2.1.0: resolution: {integrity: sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==} engines: {node: '>=0.10.0'} @@ -16631,7 +17802,6 @@ packages: /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - dev: false /detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -16920,7 +18090,6 @@ packages: /dotenv-expand@10.0.0: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} - dev: true /dotenv-expand@5.1.0: resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} @@ -16946,6 +18115,11 @@ packages: engines: {node: '>=12'} dev: true + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false + /dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -17039,6 +18213,11 @@ packages: engines: {node: '>=12'} dev: false + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + dev: true + /emittery@0.8.1: resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==} engines: {node: '>=10'} @@ -17065,6 +18244,10 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + /encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} requiresBuild: true @@ -17102,7 +18285,7 @@ packages: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 14.18.63 + '@types/node': 20.5.1 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 @@ -17132,6 +18315,14 @@ packages: graceful-fs: 4.2.11 tapable: 2.2.1 + /enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + /enquirer@2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} engines: {node: '>=8.6'} @@ -17493,7 +18684,6 @@ packages: /escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} - dev: false /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -17553,6 +18743,15 @@ packages: eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0)(eslint-import-resolver-typescript@3.6.1)(eslint-import-resolver-webpack@0.11.1)(eslint@8.57.0) dev: true + /eslint-config-prettier@9.1.0(eslint@9.13.0): + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 9.13.0 + dev: true + /eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.26.0)(@babel/plugin-transform-react-jsx@7.23.4)(eslint@8.57.0)(jest@27.5.1)(typescript@4.9.5): resolution: {integrity: sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==} engines: {node: '>=14.0.0'} @@ -17862,6 +19061,27 @@ packages: object.fromentries: 2.0.8 dev: false + /eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0)(eslint@9.13.0)(prettier@3.3.3): + resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + dependencies: + eslint: 9.13.0 + eslint-config-prettier: 9.1.0(eslint@9.13.0) + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.2 + dev: true + /eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} engines: {node: '>=10'} @@ -18311,7 +19531,6 @@ packages: /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} - dev: false /expand-brackets@2.1.4(supports-color@5.5.0): resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==} @@ -18359,6 +19578,17 @@ packages: jest-message-util: 27.5.1 dev: false + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + dev: true + /exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} @@ -18393,6 +19623,14 @@ packages: validator: 13.12.0 dev: false + /express-validator@7.2.0: + resolution: {integrity: sha512-I2ByKD8panjtr8Y05l21Wph9xk7kk64UMyvJCl/fFM/3CTJq8isXYPLeKW/aZBCdb/LYNv63PwhY8khw8VWocA==} + engines: {node: '>= 8.0.0'} + dependencies: + lodash: 4.17.21 + validator: 13.12.0 + dev: false + /express@4.19.2: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} @@ -18431,6 +19669,44 @@ packages: transitivePeerDependencies: - supports-color + /express@4.21.1: + resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9(supports-color@5.5.0) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.10 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + /ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} dependencies: @@ -18539,6 +19815,10 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true + /fast-glob@2.2.7: resolution: {integrity: sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==} engines: {node: '>=4.0.0'} @@ -18584,6 +19864,10 @@ packages: resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} dev: false + /fast-uri@3.0.3: + resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} + dev: false + /fast-xml-parser@4.2.5: resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} hasBin: true @@ -18751,6 +20035,20 @@ packages: transitivePeerDependencies: - supports-color + /finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9(supports-color@5.5.0) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + /find-cache-dir@0.1.1: resolution: {integrity: sha512-Z9XSBoNE7xQiV6MSgPuCfyMokH2K7JdpRkOYE1+mu3d4BFJtx3GW+f6Bo4q8IX6rlf5MYbLBKW0pjl2cWdkm2A==} engines: {node: '>=0.10.0'} @@ -19053,6 +20351,29 @@ packages: webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false + /fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.96.1): + resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + dependencies: + '@babel/code-frame': 7.26.0 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.6.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.6.2 + tapable: 2.2.1 + typescript: 5.6.3 + webpack: 5.96.1(esbuild@0.23.1) + dev: true + /form-data@2.3.3: resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} engines: {node: '>= 0.12'} @@ -19097,6 +20418,14 @@ packages: qs: 6.12.1 dev: true + /formidable@3.5.2: + resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} + dependencies: + dezalgo: 1.0.4 + hexoid: 2.0.0 + once: 1.4.0 + dev: true + /formik@2.4.6(react@18.3.1): resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==} peerDependencies: @@ -19117,6 +20446,10 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + /fp-ts@2.16.9: + resolution: {integrity: sha512-+I2+FnVB+tVaxcYyQkHUq7ZdKScaBlX53A41mxQtpIccsfyv8PzdzP7fzp2AY832T4aoK6UZ5WRX/ebGd8uZuQ==} + dev: false + /frac@1.1.2: resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==} engines: {node: '>=0.8'} @@ -19147,7 +20480,6 @@ packages: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 - dev: false /fs-extra@11.1.1: resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} @@ -19217,7 +20549,6 @@ packages: /fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} - dev: false /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -19299,6 +20630,11 @@ packages: is-property: 1.0.2 dev: false + /generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + dev: false + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -19362,7 +20698,6 @@ packages: /get-port@5.1.1: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} - dev: true /get-stream@3.0.0: resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} @@ -19583,6 +20918,19 @@ packages: minipass: 7.1.2 path-scurry: 1.11.1 + /glob@10.4.2: + resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 3.1.2 + minimatch: 9.0.4 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + dev: true + /glob@7.1.2: resolution: {integrity: sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==} dependencies: @@ -19948,6 +21296,11 @@ packages: is-glob: 3.1.0 dev: false + /has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + dev: true + /has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: @@ -20097,6 +21450,11 @@ packages: engines: {node: '>=8'} dev: true + /hexoid@2.0.0: + resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} + engines: {node: '>=8'} + dev: true + /hide-powered-by@1.1.0: resolution: {integrity: sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==} engines: {node: '>=4.0.0'} @@ -20729,6 +22087,27 @@ packages: wrap-ansi: 6.2.0 dev: true + /inquirer@9.2.15: + resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} + engines: {node: '>=18'} + dependencies: + '@ljharb/through': 2.3.13 + ansi-escapes: 4.3.2 + chalk: 5.3.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + /internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -20777,6 +22156,23 @@ packages: engines: {node: '>=0.10.0'} dev: false + /ioredis@5.4.1: + resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==} + engines: {node: '>=12.22.0'} + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.7(supports-color@5.5.0) + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + /ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -21000,7 +22396,6 @@ packages: /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} - dev: false /is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} @@ -21410,6 +22805,19 @@ packages: transitivePeerDependencies: - supports-color + /istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.1 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + dev: true + /istanbul-lib-report@2.0.8: resolution: {integrity: sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==} engines: {node: '>=6'} @@ -21447,7 +22855,6 @@ packages: source-map: 0.6.1 transitivePeerDependencies: - supports-color - dev: false /istanbul-reports@2.2.7: resolution: {integrity: sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==} @@ -21462,6 +22869,10 @@ packages: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + /iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + /iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} dependencies: @@ -21512,6 +22923,15 @@ packages: throat: 6.0.2 dev: false + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + dev: true + /jest-circus@27.5.1: resolution: {integrity: sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -21519,7 +22939,7 @@ packages: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -21539,6 +22959,35 @@ packages: - supports-color dev: false + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-cli@24.9.0: resolution: {integrity: sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==} engines: {node: '>= 6'} @@ -21593,6 +23042,34 @@ packages: - utf-8-validate dev: false + /jest-cli@29.7.0(@types/node@20.5.1)(ts-node@10.9.2): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jest-config@24.9.0: resolution: {integrity: sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==} engines: {node: '>= 6'} @@ -21661,6 +23138,47 @@ packages: - utf-8-validate dev: false + /jest-config@29.7.0(@types/node@20.5.1)(ts-node@10.9.2): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + babel-jest: 29.7.0(@babel/core@7.26.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.7 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.6.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + /jest-diff@24.9.0: resolution: {integrity: sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==} engines: {node: '>= 6'} @@ -21715,6 +23233,13 @@ packages: detect-newline: 3.1.0 dev: false + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + dev: true + /jest-each@24.9.0: resolution: {integrity: sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==} engines: {node: '>= 6'} @@ -21739,6 +23264,17 @@ packages: pretty-format: 27.5.1 dev: false + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + dev: true + /jest-environment-jsdom-fourteen@1.0.1: resolution: {integrity: sha512-DojMX1sY+at5Ep+O9yME34CdidZnO3/zfPh8UW+918C5fIZET5vCjfkegixmsi7AtdYfkr4bPlIzmWnlvQkP7Q==} dependencies: @@ -21777,7 +23313,7 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.7.0 @@ -21808,11 +23344,23 @@ packages: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 dev: false + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + /jest-get-type@24.9.0: resolution: {integrity: sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==} engines: {node: '>= 6'} @@ -21860,7 +23408,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.9 - '@types/node': 14.18.63 + '@types/node': 20.5.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -21880,7 +23428,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 14.18.63 + '@types/node': 20.5.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -21925,7 +23473,7 @@ packages: '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 @@ -21958,6 +23506,14 @@ packages: pretty-format: 27.5.1 dev: false + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + /jest-matcher-utils@24.9.0: resolution: {integrity: sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==} engines: {node: '>= 6'} @@ -21978,6 +23534,16 @@ packages: pretty-format: 27.5.1 dev: false + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + /jest-message-util@24.9.0: resolution: {integrity: sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==} engines: {node: '>= 6'} @@ -22024,6 +23590,21 @@ packages: stack-utils: 2.0.6 dev: false + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.26.0 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.7 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + /jest-mock@24.9.0: resolution: {integrity: sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==} engines: {node: '>= 6'} @@ -22036,7 +23617,16 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 + + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + jest-util: 29.7.0 + dev: true /jest-pnp-resolver@1.2.3(jest-resolve@24.9.0): resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} @@ -22062,6 +23652,18 @@ packages: jest-resolve: 27.5.1 dev: false + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.7.0 + dev: true + /jest-regex-util@24.9.0: resolution: {integrity: sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==} engines: {node: '>= 6'} @@ -22104,6 +23706,16 @@ packages: - supports-color dev: false + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + /jest-resolve@24.9.0: resolution: {integrity: sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==} engines: {node: '>= 6'} @@ -22131,6 +23743,21 @@ packages: slash: 3.0.0 dev: false + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 + dev: true + /jest-runner@24.9.0: resolution: {integrity: sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==} engines: {node: '>= 6'} @@ -22169,7 +23796,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.11 @@ -22192,6 +23819,35 @@ packages: - utf-8-validate dev: false + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + dev: true + /jest-runtime@24.9.0: resolution: {integrity: sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==} engines: {node: '>= 6'} @@ -22256,6 +23912,36 @@ packages: - supports-color dev: false + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + chalk: 4.1.2 + cjs-module-lexer: 1.3.1 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + /jest-serializer@24.9.0: resolution: {integrity: sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==} engines: {node: '>= 6'} @@ -22265,7 +23951,7 @@ packages: resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 graceful-fs: 4.2.11 dev: false @@ -22320,6 +24006,34 @@ packages: - supports-color dev: false + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.0 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.26.0) + '@babel/types': 7.26.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + dev: true + /jest-util@24.9.0: resolution: {integrity: sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==} engines: {node: '>= 6'} @@ -22345,7 +24059,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -22357,7 +24071,7 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@jest/types': 28.1.3 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -22369,7 +24083,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 14.18.63 + '@types/node': 20.5.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -22400,6 +24114,18 @@ packages: pretty-format: 27.5.1 dev: false + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + dev: true + /jest-watch-typeahead@0.4.2: resolution: {integrity: sha512-f7VpLebTdaXs81rg/oj4Vg/ObZy2QtGzAmGLNsqUS5G5KtSN68tFcIsbvNODfNyQxU78g7D8x77o3bgfBTR+2Q==} dependencies: @@ -22451,7 +24177,7 @@ packages: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 14.18.63 + '@types/node': 20.5.1 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 @@ -22464,7 +24190,7 @@ packages: dependencies: '@jest/test-result': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 14.18.63 + '@types/node': 20.5.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -22472,6 +24198,20 @@ packages: string-length: 4.0.2 dev: false + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.5.1 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + dev: true + /jest-worker@24.9.0: resolution: {integrity: sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==} engines: {node: '>= 6'} @@ -22484,7 +24224,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 merge-stream: 2.0.0 supports-color: 7.2.0 dev: false @@ -22493,7 +24233,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -22501,7 +24241,7 @@ packages: resolution: {integrity: sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 merge-stream: 2.0.0 supports-color: 8.1.1 dev: false @@ -22510,7 +24250,7 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 14.18.63 + '@types/node': 20.5.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -22550,6 +24290,27 @@ packages: - utf-8-validate dev: false + /jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + /jiti@1.21.0: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true @@ -22827,6 +24588,14 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true + /jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: true + + /jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + dev: true + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -22878,6 +24647,22 @@ packages: semver: 5.7.2 dev: false + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.2 + dev: false + /jsprim@1.4.2: resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} engines: {node: '>=0.6.0'} @@ -22942,6 +24727,12 @@ packages: dependencies: json-buffer: 3.0.1 + /keyv@5.2.1: + resolution: {integrity: sha512-tpIgCaY02VCW2Pz0zAn4guyct+IeH6Mb5wZdOvpe4oqXeQOJO0C3Wo8fTnf7P3ZD83Vr9kghbkNmzG3lTOhy/A==} + dependencies: + '@keyv/serialize': 1.0.1 + dev: false + /kind-of@3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -23079,6 +24870,54 @@ packages: - supports-color dev: false + /knex@3.1.0(mysql2@3.11.4)(mysql@2.18.1): + resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} + engines: {node: '>=16'} + hasBin: true + peerDependencies: + better-sqlite3: '*' + mysql: '*' + mysql2: '*' + pg: '*' + pg-native: '*' + sqlite3: '*' + tedious: '*' + peerDependenciesMeta: + better-sqlite3: + optional: true + mysql: + optional: true + mysql2: + optional: true + pg: + optional: true + pg-native: + optional: true + sqlite3: + optional: true + tedious: + optional: true + dependencies: + colorette: 2.0.19 + commander: 10.0.1 + debug: 4.3.4 + escalade: 3.2.0 + esm: 3.2.25 + get-package-type: 0.1.0 + getopts: 2.3.0 + interpret: 2.2.0 + lodash: 4.17.21 + mysql: 2.18.1 + mysql2: 3.11.4 + pg-connection-string: 2.6.2 + rechoir: 0.8.0 + resolve-from: 5.0.0 + tarn: 3.0.2 + tildify: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} dev: false @@ -23087,6 +24926,10 @@ packages: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} dev: false + /lamda@0.4.1: + resolution: {integrity: sha512-0ITucytoxEWajbJAECVk12/yQ5NvpZg5RO50fSbXZ7/5eOcnNDTZ08jDp9OQk40M8flCN70MAmy1/nQ+9teOEA==} + dev: false + /language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} dev: false @@ -23322,7 +25165,6 @@ packages: /libphonenumber-js@1.11.1: resolution: {integrity: sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw==} - dev: false /liftoff@3.1.0: resolution: {integrity: sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==} @@ -23503,6 +25345,10 @@ packages: /lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + /lodash.flattendeep@4.4.0: resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} dev: true @@ -23515,6 +25361,10 @@ packages: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} dev: false + /lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + dev: false + /lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} dev: false @@ -23556,7 +25406,6 @@ packages: /lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - dev: false /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -23698,10 +25547,20 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + /lru.min@1.1.1: + resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + dev: false + /luxon@1.28.1: resolution: {integrity: sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==} dev: false + /luxon@3.5.0: + resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + engines: {node: '>=12'} + dev: false + /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -23724,6 +25583,13 @@ packages: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + /magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + /make-dir@1.3.0: resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} engines: {node: '>=4'} @@ -23964,7 +25830,6 @@ packages: engines: {node: '>= 4.0.0'} dependencies: fs-monkey: 1.0.6 - dev: false /memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -24031,6 +25896,9 @@ packages: /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + /merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + /merge-source-map@1.1.0: resolution: {integrity: sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==} dependencies: @@ -24544,6 +26412,28 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + requiresBuild: true + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + dev: false + optional: true + + /msgpackr@1.11.2: + resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} + optionalDependencies: + msgpackr-extract: 3.0.3 + dev: false + /muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} dev: false @@ -24561,6 +26451,18 @@ packages: run-parallel: 1.2.0 dev: false + /multer@1.4.4-lts.1: + resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} + engines: {node: '>= 6.0.0'} + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + /multer@1.4.5-lts.1: resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==} engines: {node: '>= 6.0.0'} @@ -24639,6 +26541,21 @@ packages: sqlstring: 2.3.3 dev: false + /mysql2@3.11.4: + resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} + engines: {node: '>= 8.0'} + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.6.3 + long: 5.2.3 + lru.min: 1.1.1 + named-placeholders: 1.1.3 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + dev: false + /mysql@2.18.1: resolution: {integrity: sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==} engines: {node: '>= 0.6'} @@ -24744,6 +26661,42 @@ packages: /nested-error-stacks@2.1.1: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} + /nestjs-cls@4.4.1(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1): + resolution: {integrity: sha512-4yhldwm/cJ02lQ8ZAdM8KQ7gMfjAc1z3fo5QAQgXNyN4N6X5So9BCwv+BTLRugDCkELUo3qtzQHnKhGYL/ftPg==} + engines: {node: '>=16'} + peerDependencies: + '@nestjs/common': '> 7.0.0 < 11' + '@nestjs/core': '> 7.0.0 < 11' + reflect-metadata: '*' + rxjs: '>= 7' + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + reflect-metadata: 0.2.2 + rxjs: 7.8.1 + dev: false + + /nestjs-i18n@10.5.0(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(class-validator@0.14.1)(rxjs@7.8.1): + resolution: {integrity: sha512-Pf95sOk9NiNdEakEEuoxzM8W5Nnt/jj1TavbHeId05aIdTnR6slrvGD4mEur+1fudplrTRa7g/XA0gh5GZ30aw==} + engines: {node: '>=18'} + peerDependencies: + '@nestjs/common': '*' + '@nestjs/core': '*' + class-validator: '*' + rxjs: '*' + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + accept-language-parser: 1.5.0 + chokidar: 3.6.0 + class-validator: 0.14.1 + cookie: 0.5.0 + iterare: 1.2.1 + js-yaml: 4.1.0 + rxjs: 7.8.1 + string-format: 2.0.0 + dev: false + /newrelic@11.17.0: resolution: {integrity: sha512-gI5FGsfvHyGLUW/+q3op1SsF8jisW5wV+NVOoxV9J58GOEEsP1B4/9D5jyL3iiL5QO1REeWtK5n15d2OsiYAIg==} engines: {node: '>=16', npm: '>=6.0.0'} @@ -24814,7 +26767,6 @@ packages: /node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - dev: false /node-cache@4.2.1: resolution: {integrity: sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==} @@ -24831,6 +26783,12 @@ packages: minimatch: 3.1.2 dev: true + /node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + dependencies: + lodash: 4.17.21 + dev: true + /node-fetch-native@1.6.4: resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} dev: true @@ -24850,13 +26808,21 @@ packages: optional: true dependencies: whatwg-url: 5.0.0 - dev: true /node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} dev: false + /node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + requiresBuild: true + dependencies: + detect-libc: 2.0.3 + dev: false + optional: true + /node-gyp-build@4.8.1: resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} hasBin: true @@ -25554,6 +27520,18 @@ packages: knex: 3.1.0(mysql2@1.7.0)(mysql@2.18.1) dev: false + /objection@3.1.5(knex@3.1.0): + resolution: {integrity: sha512-Hx/ipAwXSuRBbOMWFKtRsAN0yITafqXtWB4OT4Z9wED7ty1h7bOnBdhLtcNus23GwLJqcMsRWdodL2p5GwlnfQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + knex: '>=1.0.1' + dependencies: + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + db-errors: 0.2.3 + knex: 3.1.0(mysql2@3.11.4)(mysql@2.18.1) + dev: false + /oblivious-set@1.0.0: resolution: {integrity: sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==} dev: false @@ -25887,6 +27865,10 @@ packages: release-zalgo: 1.0.0 dev: true + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: true + /package-json@4.0.1: resolution: {integrity: sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==} engines: {node: '>=4'} @@ -26050,6 +28032,34 @@ packages: engines: {node: '>=0.10.0'} dev: false + /passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + dev: false + + /passport-local@1.0.0: + resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + dev: false + + /passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + dev: false + + /passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + dev: false + /path-browserify@0.0.1: resolution: {integrity: sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==} dev: true @@ -26131,6 +28141,9 @@ packages: lru-cache: 10.2.2 minipass: 7.1.2 + /path-to-regexp@0.1.10: + resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -26139,6 +28152,9 @@ packages: dependencies: isarray: 0.0.1 + /path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + /path-type@1.1.0: resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} engines: {node: '>=0.10.0'} @@ -26174,6 +28190,10 @@ packages: engines: {node: '>= 14.16'} dev: false + /pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + dev: false + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -26226,6 +28246,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + /picomatch@4.0.1: + resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} + engines: {node: '>=12'} + dev: true + /picomatch@4.0.2: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} @@ -26388,7 +28413,6 @@ packages: /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - dev: false /pn@1.1.0: resolution: {integrity: sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==} @@ -27303,12 +29327,25 @@ packages: engines: {node: '>=0.10.0'} dev: false + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.3.0 + dev: true + /prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true dev: true + /prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + dev: true + /pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -27662,7 +29699,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 14.18.63 + '@types/node': 20.5.1 long: 5.2.3 dev: false @@ -27874,6 +29911,10 @@ packages: - utf-8-validate dev: false + /pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + dev: true + /q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} @@ -27897,6 +29938,12 @@ packages: dependencies: side-channel: 1.0.6 + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.6 + /qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} @@ -27942,6 +29989,10 @@ packages: resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} dev: true + /ramda@0.30.1: + resolution: {integrity: sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==} + dev: false + /random-bytes@1.0.0: resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} engines: {node: '>= 0.8'} @@ -29140,6 +31191,29 @@ packages: indent-string: 4.0.0 strip-indent: 3.0.0 + /redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + dev: false + + /redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + dependencies: + redis-errors: 1.2.0 + dev: false + + /redis@4.7.0: + resolution: {integrity: sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==} + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.0) + '@redis/client': 1.6.0 + '@redis/graph': 1.1.1(@redis/client@1.6.0) + '@redis/json': 1.0.7(@redis/client@1.6.0) + '@redis/search': 1.2.0(@redis/client@1.6.0) + '@redis/time-series': 1.1.0(@redis/client@1.6.0) + dev: false + /redux-devtools-instrument@1.10.0(redux@4.2.1): resolution: {integrity: sha512-X8JRBCzX2ADSMp+iiV7YQ8uoTNyEm0VPFPd4T854coz6lvRiBrFSqAr9YAS2n8Kzxx8CJQotR0QF9wsMM+3DvA==} deprecated: Package moved to @redux-devtools/instrument. @@ -29204,6 +31278,9 @@ packages: resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} dev: false + /reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + /reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} @@ -29378,7 +31455,6 @@ packages: /repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} - dev: false /replace-ext@1.0.1: resolution: {integrity: sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==} @@ -29590,6 +31666,11 @@ packages: engines: {node: '>=10'} dev: false + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + dev: true + /resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} dev: false @@ -29774,6 +31855,11 @@ packages: engines: {node: '>=0.12.0'} dev: true + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -29792,7 +31878,6 @@ packages: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.6.2 - dev: true /safe-array-concat@1.1.2: resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} @@ -30060,6 +32145,12 @@ packages: engines: {node: '>=10'} hasBin: true + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + dev: true + /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -30080,6 +32171,26 @@ packages: transitivePeerDependencies: - supports-color + /send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9(supports-color@5.5.0) + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + /sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} dependencies: @@ -30140,6 +32251,17 @@ packages: transitivePeerDependencies: - supports-color + /serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} @@ -30536,6 +32658,13 @@ packages: urix: 0.1.0 dev: false + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: @@ -30750,7 +32879,6 @@ packages: engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 - dev: false /stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -30775,6 +32903,10 @@ packages: stacktrace-gps: 3.1.2 dev: false + /standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + dev: false + /start-server-webpack-plugin@2.2.5: resolution: {integrity: sha512-DRCkciwCJoCFZ+wt3wWMkR1M2mpVhJbUKFXqhK3FWyIUKYb42NnocH5sMwqgo+nPNHupqNwK/v8lgfBbr2NKdg==} dev: true @@ -30884,7 +33016,6 @@ packages: /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} - dev: false /strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} @@ -30896,6 +33027,10 @@ packages: engines: {node: '>=0.6.19'} dev: false + /string-format@2.0.0: + resolution: {integrity: sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==} + dev: false + /string-length@2.0.0: resolution: {integrity: sha512-Qka42GGrS8Mm3SZ+7cH8UXiIWI867/b/Z/feQSpQx/rbfB8UGknGEZVaUQMOUVj+soY6NpWAxily63HI1OckVQ==} engines: {node: '>=4'} @@ -30918,7 +33053,6 @@ packages: dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 - dev: false /string-length@5.0.1: resolution: {integrity: sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==} @@ -31278,6 +33412,33 @@ packages: - supports-color dev: true + /superagent@9.0.2: + resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + engines: {node: '>=14.18.0'} + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.7(supports-color@5.5.0) + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 3.5.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.12.1 + transitivePeerDependencies: + - supports-color + dev: true + + /supertest@7.0.0: + resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} + engines: {node: '>=14.18.0'} + dependencies: + methods: 1.1.2 + superagent: 9.0.2 + transitivePeerDependencies: + - supports-color + dev: true + /supports-color@5.4.0: resolution: {integrity: sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==} engines: {node: '>=4'} @@ -31366,11 +33527,20 @@ packages: stable: 0.1.8 dev: false + /swagger-ui-dist@5.17.14: + resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + dev: false + /symbol-observable@1.2.0: resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} engines: {node: '>=0.10.0'} dev: false + /symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + dev: true + /symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: false @@ -31393,6 +33563,14 @@ packages: /synchronous-promise@2.0.17: resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} + /synckit@0.9.2: + resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.2 + dev: true + /tailwindcss@3.4.14(ts-node@10.9.2): resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} engines: {node: '>=14.0.0'} @@ -31588,6 +33766,31 @@ packages: terser: 5.31.0 webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0) + /terser-webpack-plugin@5.3.10(esbuild@0.23.1)(webpack@5.96.1): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + esbuild: 0.23.1 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.31.0 + webpack: 5.96.1(esbuild@0.23.1) + dev: true + /terser@5.31.0: resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==} engines: {node: '>=10'} @@ -31913,7 +34116,6 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true /tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} @@ -31945,7 +34147,6 @@ packages: /tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - dev: false /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} @@ -31982,6 +34183,45 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: false + /ts-jest@29.2.5(@babel/core@7.26.0)(esbuild@0.23.1)(jest@29.7.0)(typescript@5.6.3): + resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.26.0 + bs-logger: 0.2.6 + ejs: 3.1.10 + esbuild: 0.23.1 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.6.3 + yargs-parser: 21.1.1 + dev: true + /ts-loader@9.5.1(typescript@3.9.10)(webpack@5.91.0): resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} engines: {node: '>=12.0.0'} @@ -32011,8 +34251,7 @@ packages: semver: 7.6.2 source-map: 0.7.4 typescript: 5.6.3 - webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4) - dev: false + webpack: 5.91.0(esbuild@0.23.1) /ts-node@10.9.2(@types/node@20.5.1)(typescript@5.6.3): resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} @@ -32063,6 +34302,10 @@ packages: /ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} + /ts-toolbelt@9.6.0: + resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} + dev: false + /ts-transformer-keys@0.4.4(typescript@3.9.10): resolution: {integrity: sha512-LrqgvaFvar01/5mbunRyeLTSIkqoC2xfcpL/90aDY6vR07DGyH+UaYGdIEsUudnlAw2Sr0pxFgdZvE0QIyI4qA==} peerDependencies: @@ -32107,6 +34350,13 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + /tslib@2.8.0: + resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + dev: false + /tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} @@ -32365,6 +34615,12 @@ packages: resolution: {integrity: sha512-/c7Bxnm6eh5kXx2I+mTuO+2OvoWni5+rXA3PhXwVWCtJRYmz3hMok5s1AKLzoDvNAZqj/Q/acGstN0ri5aQoOA==} dev: false + /types-ramda@0.30.1: + resolution: {integrity: sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==} + dependencies: + ts-toolbelt: 9.6.0 + dev: false + /typescript-eslint@8.11.0(eslint@9.13.0)(typescript@5.6.3): resolution: {integrity: sha512-cBRGnW3FSlxaYwU8KfAewxFK5uzeOAp0l2KebIlPDOT5olVi65KDG/yjBooPBG0kGW/HLkoz1c/iuBFehcS3IA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -32427,6 +34683,12 @@ packages: random-bytes: 1.0.0 dev: false + /uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + dependencies: + '@lukeed/csprng': 1.1.0 + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -32900,7 +35162,6 @@ packages: /validator@13.12.0: resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} engines: {node: '>= 0.10'} - dev: false /value-equal@1.0.1: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} @@ -33200,7 +35461,6 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true /webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -33530,6 +35790,45 @@ packages: - esbuild - uglify-js + /webpack@5.96.1(esbuild@0.23.1): + resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.14.0 + browserslist: 4.24.2 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.3 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.96.1) + watchpack: 2.4.1 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + dev: true + /websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -33579,7 +35878,6 @@ packages: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true /whatwg-url@6.5.0: resolution: {integrity: sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==} @@ -34316,6 +36614,10 @@ packages: toposort: 2.0.2 dev: false + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + dev: false + /zoom-level@2.5.0: resolution: {integrity: sha512-7UlRWU4Q3uCMCeDVMOm7eBrIu145OqsIJ3p6zq58l8UsSYwKWxc6zEapC5YA9tIeh0oheb4cT9Kk2Wq353loFg==} dev: false