Jerry Liang 1 год назад
Сommit
3bc613066e
100 измененных файлов с 13969 добавлено и 0 удалено
  1. 20 0
      .env
  2. 20 0
      .env.development
  3. 20 0
      .env.production
  4. 8 0
      .gitignore
  5. 6709 0
      package-lock.json
  6. 88 0
      package.json
  7. 20 0
      src/api/accountManage/index.ts
  8. 18 0
      src/api/accountManage/interface.ts
  9. 4 0
      src/api/auth/index.ts
  10. 21 0
      src/api/auth/interface.ts
  11. 62 0
      src/api/cloudLog/index.ts
  12. 49 0
      src/api/cloudLog/interface.ts
  13. 20 0
      src/api/emsLog/index.ts
  14. 11 0
      src/api/emsLog/interface.ts
  15. 52 0
      src/api/public.ts
  16. 43 0
      src/api/siteManage/index.ts
  17. 54 0
      src/api/siteManage/interface.ts
  18. 21 0
      src/api/user/index.ts
  19. 67 0
      src/api/user/interface.ts
  20. 22 0
      src/api/userInfo/index.ts
  21. 28 0
      src/api/userInfo/interface.ts
  22. 88 0
      src/app/index.ts
  23. 98 0
      src/config/config.default.ts
  24. 14 0
      src/constant/auth/about/menu.json
  25. 173 0
      src/constant/auth/assembly/menu.json
  26. 9 0
      src/constant/auth/auth/button_admin.json
  27. 44 0
      src/constant/auth/auth/menu.json
  28. 9 0
      src/constant/auth/cloudData/button_admin.json
  29. 77 0
      src/constant/auth/cloudData/menu.json
  30. 30 0
      src/constant/auth/dashboard/menu.json
  31. 14 0
      src/constant/auth/dataScreen/menu.json
  32. 100 0
      src/constant/auth/directives/menu.json
  33. 100 0
      src/constant/auth/echarts/menu.json
  34. 9 0
      src/constant/auth/emsData/button_admin.json
  35. 30 0
      src/constant/auth/emsData/menu.json
  36. 72 0
      src/constant/auth/form/menu.json
  37. 14 0
      src/constant/auth/home/menu.json
  38. 86 0
      src/constant/auth/link/menu.json
  39. 132 0
      src/constant/auth/menu/menu.json
  40. 9 0
      src/constant/auth/operations/button_admin.json
  41. 30 0
      src/constant/auth/operations/menu.json
  42. 9 0
      src/constant/auth/proTable/button.json
  43. 132 0
      src/constant/auth/proTable/menu.json
  44. 86 0
      src/constant/auth/system/menu.json
  45. 58 0
      src/constant/auth/toolbox/menu.json
  46. 189 0
      src/constant/cloudData/log.type.ts
  47. 140 0
      src/constant/err.type.ts
  48. 34 0
      src/constant/fwh/serverConfig.ts
  49. 82 0
      src/controller/accountManage.controller.ts
  50. 53 0
      src/controller/auth.controller.ts
  51. 133 0
      src/controller/cloudLog.controller.ts
  52. 45 0
      src/controller/emsLog.controller.ts
  53. 180 0
      src/controller/siteManage.controller.ts
  54. 115 0
      src/controller/user.controller.ts
  55. 92 0
      src/controller/userInfo.controller.ts
  56. 2 0
      src/core/index.ts
  57. 11 0
      src/core/type/index.ts
  58. 67 0
      src/external/fwh/api/getEmsLog/dataType.ts
  59. 110 0
      src/external/fwh/api/getEmsLog/index.ts
  60. 30 0
      src/external/fwh/api/getMqttLog/dataType.ts
  61. 145 0
      src/external/fwh/api/getMqttLog/index.ts
  62. 119 0
      src/external/fwh/api/getSiteList/dataType.ts
  63. 117 0
      src/external/fwh/api/getSiteList/index.ts
  64. 46 0
      src/external/fwh/api/getWarehouseList/dataType.ts
  65. 60 0
      src/external/fwh/api/getWarehouseList/index.ts
  66. 24 0
      src/external/fwh/axiosRetry.ts
  67. 172 0
      src/external/fwh/login/index.ts
  68. 116 0
      src/external/fwh/request/index.ts
  69. 10 0
      src/index.ts
  70. 188 0
      src/locales/cloudLogType/en.json
  71. 188 0
      src/locales/cloudLogType/zh.json
  72. 28 0
      src/logger/index.ts
  73. 53 0
      src/middleware/cloudLog.middleware.ts
  74. 22 0
      src/middleware/jwt.middleware.ts
  75. 89 0
      src/middleware/user.middleware.ts
  76. 144 0
      src/model/cloudLog.model.ts
  77. 4 0
      src/model/emsLogManage.model.ts
  78. 15 0
      src/model/model.ts
  79. 266 0
      src/model/siteManage.model.ts
  80. 112 0
      src/model/user.model.ts
  81. 138 0
      src/model/warehouse.model.ts
  82. 17 0
      src/queue/board.queue.ts
  83. 380 0
      src/queue/fwhServerTask/getCloudLog.task.ts
  84. 131 0
      src/queue/fwhServerTask/getEmsLog.task.ts
  85. 288 0
      src/queue/fwhServerTask/getSiteInfo.task.ts
  86. 93 0
      src/queue/fwhServerTask/getWarehouseList.task.ts
  87. 121 0
      src/queue/fwhServerTask/index.ts
  88. 20 0
      src/queue/task.queue.ts
  89. 17 0
      src/router/accountManage.route.ts
  90. 17 0
      src/router/auth.route.ts
  91. 26 0
      src/router/cloudLog.route.ts
  92. 11 0
      src/router/emsLog.route.ts
  93. 20 0
      src/router/index.ts
  94. 94 0
      src/router/siteManage.route.ts
  95. 36 0
      src/router/user.route.ts
  96. 17 0
      src/router/userInfo.route.ts
  97. 101 0
      src/service/accountManage.service.ts
  98. 36 0
      src/service/auth.service.ts
  99. 183 0
      src/service/cloudLog.service.ts
  100. 242 0
      src/service/siteManage.service.ts

+ 20 - 0
.env

@@ -0,0 +1,20 @@
+APP_HOST = '0.0.0.0'
+
+# db config
+DB_TYPE = 'postgres'
+DB_HOST = 'localhost'
+DB_PORT = 5432
+DB_USER = 'fwh'
+DB_PWD = '12345678'
+DB_NAME = 'fwhdb'
+
+# redis
+RDS_HOST = 'localhost'
+RDS_PWD = # undefined
+# DB allot
+RDS_DB_QUEUE = 15
+RDS_DB_CACHE = 14
+RDS_DB_LOCK = 13
+
+# MQTT
+

+ 20 - 0
.env.development

@@ -0,0 +1,20 @@
+APP_PORT = 9999
+RDS_PORT = 6380
+
+# log config
+LOG_PATH = '/var/log/bee-server/dev/run.log'
+LOG_LEVEL = 'trace' # 'fatal'、'error'、'warn'、'info'、'debug'、'trace' 或 'silent' 之一
+# fatal:表示非常严重的错误,通常是应用程序崩溃、无法恢复的错误。只有在应用程序无法继续运行时才会使用这个级别的日志。
+# 示例:数据库连接失败,导致程序无法启动。
+# error:表示严重的错误,但通常这些错误不会导致程序完全崩溃。此类错误一般需要处理,可能会导致某些功能不可用。
+# 示例:用户请求处理失败,但应用程序可以继续运行。
+# warn:表示警告信息,通常是程序的某些行为可能存在问题,但程序仍然能正常运行。它提示开发者需要注意的潜在问题。
+# 示例:某个 API 请求可能返回过时的数据,或者某个功能在即将到期。
+# info:表示常规的运行信息,记录程序的正常运行过程中的一些关键步骤。这类信息通常用于了解应用程序的状态,或是记录一些重要的事件。
+# 示例:用户成功登录,服务启动成功等。
+# debug:表示调试信息,用于开发过程中。此类信息详细描述了程序的执行流程,通常帮助开发者定位问题。生产环境下不建议开启此级别的日志。
+# 示例:函数调用顺序、变量值等调试信息。
+# trace:表示极其详细的日志信息,通常用于非常细粒度的调试。它通常是记录更为详细的执行流程或内部状态,甚至是每行代码的执行情况。
+# 示例:每个函数调用、每个循环步骤等。
+# silent:表示禁用所有日志输出,不会记录任何日志。这个级别的日志通常用于生产环境,避免产生过多的日志信息。
+# 示例:关闭日志记录,完全静默运行。

+ 20 - 0
.env.production

@@ -0,0 +1,20 @@
+APP_PORT = 9009
+RDS_PORT = 6379
+
+# log config
+LOG_PATH = '/var/log/bee-server/pro/run.log'
+LOG_LEVEL = 'error' # 'fatal'、'error'、'warn'、'info'、'debug'、'trace' 或 'silent' 之一
+# fatal:表示非常严重的错误,通常是应用程序崩溃、无法恢复的错误。只有在应用程序无法继续运行时才会使用这个级别的日志。
+# 示例:数据库连接失败,导致程序无法启动。
+# error:表示严重的错误,但通常这些错误不会导致程序完全崩溃。此类错误一般需要处理,可能会导致某些功能不可用。
+# 示例:用户请求处理失败,但应用程序可以继续运行。
+# warn:表示警告信息,通常是程序的某些行为可能存在问题,但程序仍然能正常运行。它提示开发者需要注意的潜在问题。
+# 示例:某个 API 请求可能返回过时的数据,或者某个功能在即将到期。
+# info:表示常规的运行信息,记录程序的正常运行过程中的一些关键步骤。这类信息通常用于了解应用程序的状态,或是记录一些重要的事件。
+# 示例:用户成功登录,服务启动成功等。
+# debug:表示调试信息,用于开发过程中。此类信息详细描述了程序的执行流程,通常帮助开发者定位问题。生产环境下不建议开启此级别的日志。
+# 示例:函数调用顺序、变量值等调试信息。
+# trace:表示极其详细的日志信息,通常用于非常细粒度的调试。它通常是记录更为详细的执行流程或内部状态,甚至是每行代码的执行情况。
+# 示例:每个函数调用、每个循环步骤等。
+# silent:表示禁用所有日志输出,不会记录任何日志。这个级别的日志通常用于生产环境,避免产生过多的日志信息。
+# 示例:关闭日志记录,完全静默运行。

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+node_modules
+cache
+dist
+temp
+log
+
+.DS_Store
+.temp

+ 6709 - 0
package-lock.json

@@ -0,0 +1,6709 @@
+{
+  "name": "bee-server",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "bee-server",
+      "version": "1.0.0",
+      "license": "ISC",
+      "dependencies": {
+        "@bull-board/api": "^6.6.2",
+        "@bull-board/koa": "^6.6.2",
+        "axios": "^1.7.7",
+        "axios-retry": "^4.5.0",
+        "bcryptjs": "^2.4.3",
+        "bullmq": "^5.28.2",
+        "date-fns": "^4.1.0",
+        "dotenv": "^16.4.5",
+        "i18next": "^23.16.5",
+        "i18next-fs-backend": "^2.3.2",
+        "i18next-http-middleware": "^3.6.0",
+        "ioredis": "^5.4.1",
+        "jsonwebtoken": "^9.0.2",
+        "koa": "^2.15.3",
+        "koa-body": "^6.0.1",
+        "koa-i18next-detector": "^0.7.2",
+        "koa-jwt": "^4.0.4",
+        "koa-pino-logger": "^4.0.0",
+        "koa-router": "^13.0.1",
+        "koa-zod-errorhandler": "^1.0.0",
+        "koa2-cors": "^2.0.6",
+        "lodash": "^4.17.21",
+        "md5": "^2.3.0",
+        "mongoose": "^8.8.3",
+        "mqtt": "^5.10.3",
+        "pg": "^8.13.1",
+        "pg-hstore": "^2.3.4",
+        "pino": "^9.5.0",
+        "pino-http": "^10.3.0",
+        "qs": "^6.13.1",
+        "query-string": "^9.1.1",
+        "redis": "^4.7.0",
+        "redlock": "^5.0.0-beta.2",
+        "sequelize": "^6.37.5",
+        "zod": "^3.23.8"
+      },
+      "bin": {
+        "bee-server": "dist/index.js"
+      },
+      "devDependencies": {
+        "@types/bcryptjs": "^2.4.6",
+        "@types/bull": "^3.15.9",
+        "@types/jsonwebtoken": "^9.0.7",
+        "@types/koa": "^2.15.0",
+        "@types/koa-pino-logger": "^3.0.4",
+        "@types/koa-router": "^7.4.8",
+        "@types/koa2-cors": "^2.0.5",
+        "@types/lodash": "^4.17.13",
+        "@types/md5": "^2.3.5",
+        "@types/node": "^22.9.0",
+        "pino-pretty": "^11.3.0",
+        "pkg": "^5.8.1",
+        "tsc-alias": "^1.8.10",
+        "tsx": "^4.19.2",
+        "typescript": "^5.6.3"
+      }
+    },
+    "node_modules/@babel/generator": {
+      "version": "7.18.2",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.18.2.tgz",
+      "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/types": "^7.18.2",
+        "@jridgewell/gen-mapping": "^0.3.0",
+        "jsesc": "^2.5.1"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.18.4",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.18.4.tgz",
+      "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/runtime": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz",
+      "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
+      "license": "MIT",
+      "dependencies": {
+        "regenerator-runtime": "^0.14.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.19.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.19.0.tgz",
+      "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.18.10",
+        "@babel/helper-validator-identifier": "^7.18.6",
+        "to-fast-properties": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@bull-board/api": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmmirror.com/@bull-board/api/-/api-6.6.2.tgz",
+      "integrity": "sha512-et9f9EYsQpMcql7ASc5n0d30/ePri0pS3Cyq3QsVSYWiyr8BeibdL+XAWmZmqluLL8YI4UQrirbvKgOoCPzcPA==",
+      "license": "MIT",
+      "dependencies": {
+        "redis-info": "^3.0.8"
+      },
+      "peerDependencies": {
+        "@bull-board/ui": "6.6.2"
+      }
+    },
+    "node_modules/@bull-board/koa": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmmirror.com/@bull-board/koa/-/koa-6.6.2.tgz",
+      "integrity": "sha512-0Ms1dY3ioNikEkQ4pzPO2CvHBn+VMzel+jtf/Ee0UylqaG4Jeik9Xk+kb8GUGeGwTA7a1xD8ZwcIH1NiITP5Rg==",
+      "license": "MIT",
+      "dependencies": {
+        "@bull-board/api": "6.6.2",
+        "@bull-board/ui": "6.6.2",
+        "@koa/bodyparser": "^5.1.1",
+        "@ladjs/koa-views": "^9.0.0",
+        "ejs": "^3.1.10",
+        "koa": "^2.15.3",
+        "koa-mount": "^4.0.0",
+        "koa-router": "^13.0.1",
+        "koa-static": "^5.0.0"
+      }
+    },
+    "node_modules/@bull-board/ui": {
+      "version": "6.6.2",
+      "resolved": "https://registry.npmmirror.com/@bull-board/ui/-/ui-6.6.2.tgz",
+      "integrity": "sha512-v7JhyQbdmTYOD2A9PMT9a+9mXybpvPEDDiFhBEUr7GbmR8C61Pxiaxm21OcXWGCz19TcnjUmoFVbSN4pGVVioA==",
+      "license": "MIT",
+      "dependencies": {
+        "@bull-board/api": "6.6.2"
+      }
+    },
+    "node_modules/@hapi/bourne": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/@hapi/bourne/-/bourne-3.0.0.tgz",
+      "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/@ioredis/commands": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@ioredis/commands/-/commands-1.2.0.tgz",
+      "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
+      "license": "MIT"
+    },
+    "node_modules/@isaacs/cliui": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
+      "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^5.1.2",
+        "string-width-cjs": "npm:string-width@^4.2.0",
+        "strip-ansi": "^7.0.1",
+        "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+        "wrap-ansi": "^8.1.0",
+        "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz",
+      "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz",
+      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
+      "version": "9.2.2",
+      "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz",
+      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+      "license": "MIT"
+    },
+    "node_modules/@isaacs/cliui/node_modules/string-width": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz",
+      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+      "license": "MIT",
+      "dependencies": {
+        "eastasianwidth": "^0.2.0",
+        "emoji-regex": "^9.2.2",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz",
+      "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+      }
+    },
+    "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+      "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^6.1.0",
+        "string-width": "^5.0.1",
+        "strip-ansi": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/@jridgewell/gen-mapping": {
+      "version": "0.3.8",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/set-array": "^1.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/set-array": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.25",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "node_modules/@koa/bodyparser": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/@koa/bodyparser/-/bodyparser-5.1.1.tgz",
+      "integrity": "sha512-ZBF49xqNVxnmJ+8iXegq+fXPQm9RSX8giNl/aXS5rW1VpNct92wnFbGR/47vfoRJVLARGQ4HVL4WaQ0u8IJVoA==",
+      "license": "MIT",
+      "dependencies": {
+        "co-body": "^6.1.0",
+        "lodash.merge": "^4.6.2",
+        "type-is": "^1.6.18"
+      },
+      "engines": {
+        "node": ">= 16"
+      },
+      "peerDependencies": {
+        "koa": "^2.14.1"
+      }
+    },
+    "node_modules/@ladjs/consolidate": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/@ladjs/consolidate/-/consolidate-1.0.4.tgz",
+      "integrity": "sha512-ErvBg5acSqns86V/xW7gjqqnBBs6thnpMB0gGc3oM7WHsV8PWrnBtKI6dumHDT3UT/zEOfGzp7dmSFqWoCXKWQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "@babel/core": "^7.22.5",
+        "arc-templates": "^0.5.3",
+        "atpl": ">=0.7.6",
+        "bracket-template": "^1.1.5",
+        "coffee-script": "^1.12.7",
+        "dot": "^1.1.3",
+        "dust": "^0.3.0",
+        "dustjs-helpers": "^1.7.4",
+        "dustjs-linkedin": "^2.7.5",
+        "eco": "^1.1.0-rc-3",
+        "ect": "^0.5.9",
+        "ejs": "^3.1.5",
+        "haml-coffee": "^1.14.1",
+        "hamlet": "^0.3.3",
+        "hamljs": "^0.6.2",
+        "handlebars": "^4.7.6",
+        "hogan.js": "^3.0.2",
+        "htmling": "^0.0.8",
+        "jazz": "^0.0.18",
+        "jqtpl": "~1.1.0",
+        "just": "^0.1.8",
+        "liquid-node": "^3.0.1",
+        "liquor": "^0.0.5",
+        "lodash": "^4.17.20",
+        "mote": "^0.2.0",
+        "mustache": "^4.0.1",
+        "nunjucks": "^3.2.2",
+        "plates": "~0.4.11",
+        "pug": "^3.0.0",
+        "qejs": "^3.0.5",
+        "ractive": "^1.3.12",
+        "react": ">=16.13.1",
+        "react-dom": ">=16.13.1",
+        "slm": "^2.0.0",
+        "swig": "^1.4.2",
+        "swig-templates": "^2.0.3",
+        "teacup": "^2.0.0",
+        "templayed": ">=0.2.3",
+        "then-pug": "*",
+        "tinyliquid": "^0.2.34",
+        "toffee": "^0.3.6",
+        "twig": "^1.15.2",
+        "twing": "^5.0.2",
+        "underscore": "^1.11.0",
+        "vash": "^0.13.0",
+        "velocityjs": "^2.0.1",
+        "walrus": "^0.10.1",
+        "whiskers": "^0.4.0"
+      },
+      "peerDependenciesMeta": {
+        "@babel/core": {
+          "optional": true
+        },
+        "arc-templates": {
+          "optional": true
+        },
+        "atpl": {
+          "optional": true
+        },
+        "bracket-template": {
+          "optional": true
+        },
+        "coffee-script": {
+          "optional": true
+        },
+        "dot": {
+          "optional": true
+        },
+        "dust": {
+          "optional": true
+        },
+        "dustjs-helpers": {
+          "optional": true
+        },
+        "dustjs-linkedin": {
+          "optional": true
+        },
+        "eco": {
+          "optional": true
+        },
+        "ect": {
+          "optional": true
+        },
+        "ejs": {
+          "optional": true
+        },
+        "haml-coffee": {
+          "optional": true
+        },
+        "hamlet": {
+          "optional": true
+        },
+        "hamljs": {
+          "optional": true
+        },
+        "handlebars": {
+          "optional": true
+        },
+        "hogan.js": {
+          "optional": true
+        },
+        "htmling": {
+          "optional": true
+        },
+        "jazz": {
+          "optional": true
+        },
+        "jqtpl": {
+          "optional": true
+        },
+        "just": {
+          "optional": true
+        },
+        "liquid-node": {
+          "optional": true
+        },
+        "liquor": {
+          "optional": true
+        },
+        "lodash": {
+          "optional": true
+        },
+        "mote": {
+          "optional": true
+        },
+        "mustache": {
+          "optional": true
+        },
+        "nunjucks": {
+          "optional": true
+        },
+        "plates": {
+          "optional": true
+        },
+        "pug": {
+          "optional": true
+        },
+        "qejs": {
+          "optional": true
+        },
+        "ractive": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "slm": {
+          "optional": true
+        },
+        "swig": {
+          "optional": true
+        },
+        "swig-templates": {
+          "optional": true
+        },
+        "teacup": {
+          "optional": true
+        },
+        "templayed": {
+          "optional": true
+        },
+        "then-pug": {
+          "optional": true
+        },
+        "tinyliquid": {
+          "optional": true
+        },
+        "toffee": {
+          "optional": true
+        },
+        "twig": {
+          "optional": true
+        },
+        "twing": {
+          "optional": true
+        },
+        "underscore": {
+          "optional": true
+        },
+        "vash": {
+          "optional": true
+        },
+        "velocityjs": {
+          "optional": true
+        },
+        "walrus": {
+          "optional": true
+        },
+        "whiskers": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@ladjs/koa-views": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmmirror.com/@ladjs/koa-views/-/koa-views-9.0.0.tgz",
+      "integrity": "sha512-/nzUN3HhB7SIxrE4c2MXCLARXMwgoy7xJogyX8lNJo4VBxM6dVQ47iZWjOoHxTBKGFNAv2OTQ0rWI7HYpiv8kg==",
+      "license": "MIT",
+      "dependencies": {
+        "@ladjs/consolidate": "^1.0.1",
+        "debug": "^4.3.4",
+        "get-paths": "0.0.7",
+        "koa-send": "^5.0.1",
+        "mz": "^2.7.0",
+        "pretty": "^2.0.0",
+        "resolve-path": "^1.4.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "@types/koa": "^2.13.1"
+      },
+      "peerDependenciesMeta": {
+        "@types/koa": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mongodb-js/saslprep": {
+      "version": "1.1.9",
+      "resolved": "https://registry.npmmirror.com/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
+      "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
+      "license": "MIT",
+      "dependencies": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
+    "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
+      "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
+      "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
+      "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
+      "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
+      "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
+      "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@one-ini/wasm": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmmirror.com/@one-ini/wasm/-/wasm-0.1.1.tgz",
+      "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+      "license": "MIT"
+    },
+    "node_modules/@pkgjs/parseargs": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+      "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@redis/bloom": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@redis/bloom/-/bloom-1.2.0.tgz",
+      "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@redis/client": "^1.0.0"
+      }
+    },
+    "node_modules/@redis/client": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/@redis/client/-/client-1.6.0.tgz",
+      "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==",
+      "license": "MIT",
+      "dependencies": {
+        "cluster-key-slot": "1.1.2",
+        "generic-pool": "3.9.0",
+        "yallist": "4.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@redis/graph": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/@redis/graph/-/graph-1.1.1.tgz",
+      "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@redis/client": "^1.0.0"
+      }
+    },
+    "node_modules/@redis/json": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/@redis/json/-/json-1.0.7.tgz",
+      "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@redis/client": "^1.0.0"
+      }
+    },
+    "node_modules/@redis/search": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/@redis/search/-/search-1.2.0.tgz",
+      "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@redis/client": "^1.0.0"
+      }
+    },
+    "node_modules/@redis/time-series": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/@redis/time-series/-/time-series-1.1.0.tgz",
+      "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@redis/client": "^1.0.0"
+      }
+    },
+    "node_modules/@types/accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmmirror.com/@types/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/bcryptjs": {
+      "version": "2.4.6",
+      "resolved": "https://registry.npmmirror.com/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
+      "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.5",
+      "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz",
+      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/bull": {
+      "version": "3.15.9",
+      "resolved": "https://registry.npmmirror.com/@types/bull/-/bull-3.15.9.tgz",
+      "integrity": "sha512-MPUcyPPQauAmynoO3ezHAmCOhbB0pWmYyijr/5ctaCqhbKWsjW0YCod38ZcLzUBprosfZ9dPqfYIcfdKjk7RNQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/ioredis": "*",
+        "@types/redis": "^2.8.0"
+      }
+    },
+    "node_modules/@types/co-body": {
+      "version": "6.1.3",
+      "resolved": "https://registry.npmmirror.com/@types/co-body/-/co-body-6.1.3.tgz",
+      "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/content-disposition": {
+      "version": "0.5.8",
+      "resolved": "https://registry.npmmirror.com/@types/content-disposition/-/content-disposition-0.5.8.tgz",
+      "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/cookies": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmmirror.com/@types/cookies/-/cookies-0.9.0.tgz",
+      "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/express": "*",
+        "@types/keygrip": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/debug": {
+      "version": "4.1.12",
+      "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
+      "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/ms": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/@types/express/-/express-5.0.0.tgz",
+      "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^5.0.0",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz",
+      "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/@types/formidable": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/@types/formidable/-/formidable-2.0.6.tgz",
+      "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/http-assert": {
+      "version": "1.5.6",
+      "resolved": "https://registry.npmmirror.com/@types/http-assert/-/http-assert-1.5.6.tgz",
+      "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz",
+      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/ioredis": {
+      "version": "4.28.10",
+      "resolved": "https://registry.npmmirror.com/@types/ioredis/-/ioredis-4.28.10.tgz",
+      "integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/jsonwebtoken": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
+      "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/keygrip": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/@types/keygrip/-/keygrip-1.0.6.tgz",
+      "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/koa": {
+      "version": "2.15.0",
+      "resolved": "https://registry.npmmirror.com/@types/koa/-/koa-2.15.0.tgz",
+      "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/accepts": "*",
+        "@types/content-disposition": "*",
+        "@types/cookies": "*",
+        "@types/http-assert": "*",
+        "@types/http-errors": "*",
+        "@types/keygrip": "*",
+        "@types/koa-compose": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/koa-compose": {
+      "version": "3.2.8",
+      "resolved": "https://registry.npmmirror.com/@types/koa-compose/-/koa-compose-3.2.8.tgz",
+      "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/koa": "*"
+      }
+    },
+    "node_modules/@types/koa-pino-logger": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmmirror.com/@types/koa-pino-logger/-/koa-pino-logger-3.0.4.tgz",
+      "integrity": "sha512-rjXihDguqIbisctje6soD6KSUgdRM97uLfcyL0hQyv2aewTa55ZReRrP481hZUZ7C2BIgscN5xS9QLG/SdWZ3Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/koa": "*",
+        "@types/pino": "6.3",
+        "@types/pino-http": "*"
+      }
+    },
+    "node_modules/@types/koa-router": {
+      "version": "7.4.8",
+      "resolved": "https://registry.npmmirror.com/@types/koa-router/-/koa-router-7.4.8.tgz",
+      "integrity": "sha512-SkWlv4F9f+l3WqYNQHnWjYnyTxYthqt8W9az2RTdQW7Ay8bc00iRZcrb8MC75iEfPqnGcg2csEl8tTG1NQPD4A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/koa": "*"
+      }
+    },
+    "node_modules/@types/koa2-cors": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/@types/koa2-cors/-/koa2-cors-2.0.5.tgz",
+      "integrity": "sha512-MrZ8NpnkPYQqTRy2Llflt2f2sLTQTSy5fAOa2YTxUu6UJZXdsY6cszpzuB9EhkAFXJL1D8kEEHGq4Q7rU+jEAg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/koa": "*"
+      }
+    },
+    "node_modules/@types/lodash": {
+      "version": "4.17.13",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz",
+      "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/md5": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmmirror.com/@types/md5/-/md5-2.3.5.tgz",
+      "integrity": "sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "license": "MIT"
+    },
+    "node_modules/@types/ms": {
+      "version": "0.7.34",
+      "resolved": "https://registry.npmmirror.com/@types/ms/-/ms-0.7.34.tgz",
+      "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "22.9.0",
+      "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.9.0.tgz",
+      "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.19.8"
+      }
+    },
+    "node_modules/@types/pino": {
+      "version": "6.3.12",
+      "resolved": "https://registry.npmmirror.com/@types/pino/-/pino-6.3.12.tgz",
+      "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/pino-pretty": "*",
+        "@types/pino-std-serializers": "*",
+        "sonic-boom": "^2.1.0"
+      }
+    },
+    "node_modules/@types/pino-http": {
+      "version": "5.8.4",
+      "resolved": "https://registry.npmmirror.com/@types/pino-http/-/pino-http-5.8.4.tgz",
+      "integrity": "sha512-UTYBQ2acmJ2eK0w58vVtgZ9RAicFFndfrnWC1w5cBTf8zwn/HEy8O+H7psc03UZgTzHmlcuX8VkPRnRDEj+FUQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/pino": "6.3"
+      }
+    },
+    "node_modules/@types/pino-pretty": {
+      "version": "4.7.5",
+      "resolved": "https://registry.npmmirror.com/@types/pino-pretty/-/pino-pretty-4.7.5.tgz",
+      "integrity": "sha512-rfHe6VIknk14DymxGqc9maGsRe8/HQSvM2u46EAz2XrS92qsAJnW16dpdFejBuZKD8cRJX6Aw6uVZqIQctMpAg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/pino": "6.3"
+      }
+    },
+    "node_modules/@types/pino-std-serializers": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/@types/pino-std-serializers/-/pino-std-serializers-2.4.1.tgz",
+      "integrity": "sha512-17XcksO47M24IVTVKPeAByWUd3Oez7EbIjXpSbzMPhXVzgjGtrOa49gKBwxH9hb8dKv58OelsWQ+A1G1l9S3wQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/qs": {
+      "version": "6.9.16",
+      "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.16.tgz",
+      "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
+      "license": "MIT"
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/readable-stream": {
+      "version": "4.0.18",
+      "resolved": "https://registry.npmmirror.com/@types/readable-stream/-/readable-stream-4.0.18.tgz",
+      "integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "safe-buffer": "~5.1.1"
+      }
+    },
+    "node_modules/@types/readable-stream/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
+    "node_modules/@types/redis": {
+      "version": "2.8.32",
+      "resolved": "https://registry.npmmirror.com/@types/redis/-/redis-2.8.32.tgz",
+      "integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/send": {
+      "version": "0.17.4",
+      "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz",
+      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.7",
+      "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz",
+      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/@types/validator": {
+      "version": "13.12.2",
+      "resolved": "https://registry.npmmirror.com/@types/validator/-/validator-13.12.2.tgz",
+      "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/webidl-conversions": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmmirror.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+      "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/whatwg-url": {
+      "version": "11.0.5",
+      "resolved": "https://registry.npmmirror.com/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+      "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/webidl-conversions": "*"
+      }
+    },
+    "node_modules/@types/ws": {
+      "version": "8.5.13",
+      "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.13.tgz",
+      "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/abbrev": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-2.0.0.tgz",
+      "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+      "license": "ISC",
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
+    "node_modules/abort-controller": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/abort-controller/-/abort-controller-3.0.0.tgz",
+      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+      "license": "MIT",
+      "dependencies": {
+        "event-target-shim": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=6.5"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/agent-base": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
+      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6.0.0"
+      }
+    },
+    "node_modules/aggregate-error": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/aggregate-error/-/aggregate-error-3.1.0.tgz",
+      "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+      "license": "MIT",
+      "dependencies": {
+        "clean-stack": "^2.0.0",
+        "indent-string": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+      "license": "MIT"
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/array-union": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz",
+      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+      "license": "MIT"
+    },
+    "node_modules/async": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
+      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/at-least-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz",
+      "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
+    "node_modules/atomic-sleep": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+      "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
+    "node_modules/axios": {
+      "version": "1.7.7",
+      "resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.7.tgz",
+      "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/axios-retry": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/axios-retry/-/axios-retry-4.5.0.tgz",
+      "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "is-retry-allowed": "^2.2.0"
+      },
+      "peerDependencies": {
+        "axios": "0.x || 1.x"
+      }
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "license": "MIT"
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/bcryptjs": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-2.4.3.tgz",
+      "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
+      "license": "MIT"
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/bl": {
+      "version": "6.0.16",
+      "resolved": "https://registry.npmmirror.com/bl/-/bl-6.0.16.tgz",
+      "integrity": "sha512-V/kz+z2Mx5/6qDfRCilmrukUXcXuCoXKg3/3hDvzKKoSUx8CJKudfIoT29XZc3UE9xBvxs5qictiHdprwtteEg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/readable-stream": "^4.0.0",
+        "buffer": "^6.0.3",
+        "inherits": "^2.0.4",
+        "readable-stream": "^4.2.0"
+      }
+    },
+    "node_modules/bl/node_modules/readable-stream": {
+      "version": "4.5.2",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-4.5.2.tgz",
+      "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
+      "license": "MIT",
+      "dependencies": {
+        "abort-controller": "^3.0.0",
+        "buffer": "^6.0.3",
+        "events": "^3.3.0",
+        "process": "^0.11.10",
+        "string_decoder": "^1.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/bson": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmmirror.com/bson/-/bson-6.10.0.tgz",
+      "integrity": "sha512-ROchNosXMJD2cbQGm84KoP7vOGPO6/bOAW0veMMbzhXLqoZptcaYRVLitwvuhwhjjpU1qP4YZRWLhgETdgqUQw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=16.20.1"
+      }
+    },
+    "node_modules/buffer": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz",
+      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.2.1"
+      }
+    },
+    "node_modules/buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/buffer-from": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz",
+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+      "license": "MIT"
+    },
+    "node_modules/bullmq": {
+      "version": "5.28.2",
+      "resolved": "https://registry.npmmirror.com/bullmq/-/bullmq-5.28.2.tgz",
+      "integrity": "sha512-2F94CJnOKP2kyf29v8bxpNNyF7IsG+z0RY80gP7dAvUprcEbb3CAvyCKMcbkHeF/kBKkcJCNQOlVY/+JY8dsUQ==",
+      "license": "MIT",
+      "dependencies": {
+        "cron-parser": "^4.6.0",
+        "ioredis": "^5.4.1",
+        "msgpackr": "^1.11.2",
+        "node-abort-controller": "^3.1.1",
+        "semver": "^7.5.4",
+        "tslib": "^2.0.0",
+        "uuid": "^9.0.0"
+      }
+    },
+    "node_modules/bullmq/node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/cache-content-type": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/cache-content-type/-/cache-content-type-1.0.1.tgz",
+      "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "^2.1.18",
+        "ylru": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 6.0.0"
+      }
+    },
+    "node_modules/call-bind": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz",
+      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/charenc": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz",
+      "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/clean-stack": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/clean-stack/-/clean-stack-2.2.0.tgz",
+      "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz",
+      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^7.0.0"
+      }
+    },
+    "node_modules/cluster-key-slot": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
+      "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmmirror.com/co/-/co-4.6.0.tgz",
+      "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+      "license": "MIT",
+      "engines": {
+        "iojs": ">= 1.0.0",
+        "node": ">= 0.12.0"
+      }
+    },
+    "node_modules/co-body": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmmirror.com/co-body/-/co-body-6.2.0.tgz",
+      "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==",
+      "license": "MIT",
+      "dependencies": {
+        "@hapi/bourne": "^3.0.0",
+        "inflation": "^2.0.0",
+        "qs": "^6.5.2",
+        "raw-body": "^2.3.3",
+        "type-is": "^1.6.16"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/colorette": {
+      "version": "2.0.20",
+      "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz",
+      "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/commander": {
+      "version": "9.5.0",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz",
+      "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || >=14"
+      }
+    },
+    "node_modules/commist": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/commist/-/commist-3.2.0.tgz",
+      "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==",
+      "license": "MIT"
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "license": "MIT"
+    },
+    "node_modules/concat-stream": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-2.0.0.tgz",
+      "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+      "engines": [
+        "node >= 6.0"
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.0.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "node_modules/condense-newlines": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmmirror.com/condense-newlines/-/condense-newlines-0.2.1.tgz",
+      "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==",
+      "license": "MIT",
+      "dependencies": {
+        "extend-shallow": "^2.0.1",
+        "is-whitespace": "^0.3.0",
+        "kind-of": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/config-chain": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz",
+      "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ini": "^1.3.4",
+        "proto-list": "~1.2.1"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookies": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmmirror.com/cookies/-/cookies-0.9.1.tgz",
+      "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~2.0.0",
+        "keygrip": "~1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/cron-parser": {
+      "version": "4.9.0",
+      "resolved": "https://registry.npmmirror.com/cron-parser/-/cron-parser-4.9.0.tgz",
+      "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
+      "license": "MIT",
+      "dependencies": {
+        "luxon": "^3.2.1"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/cross-spawn": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/crypt": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz",
+      "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/date-fns": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-4.1.0.tgz",
+      "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/kossnocorp"
+      }
+    },
+    "node_modules/dateformat": {
+      "version": "4.6.3",
+      "resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-4.6.3.tgz",
+      "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/decode-uri-component": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
+      "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.16"
+      }
+    },
+    "node_modules/decompress-response": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz",
+      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "mimic-response": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/deep-equal": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.0.1.tgz",
+      "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==",
+      "license": "MIT"
+    },
+    "node_modules/deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+      "license": "MIT"
+    },
+    "node_modules/denque": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz",
+      "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.3.tgz",
+      "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+      "devOptional": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/dezalgo": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/dezalgo/-/dezalgo-1.0.4.tgz",
+      "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
+      "license": "ISC",
+      "dependencies": {
+        "asap": "^2.0.0",
+        "wrappy": "1"
+      }
+    },
+    "node_modules/dir-glob": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
+      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "path-type": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.4.5",
+      "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.5.tgz",
+      "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/dottie": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/dottie/-/dottie-2.0.6.tgz",
+      "integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==",
+      "license": "MIT"
+    },
+    "node_modules/duplexify": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.3.tgz",
+      "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
+      "license": "MIT",
+      "dependencies": {
+        "end-of-stream": "^1.4.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1",
+        "stream-shift": "^1.0.2"
+      }
+    },
+    "node_modules/eastasianwidth": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+      "license": "MIT"
+    },
+    "node_modules/ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/editorconfig": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/editorconfig/-/editorconfig-1.0.4.tgz",
+      "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@one-ini/wasm": "0.1.1",
+        "commander": "^10.0.0",
+        "minimatch": "9.0.1",
+        "semver": "^7.5.3"
+      },
+      "bin": {
+        "editorconfig": "bin/editorconfig"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/editorconfig/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/editorconfig/node_modules/commander": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-10.0.1.tgz",
+      "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/editorconfig/node_modules/minimatch": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.1.tgz",
+      "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/ejs": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmmirror.com/ejs/-/ejs-3.1.10.tgz",
+      "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "jake": "^10.8.5"
+      },
+      "bin": {
+        "ejs": "bin/cli.js"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "license": "MIT",
+      "dependencies": {
+        "once": "^1.4.0"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/event-target-shim": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/event-target-shim/-/event-target-shim-5.0.1.tgz",
+      "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/events": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz",
+      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8.x"
+      }
+    },
+    "node_modules/expand-template": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz",
+      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+      "dev": true,
+      "license": "(MIT OR WTFPL)",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/extend-shallow": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz",
+      "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+      "license": "MIT",
+      "dependencies": {
+        "is-extendable": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/fast-copy": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/fast-copy/-/fast-copy-3.0.2.tgz",
+      "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/fast-glob": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz",
+      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/fast-redact": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.5.0.tgz",
+      "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/fast-safe-stringify": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+      "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/fast-unique-numbers": {
+      "version": "8.0.13",
+      "resolved": "https://registry.npmmirror.com/fast-unique-numbers/-/fast-unique-numbers-8.0.13.tgz",
+      "integrity": "sha512-7OnTFAVPefgw2eBJ1xj2PGGR9FwYzSUso9decayHgCDX4sJkHLdcsYTytTg+tYv+wKF3U8gJuSBz2jJpQV4u/g==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.23.8",
+        "tslib": "^2.6.2"
+      },
+      "engines": {
+        "node": ">=16.1.0"
+      }
+    },
+    "node_modules/fast-url-parser": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz",
+      "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==",
+      "license": "MIT",
+      "dependencies": {
+        "punycode": "^1.3.2"
+      }
+    },
+    "node_modules/fastq": {
+      "version": "1.18.0",
+      "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.18.0.tgz",
+      "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "node_modules/filelist": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz",
+      "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "minimatch": "^5.0.1"
+      }
+    },
+    "node_modules/filelist/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/filelist/node_modules/minimatch": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.6.tgz",
+      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/filter-obj": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmmirror.com/filter-obj/-/filter-obj-5.1.0.tgz",
+      "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.16"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/foreground-child": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.0.tgz",
+      "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+      "license": "ISC",
+      "dependencies": {
+        "cross-spawn": "^7.0.0",
+        "signal-exit": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.1.tgz",
+      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/formidable": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/formidable/-/formidable-2.1.2.tgz",
+      "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
+      "license": "MIT",
+      "dependencies": {
+        "dezalgo": "^1.0.4",
+        "hexoid": "^1.0.0",
+        "once": "^1.4.0",
+        "qs": "^6.11.0"
+      },
+      "funding": {
+        "url": "https://ko-fi.com/tunnckoCore/commissions"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/from2": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/from2/-/from2-2.3.0.tgz",
+      "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
+      }
+    },
+    "node_modules/from2/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/from2/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/from2/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "node_modules/fs-constants": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz",
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/fs-extra": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz",
+      "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "at-least-node": "^1.0.0",
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/generic-pool": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmmirror.com/generic-pool/-/generic-pool-3.9.0.tgz",
+      "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "has-proto": "^1.0.1",
+        "has-symbols": "^1.0.3",
+        "hasown": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-paths": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmmirror.com/get-paths/-/get-paths-0.0.7.tgz",
+      "integrity": "sha512-0wdJt7C1XKQxuCgouqd+ZvLJ56FQixKoki9MrFaO4EriqzXOiH9gbukaDE1ou08S8Ns3/yDzoBAISNPqj6e6tA==",
+      "license": "MIT",
+      "dependencies": {
+        "pify": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=6.4"
+      }
+    },
+    "node_modules/get-tsconfig": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
+      "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "resolve-pkg-maps": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+      }
+    },
+    "node_modules/github-from-package": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz",
+      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/glob": {
+      "version": "10.4.5",
+      "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz",
+      "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+      "license": "ISC",
+      "dependencies": {
+        "foreground-child": "^3.1.0",
+        "jackspeak": "^3.1.2",
+        "minimatch": "^9.0.4",
+        "minipass": "^7.1.2",
+        "package-json-from-dist": "^1.0.0",
+        "path-scurry": "^1.11.1"
+      },
+      "bin": {
+        "glob": "dist/esm/bin.mjs"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/glob/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/glob/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/globby": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz",
+      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.2.9",
+        "ignore": "^5.2.0",
+        "merge2": "^1.4.1",
+        "slash": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz",
+      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.1.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/has": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/has/-/has-1.0.4.tgz",
+      "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-proto": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz",
+      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/help-me": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/help-me/-/help-me-5.0.0.tgz",
+      "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
+      "license": "MIT"
+    },
+    "node_modules/hexoid": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/hexoid/-/hexoid-1.0.0.tgz",
+      "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/http-assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/http-assert/-/http-assert-1.5.0.tgz",
+      "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==",
+      "license": "MIT",
+      "dependencies": {
+        "deep-equal": "~1.0.1",
+        "http-errors": "~1.8.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.8.1.tgz",
+      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/http-errors/node_modules/depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/https-proxy-agent": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "6",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/i18next": {
+      "version": "23.16.5",
+      "resolved": "https://registry.npmmirror.com/i18next/-/i18next-23.16.5.tgz",
+      "integrity": "sha512-KTlhE3EP9x6pPTAW7dy0WKIhoCpfOGhRQlO+jttQLgzVaoOjWwBWramu7Pp0i+8wDNduuzXfe3kkVbzrKyrbTA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://locize.com"
+        },
+        {
+          "type": "individual",
+          "url": "https://locize.com/i18next.html"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.23.2"
+      }
+    },
+    "node_modules/i18next-fs-backend": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmmirror.com/i18next-fs-backend/-/i18next-fs-backend-2.3.2.tgz",
+      "integrity": "sha512-LIwUlkqDZnUI8lnUxBnEj8K/FrHQTT/Sc+1rvDm9E8YvvY5YxzoEAASNx+W5M9DfD5s77lI5vSAFWeTp26B/3Q==",
+      "license": "MIT"
+    },
+    "node_modules/i18next-http-middleware": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/i18next-http-middleware/-/i18next-http-middleware-3.6.0.tgz",
+      "integrity": "sha512-pLyTOC8Dzj83byN0s4hd/i/Ewg6T36YjMrc+Zfnqz2Ca0G5ab9IPvPR8xZqr6TS0s/ZtPs2MZucDkWgqoRmNXA==",
+      "license": "MIT"
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/ignore": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4"
+      }
+    },
+    "node_modules/indent-string": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz",
+      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/inflation": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/inflation/-/inflation-2.1.0.tgz",
+      "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/inflection": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmmirror.com/inflection/-/inflection-1.13.4.tgz",
+      "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==",
+      "engines": [
+        "node >= 0.4.0"
+      ],
+      "license": "MIT"
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ini": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz",
+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+      "license": "ISC"
+    },
+    "node_modules/into-stream": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/into-stream/-/into-stream-6.0.0.tgz",
+      "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "from2": "^2.3.0",
+        "p-is-promise": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/ioredis": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmmirror.com/ioredis/-/ioredis-5.4.1.tgz",
+      "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==",
+      "license": "MIT",
+      "dependencies": {
+        "@ioredis/commands": "^1.1.1",
+        "cluster-key-slot": "^1.1.0",
+        "debug": "^4.3.4",
+        "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"
+      },
+      "engines": {
+        "node": ">=12.22.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/ioredis"
+      }
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+      "license": "MIT"
+    },
+    "node_modules/is-core-module": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.9.0.tgz",
+      "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has": "^1.0.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-generator-function": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.0.10.tgz",
+      "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+      "license": "MIT",
+      "dependencies": {
+        "has-tostringtag": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/is-retry-allowed": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
+      "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/is-whitespace": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/is-whitespace/-/is-whitespace-0.3.0.tgz",
+      "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "license": "ISC"
+    },
+    "node_modules/jackspeak": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz",
+      "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "@isaacs/cliui": "^8.0.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      },
+      "optionalDependencies": {
+        "@pkgjs/parseargs": "^0.11.0"
+      }
+    },
+    "node_modules/jake": {
+      "version": "10.9.2",
+      "resolved": "https://registry.npmmirror.com/jake/-/jake-10.9.2.tgz",
+      "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "async": "^3.2.3",
+        "chalk": "^4.0.2",
+        "filelist": "^1.0.4",
+        "minimatch": "^3.1.2"
+      },
+      "bin": {
+        "jake": "bin/cli.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/joycon": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz",
+      "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/js-beautify": {
+      "version": "1.15.1",
+      "resolved": "https://registry.npmmirror.com/js-beautify/-/js-beautify-1.15.1.tgz",
+      "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
+      "license": "MIT",
+      "dependencies": {
+        "config-chain": "^1.1.13",
+        "editorconfig": "^1.0.4",
+        "glob": "^10.3.3",
+        "js-cookie": "^3.0.5",
+        "nopt": "^7.2.0"
+      },
+      "bin": {
+        "css-beautify": "js/bin/css-beautify.js",
+        "html-beautify": "js/bin/html-beautify.js",
+        "js-beautify": "js/bin/js-beautify.js"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/js-cookie": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz",
+      "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/js-sdsl": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/js-sdsl/-/js-sdsl-4.3.0.tgz",
+      "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/js-sdsl"
+      }
+    },
+    "node_modules/jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/jsonfile": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz",
+      "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "universalify": "^2.0.0"
+      },
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "node_modules/jsonwebtoken": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+      "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+      "license": "MIT",
+      "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.0.0",
+        "ms": "^2.1.1",
+        "semver": "^7.5.4"
+      },
+      "engines": {
+        "node": ">=12",
+        "npm": ">=6"
+      }
+    },
+    "node_modules/jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "license": "MIT",
+      "dependencies": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "node_modules/kareem": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmmirror.com/kareem/-/kareem-2.6.3.tgz",
+      "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/keygrip": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/keygrip/-/keygrip-1.1.0.tgz",
+      "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "tsscmp": "1.0.6"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/kind-of": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/kind-of/-/kind-of-3.2.2.tgz",
+      "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "is-buffer": "^1.1.5"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/koa": {
+      "version": "2.15.3",
+      "resolved": "https://registry.npmmirror.com/koa/-/koa-2.15.3.tgz",
+      "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "^1.3.5",
+        "cache-content-type": "^1.0.0",
+        "content-disposition": "~0.5.2",
+        "content-type": "^1.0.4",
+        "cookies": "~0.9.0",
+        "debug": "^4.3.2",
+        "delegates": "^1.0.0",
+        "depd": "^2.0.0",
+        "destroy": "^1.0.4",
+        "encodeurl": "^1.0.2",
+        "escape-html": "^1.0.3",
+        "fresh": "~0.5.2",
+        "http-assert": "^1.3.0",
+        "http-errors": "^1.6.3",
+        "is-generator-function": "^1.0.7",
+        "koa-compose": "^4.1.0",
+        "koa-convert": "^2.0.0",
+        "on-finished": "^2.3.0",
+        "only": "~0.0.2",
+        "parseurl": "^1.3.2",
+        "statuses": "^1.5.0",
+        "type-is": "^1.6.16",
+        "vary": "^1.1.2"
+      },
+      "engines": {
+        "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4"
+      }
+    },
+    "node_modules/koa-body": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/koa-body/-/koa-body-6.0.1.tgz",
+      "integrity": "sha512-M8ZvMD8r+kPHy28aWP9VxL7kY8oPWA+C7ZgCljrCMeaU7uX6wsIQgDHskyrAr9sw+jqnIXyv4Mlxri5R4InIJg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/co-body": "^6.1.0",
+        "@types/formidable": "^2.0.5",
+        "@types/koa": "^2.13.5",
+        "co-body": "^6.1.0",
+        "formidable": "^2.0.1",
+        "zod": "^3.19.1"
+      }
+    },
+    "node_modules/koa-compose": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/koa-compose/-/koa-compose-4.1.0.tgz",
+      "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
+      "license": "MIT"
+    },
+    "node_modules/koa-convert": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/koa-convert/-/koa-convert-2.0.0.tgz",
+      "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==",
+      "license": "MIT",
+      "dependencies": {
+        "co": "^4.6.0",
+        "koa-compose": "^4.1.0"
+      },
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/koa-i18next-detector": {
+      "version": "0.7.2",
+      "resolved": "https://registry.npmmirror.com/koa-i18next-detector/-/koa-i18next-detector-0.7.2.tgz",
+      "integrity": "sha512-RXgwEB7ADH9h5380v94Gk7i82+PxSap5BKf+XC+aYAKEoPiqnn6i2mNYuO3801hADLutb5YBCMqvIe1xpT+5hw==",
+      "license": "MIT",
+      "dependencies": {
+        "cookies": "^0.7.0"
+      }
+    },
+    "node_modules/koa-i18next-detector/node_modules/cookies": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmmirror.com/cookies/-/cookies-0.7.3.tgz",
+      "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~1.1.2",
+        "keygrip": "~1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/koa-i18next-detector/node_modules/depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/koa-i18next-detector/node_modules/keygrip": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/keygrip/-/keygrip-1.0.3.tgz",
+      "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/koa-jwt": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/koa-jwt/-/koa-jwt-4.0.4.tgz",
+      "integrity": "sha512-Tid9BQfpVtUG/8YZV38a+hDKll0pfVhfl7A/2cNaYThS1cxMFXylZzfARqHQqvNhHy9qM+qkxd4/z6EaIV4SAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "jsonwebtoken": "^9.0.0",
+        "koa-unless": "^1.0.7",
+        "p-any": "^2.1.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/koa-mount": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/koa-mount/-/koa-mount-4.0.0.tgz",
+      "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.0.1",
+        "koa-compose": "^4.1.0"
+      },
+      "engines": {
+        "node": ">= 7.6.0"
+      }
+    },
+    "node_modules/koa-pino-logger": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/koa-pino-logger/-/koa-pino-logger-4.0.0.tgz",
+      "integrity": "sha512-YI/LB9ajyLPpjvf6e+7Ewmn+OQkCJpu/Y9eI1n7fnipu5Y1NchuNlke0mqh3/2z+5oDYr7pijjOWruEDIfua2A==",
+      "license": "MIT",
+      "dependencies": {
+        "pino-http": "^6.5.0"
+      }
+    },
+    "node_modules/koa-pino-logger/node_modules/on-exit-leak-free": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz",
+      "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==",
+      "license": "MIT"
+    },
+    "node_modules/koa-pino-logger/node_modules/pino": {
+      "version": "7.11.0",
+      "resolved": "https://registry.npmmirror.com/pino/-/pino-7.11.0.tgz",
+      "integrity": "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==",
+      "license": "MIT",
+      "dependencies": {
+        "atomic-sleep": "^1.0.0",
+        "fast-redact": "^3.0.0",
+        "on-exit-leak-free": "^0.2.0",
+        "pino-abstract-transport": "v0.5.0",
+        "pino-std-serializers": "^4.0.0",
+        "process-warning": "^1.0.0",
+        "quick-format-unescaped": "^4.0.3",
+        "real-require": "^0.1.0",
+        "safe-stable-stringify": "^2.1.0",
+        "sonic-boom": "^2.2.1",
+        "thread-stream": "^0.15.1"
+      },
+      "bin": {
+        "pino": "bin.js"
+      }
+    },
+    "node_modules/koa-pino-logger/node_modules/pino-abstract-transport": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz",
+      "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==",
+      "license": "MIT",
+      "dependencies": {
+        "duplexify": "^4.1.2",
+        "split2": "^4.0.0"
+      }
+    },
+    "node_modules/koa-pino-logger/node_modules/pino-http": {
+      "version": "6.6.0",
+      "resolved": "https://registry.npmmirror.com/pino-http/-/pino-http-6.6.0.tgz",
+      "integrity": "sha512-PlItaK2MLpoIMLEcClhfb1VQk/o6fKppINl5s6sPE/4rvufkdO3kCSs/92EwrBsB1yssRCQqDV+w1xpYuPVnjg==",
+      "license": "MIT",
+      "dependencies": {
+        "fast-url-parser": "^1.1.3",
+        "get-caller-file": "^2.0.5",
+        "pino": "^7.5.0",
+        "pino-std-serializers": "^5.0.0"
+      }
+    },
+    "node_modules/koa-pino-logger/node_modules/pino-std-serializers": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-5.6.0.tgz",
+      "integrity": "sha512-VdUXCw8gO+xhir7sFuoYSjTnzB+TMDGxhAC/ph3YS3sdHnXNdsK0wMtADNUltfeGkn2KDxEM21fnjF3RwXyC8A==",
+      "license": "MIT"
+    },
+    "node_modules/koa-pino-logger/node_modules/pino/node_modules/pino-std-serializers": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz",
+      "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==",
+      "license": "MIT"
+    },
+    "node_modules/koa-pino-logger/node_modules/process-warning": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz",
+      "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==",
+      "license": "MIT"
+    },
+    "node_modules/koa-pino-logger/node_modules/real-require": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.1.0.tgz",
+      "integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12.13.0"
+      }
+    },
+    "node_modules/koa-pino-logger/node_modules/thread-stream": {
+      "version": "0.15.2",
+      "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-0.15.2.tgz",
+      "integrity": "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==",
+      "license": "MIT",
+      "dependencies": {
+        "real-require": "^0.1.0"
+      }
+    },
+    "node_modules/koa-router": {
+      "version": "13.0.1",
+      "resolved": "https://registry.npmmirror.com/koa-router/-/koa-router-13.0.1.tgz",
+      "integrity": "sha512-4/sijXdSxocIe2wv7RFFSxvo2ic1pDzPSmy11yCGztng1hx408qfw1wVmN3aqhQaU7U6nJ039JKC8ObE73Ohgw==",
+      "license": "MIT",
+      "dependencies": {
+        "http-errors": "^2.0.0",
+        "koa-compose": "^4.1.0",
+        "path-to-regexp": "^8.1.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
+    "node_modules/koa-router/node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/koa-router/node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/koa-send": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/koa-send/-/koa-send-5.0.1.tgz",
+      "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.1.1",
+        "http-errors": "^1.7.3",
+        "resolve-path": "^1.4.0"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/koa-static": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/koa-static/-/koa-static-5.0.0.tgz",
+      "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^3.1.0",
+        "koa-send": "^5.0.0"
+      },
+      "engines": {
+        "node": ">= 7.6.0"
+      }
+    },
+    "node_modules/koa-static/node_modules/debug": {
+      "version": "3.2.7",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz",
+      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.1"
+      }
+    },
+    "node_modules/koa-unless": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/koa-unless/-/koa-unless-1.0.7.tgz",
+      "integrity": "sha512-NKiz+nk4KxSJFskiJMuJvxeA41Lcnx3d8Zy+8QETgifm4ab4aOeGD3RgR6bIz0FGNWwo3Fz0DtnK77mEIqHWxA==",
+      "license": "MIT"
+    },
+    "node_modules/koa-zod-errorhandler": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/koa-zod-errorhandler/-/koa-zod-errorhandler-1.0.0.tgz",
+      "integrity": "sha512-7/bdsc0SOXmJQpF3vCopipwORfN2Uw70vCrYbXh2r0vsRCw43Wrumg7+glU9S44QQNW/2CJBqRJPjDeDNxGW5g==",
+      "license": "MIT",
+      "peerDependencies": {
+        "zod": "*"
+      }
+    },
+    "node_modules/koa2-cors": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/koa2-cors/-/koa2-cors-2.0.6.tgz",
+      "integrity": "sha512-JRCcSM4lamM+8kvKGDKlesYk2ASrmSTczDtGUnIadqMgnHU4Ct5Gw7Bxt3w3m6d6dy3WN0PU4oMP43HbddDEWg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 7.6.0"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.defaults": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+      "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.includes": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz",
+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isarguments": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+      "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isboolean": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isinteger": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isnumber": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "license": "MIT"
+    },
+    "node_modules/lodash.once": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz",
+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+      "license": "MIT"
+    },
+    "node_modules/lru-cache": {
+      "version": "10.4.3",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
+      "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+      "license": "ISC"
+    },
+    "node_modules/luxon": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.5.0.tgz",
+      "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/md5": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz",
+      "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "charenc": "0.0.2",
+        "crypt": "0.0.2",
+        "is-buffer": "~1.1.6"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "license": "MIT"
+    },
+    "node_modules/merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "braces": "^3.0.3",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-response": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz",
+      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/minipass": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz",
+      "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
+    "node_modules/mkdirp-classic": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/moment": {
+      "version": "2.30.1",
+      "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
+      "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/moment-timezone": {
+      "version": "0.5.46",
+      "resolved": "https://registry.npmmirror.com/moment-timezone/-/moment-timezone-0.5.46.tgz",
+      "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
+      "license": "MIT",
+      "dependencies": {
+        "moment": "^2.29.4"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/mongodb": {
+      "version": "6.10.0",
+      "resolved": "https://registry.npmmirror.com/mongodb/-/mongodb-6.10.0.tgz",
+      "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@mongodb-js/saslprep": "^1.1.5",
+        "bson": "^6.7.0",
+        "mongodb-connection-string-url": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=16.20.1"
+      },
+      "peerDependencies": {
+        "@aws-sdk/credential-providers": "^3.188.0",
+        "@mongodb-js/zstd": "^1.1.0",
+        "gcp-metadata": "^5.2.0",
+        "kerberos": "^2.0.1",
+        "mongodb-client-encryption": ">=6.0.0 <7",
+        "snappy": "^7.2.2",
+        "socks": "^2.7.1"
+      },
+      "peerDependenciesMeta": {
+        "@aws-sdk/credential-providers": {
+          "optional": true
+        },
+        "@mongodb-js/zstd": {
+          "optional": true
+        },
+        "gcp-metadata": {
+          "optional": true
+        },
+        "kerberos": {
+          "optional": true
+        },
+        "mongodb-client-encryption": {
+          "optional": true
+        },
+        "snappy": {
+          "optional": true
+        },
+        "socks": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/mongodb-connection-string-url": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
+      "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@types/whatwg-url": "^11.0.2",
+        "whatwg-url": "^13.0.0"
+      }
+    },
+    "node_modules/mongoose": {
+      "version": "8.8.3",
+      "resolved": "https://registry.npmmirror.com/mongoose/-/mongoose-8.8.3.tgz",
+      "integrity": "sha512-/I4n/DcXqXyIiLRfAmUIiTjj3vXfeISke8dt4U4Y8Wfm074Wa6sXnQrXN49NFOFf2mM1kUdOXryoBvkuCnr+Qw==",
+      "license": "MIT",
+      "dependencies": {
+        "bson": "^6.7.0",
+        "kareem": "2.6.3",
+        "mongodb": "~6.10.0",
+        "mpath": "0.9.0",
+        "mquery": "5.0.0",
+        "ms": "2.1.3",
+        "sift": "17.1.3"
+      },
+      "engines": {
+        "node": ">=16.20.1"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mongoose"
+      }
+    },
+    "node_modules/mpath": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmmirror.com/mpath/-/mpath-0.9.0.tgz",
+      "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/mqtt": {
+      "version": "5.10.3",
+      "resolved": "https://registry.npmmirror.com/mqtt/-/mqtt-5.10.3.tgz",
+      "integrity": "sha512-hA/6YrUS4fywhBGCjH/XXUuLeueJiPqruVVWjK2A24Ma4KcWfZ/x8x07aoesBV+HXDWBC08tbT4IWfSXNW0Jtw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/readable-stream": "^4.0.5",
+        "@types/ws": "^8.5.9",
+        "commist": "^3.2.0",
+        "concat-stream": "^2.0.0",
+        "debug": "^4.3.4",
+        "help-me": "^5.0.0",
+        "lru-cache": "^10.0.1",
+        "minimist": "^1.2.8",
+        "mqtt-packet": "^9.0.1",
+        "number-allocator": "^1.0.14",
+        "readable-stream": "^4.4.2",
+        "reinterval": "^1.1.0",
+        "rfdc": "^1.3.0",
+        "split2": "^4.2.0",
+        "worker-timers": "^7.1.4",
+        "ws": "^8.17.1"
+      },
+      "bin": {
+        "mqtt": "build/bin/mqtt.js",
+        "mqtt_pub": "build/bin/pub.js",
+        "mqtt_sub": "build/bin/sub.js"
+      },
+      "engines": {
+        "node": ">=16.0.0"
+      }
+    },
+    "node_modules/mqtt-packet": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmmirror.com/mqtt-packet/-/mqtt-packet-9.0.1.tgz",
+      "integrity": "sha512-koZF1V/X2RZUI6uD9wN5OK1JxxcG1ofAR4H3LjCw1FkeKzruZQ26aAA6v2m1lZyWONZIR5wMMJFrZJDRNzbiQw==",
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^6.0.8",
+        "debug": "^4.3.4",
+        "process-nextick-args": "^2.0.1"
+      }
+    },
+    "node_modules/mqtt/node_modules/readable-stream": {
+      "version": "4.5.2",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-4.5.2.tgz",
+      "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
+      "license": "MIT",
+      "dependencies": {
+        "abort-controller": "^3.0.0",
+        "buffer": "^6.0.3",
+        "events": "^3.3.0",
+        "process": "^0.11.10",
+        "string_decoder": "^1.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/mquery": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/mquery/-/mquery-5.0.0.tgz",
+      "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "4.x"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/msgpackr": {
+      "version": "1.11.2",
+      "resolved": "https://registry.npmmirror.com/msgpackr/-/msgpackr-1.11.2.tgz",
+      "integrity": "sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==",
+      "license": "MIT",
+      "optionalDependencies": {
+        "msgpackr-extract": "^3.0.2"
+      }
+    },
+    "node_modules/msgpackr-extract": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
+      "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "node-gyp-build-optional-packages": "5.2.2"
+      },
+      "bin": {
+        "download-msgpackr-prebuilds": "bin/download-prebuilds.js"
+      },
+      "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"
+      }
+    },
+    "node_modules/multistream": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/multistream/-/multistream-4.1.0.tgz",
+      "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "once": "^1.4.0",
+        "readable-stream": "^3.6.0"
+      }
+    },
+    "node_modules/mylas": {
+      "version": "2.1.13",
+      "resolved": "https://registry.npmmirror.com/mylas/-/mylas-2.1.13.tgz",
+      "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/raouldeheer"
+      }
+    },
+    "node_modules/mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "node_modules/napi-build-utils": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
+      "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-abi": {
+      "version": "3.71.0",
+      "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.71.0.tgz",
+      "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.3.5"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/node-abort-controller": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
+      "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
+      "license": "MIT"
+    },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/node-fetch/node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/node-fetch/node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "dev": true,
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/node-fetch/node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
+    "node_modules/node-gyp-build-optional-packages": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmmirror.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
+      "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "detect-libc": "^2.0.1"
+      },
+      "bin": {
+        "node-gyp-build-optional-packages": "bin.js",
+        "node-gyp-build-optional-packages-optional": "optional.js",
+        "node-gyp-build-optional-packages-test": "build-test.js"
+      }
+    },
+    "node_modules/nopt": {
+      "version": "7.2.1",
+      "resolved": "https://registry.npmmirror.com/nopt/-/nopt-7.2.1.tgz",
+      "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
+      "license": "ISC",
+      "dependencies": {
+        "abbrev": "^2.0.0"
+      },
+      "bin": {
+        "nopt": "bin/nopt.js"
+      },
+      "engines": {
+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+      }
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/number-allocator": {
+      "version": "1.0.14",
+      "resolved": "https://registry.npmmirror.com/number-allocator/-/number-allocator-1.0.14.tgz",
+      "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.3.1",
+        "js-sdsl": "4.3.0"
+      }
+    },
+    "node_modules/object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-exit-leak-free": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+      "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/only": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmmirror.com/only/-/only-0.0.2.tgz",
+      "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ=="
+    },
+    "node_modules/p-any": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/p-any/-/p-any-2.1.0.tgz",
+      "integrity": "sha512-JAERcaMBLYKMq+voYw36+x5Dgh47+/o7yuv2oQYuSSUml4YeqJEFznBrY2UeEkoSHqBua6hz518n/PsowTYLLg==",
+      "license": "MIT",
+      "dependencies": {
+        "p-cancelable": "^2.0.0",
+        "p-some": "^4.0.0",
+        "type-fest": "^0.3.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/p-cancelable": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz",
+      "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/p-is-promise": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/p-is-promise/-/p-is-promise-3.0.0.tgz",
+      "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/p-some": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/p-some/-/p-some-4.1.0.tgz",
+      "integrity": "sha512-MF/HIbq6GeBqTrTIl5OJubzkGU+qfFhAFi0gnTAK6rgEIJIknEiABHOTtQu4e6JiXjIwuMPMUFQzyHh5QjCl1g==",
+      "license": "MIT",
+      "dependencies": {
+        "aggregate-error": "^3.0.0",
+        "p-cancelable": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/package-json-from-dist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+      "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+      "license": "BlueOak-1.0.0"
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/path-scurry": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz",
+      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "lru-cache": "^10.2.0",
+        "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+      "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/pg": {
+      "version": "8.13.1",
+      "resolved": "https://registry.npmmirror.com/pg/-/pg-8.13.1.tgz",
+      "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-connection-string": "^2.7.0",
+        "pg-pool": "^3.7.0",
+        "pg-protocol": "^1.7.0",
+        "pg-types": "^2.1.0",
+        "pgpass": "1.x"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      },
+      "optionalDependencies": {
+        "pg-cloudflare": "^1.1.1"
+      },
+      "peerDependencies": {
+        "pg-native": ">=3.0.1"
+      },
+      "peerDependenciesMeta": {
+        "pg-native": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pg-cloudflare": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
+      "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/pg-connection-string": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
+      "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
+      "license": "MIT"
+    },
+    "node_modules/pg-hstore": {
+      "version": "2.3.4",
+      "resolved": "https://registry.npmmirror.com/pg-hstore/-/pg-hstore-2.3.4.tgz",
+      "integrity": "sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==",
+      "license": "MIT",
+      "dependencies": {
+        "underscore": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.8.x"
+      }
+    },
+    "node_modules/pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/pg-pool": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmmirror.com/pg-pool/-/pg-pool-3.7.0.tgz",
+      "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
+      "license": "MIT",
+      "peerDependencies": {
+        "pg": ">=8.0"
+      }
+    },
+    "node_modules/pg-protocol": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmmirror.com/pg-protocol/-/pg-protocol-1.7.0.tgz",
+      "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
+      "license": "MIT"
+    },
+    "node_modules/pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "license": "MIT",
+      "dependencies": {
+        "split2": "^4.1.0"
+      }
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/pify": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz",
+      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/pino": {
+      "version": "9.5.0",
+      "resolved": "https://registry.npmmirror.com/pino/-/pino-9.5.0.tgz",
+      "integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==",
+      "license": "MIT",
+      "dependencies": {
+        "atomic-sleep": "^1.0.0",
+        "fast-redact": "^3.1.1",
+        "on-exit-leak-free": "^2.1.0",
+        "pino-abstract-transport": "^2.0.0",
+        "pino-std-serializers": "^7.0.0",
+        "process-warning": "^4.0.0",
+        "quick-format-unescaped": "^4.0.3",
+        "real-require": "^0.2.0",
+        "safe-stable-stringify": "^2.3.1",
+        "sonic-boom": "^4.0.1",
+        "thread-stream": "^3.0.0"
+      },
+      "bin": {
+        "pino": "bin.js"
+      }
+    },
+    "node_modules/pino-abstract-transport": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
+      "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
+      "license": "MIT",
+      "dependencies": {
+        "split2": "^4.0.0"
+      }
+    },
+    "node_modules/pino-http": {
+      "version": "10.3.0",
+      "resolved": "https://registry.npmmirror.com/pino-http/-/pino-http-10.3.0.tgz",
+      "integrity": "sha512-kaHQqt1i5S9LXWmyuw6aPPqYW/TjoDPizPs4PnDW4hSpajz2Uo/oisNliLf7We1xzpiLacdntmw8yaZiEkppQQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-caller-file": "^2.0.5",
+        "pino": "^9.0.0",
+        "pino-std-serializers": "^7.0.0",
+        "process-warning": "^4.0.0"
+      }
+    },
+    "node_modules/pino-pretty": {
+      "version": "11.3.0",
+      "resolved": "https://registry.npmmirror.com/pino-pretty/-/pino-pretty-11.3.0.tgz",
+      "integrity": "sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "colorette": "^2.0.7",
+        "dateformat": "^4.6.3",
+        "fast-copy": "^3.0.2",
+        "fast-safe-stringify": "^2.1.1",
+        "help-me": "^5.0.0",
+        "joycon": "^3.1.1",
+        "minimist": "^1.2.6",
+        "on-exit-leak-free": "^2.1.0",
+        "pino-abstract-transport": "^2.0.0",
+        "pump": "^3.0.0",
+        "readable-stream": "^4.0.0",
+        "secure-json-parse": "^2.4.0",
+        "sonic-boom": "^4.0.1",
+        "strip-json-comments": "^3.1.1"
+      },
+      "bin": {
+        "pino-pretty": "bin.js"
+      }
+    },
+    "node_modules/pino-pretty/node_modules/readable-stream": {
+      "version": "4.5.2",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-4.5.2.tgz",
+      "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "abort-controller": "^3.0.0",
+        "buffer": "^6.0.3",
+        "events": "^3.3.0",
+        "process": "^0.11.10",
+        "string_decoder": "^1.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/pino-pretty/node_modules/sonic-boom": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-4.2.0.tgz",
+      "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "atomic-sleep": "^1.0.0"
+      }
+    },
+    "node_modules/pino-std-serializers": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
+      "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
+      "license": "MIT"
+    },
+    "node_modules/pino/node_modules/sonic-boom": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-4.2.0.tgz",
+      "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
+      "license": "MIT",
+      "dependencies": {
+        "atomic-sleep": "^1.0.0"
+      }
+    },
+    "node_modules/pkg": {
+      "version": "5.8.1",
+      "resolved": "https://registry.npmmirror.com/pkg/-/pkg-5.8.1.tgz",
+      "integrity": "sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/generator": "7.18.2",
+        "@babel/parser": "7.18.4",
+        "@babel/types": "7.19.0",
+        "chalk": "^4.1.2",
+        "fs-extra": "^9.1.0",
+        "globby": "^11.1.0",
+        "into-stream": "^6.0.0",
+        "is-core-module": "2.9.0",
+        "minimist": "^1.2.6",
+        "multistream": "^4.1.0",
+        "pkg-fetch": "3.4.2",
+        "prebuild-install": "7.1.1",
+        "resolve": "^1.22.0",
+        "stream-meter": "^1.0.4"
+      },
+      "bin": {
+        "pkg": "lib-es5/bin.js"
+      },
+      "peerDependencies": {
+        "node-notifier": ">=9.0.1"
+      },
+      "peerDependenciesMeta": {
+        "node-notifier": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pkg-fetch": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmmirror.com/pkg-fetch/-/pkg-fetch-3.4.2.tgz",
+      "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chalk": "^4.1.2",
+        "fs-extra": "^9.1.0",
+        "https-proxy-agent": "^5.0.0",
+        "node-fetch": "^2.6.6",
+        "progress": "^2.0.3",
+        "semver": "^7.3.5",
+        "tar-fs": "^2.1.1",
+        "yargs": "^16.2.0"
+      },
+      "bin": {
+        "pkg-fetch": "lib-es5/bin.js"
+      }
+    },
+    "node_modules/plimit-lit": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmmirror.com/plimit-lit/-/plimit-lit-1.6.1.tgz",
+      "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "queue-lit": "^1.5.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postgres-bytea": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+      "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/prebuild-install": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.1.tgz",
+      "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "detect-libc": "^2.0.0",
+        "expand-template": "^2.0.3",
+        "github-from-package": "0.0.0",
+        "minimist": "^1.2.3",
+        "mkdirp-classic": "^0.5.3",
+        "napi-build-utils": "^1.0.1",
+        "node-abi": "^3.3.0",
+        "pump": "^3.0.0",
+        "rc": "^1.2.7",
+        "simple-get": "^4.0.0",
+        "tar-fs": "^2.0.0",
+        "tunnel-agent": "^0.6.0"
+      },
+      "bin": {
+        "prebuild-install": "bin.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/pretty": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/pretty/-/pretty-2.0.0.tgz",
+      "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==",
+      "license": "MIT",
+      "dependencies": {
+        "condense-newlines": "^0.2.1",
+        "extend-shallow": "^2.0.1",
+        "js-beautify": "^1.6.12"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
+      "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "license": "MIT"
+    },
+    "node_modules/process-warning": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-4.0.0.tgz",
+      "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==",
+      "license": "MIT"
+    },
+    "node_modules/progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/proto-list": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz",
+      "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+      "license": "ISC"
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/pump": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.2.tgz",
+      "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "node_modules/punycode": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz",
+      "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
+      "license": "MIT"
+    },
+    "node_modules/qs": {
+      "version": "6.13.1",
+      "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.1.tgz",
+      "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.0.6"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/query-string": {
+      "version": "9.1.1",
+      "resolved": "https://registry.npmmirror.com/query-string/-/query-string-9.1.1.tgz",
+      "integrity": "sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==",
+      "license": "MIT",
+      "dependencies": {
+        "decode-uri-component": "^0.4.1",
+        "filter-obj": "^5.1.0",
+        "split-on-first": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/queue-lit": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmmirror.com/queue-lit/-/queue-lit-1.5.2.tgz",
+      "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/quick-format-unescaped": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+      "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
+      "license": "MIT"
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz",
+      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/raw-body/node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/raw-body/node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
+      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+      "dependencies": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "bin": {
+        "rc": "cli.js"
+      }
+    },
+    "node_modules/rc/node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/real-require": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz",
+      "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 12.13.0"
+      }
+    },
+    "node_modules/redis": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmmirror.com/redis/-/redis-4.7.0.tgz",
+      "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==",
+      "license": "MIT",
+      "workspaces": [
+        "./packages/*"
+      ],
+      "dependencies": {
+        "@redis/bloom": "1.2.0",
+        "@redis/client": "1.6.0",
+        "@redis/graph": "1.1.1",
+        "@redis/json": "1.0.7",
+        "@redis/search": "1.2.0",
+        "@redis/time-series": "1.1.0"
+      }
+    },
+    "node_modules/redis-errors": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/redis-errors/-/redis-errors-1.2.0.tgz",
+      "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/redis-info": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/redis-info/-/redis-info-3.1.0.tgz",
+      "integrity": "sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==",
+      "license": "MIT",
+      "dependencies": {
+        "lodash": "^4.17.11"
+      }
+    },
+    "node_modules/redis-parser": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/redis-parser/-/redis-parser-3.0.0.tgz",
+      "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
+      "license": "MIT",
+      "dependencies": {
+        "redis-errors": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/redlock": {
+      "version": "5.0.0-beta.2",
+      "resolved": "https://registry.npmmirror.com/redlock/-/redlock-5.0.0-beta.2.tgz",
+      "integrity": "sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==",
+      "license": "MIT",
+      "dependencies": {
+        "node-abort-controller": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/regenerator-runtime": {
+      "version": "0.14.1",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+      "license": "MIT"
+    },
+    "node_modules/reinterval": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/reinterval/-/reinterval-1.1.0.tgz",
+      "integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==",
+      "license": "MIT"
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve": {
+      "version": "1.22.10",
+      "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
+      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-core-module": "^2.16.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/resolve-path": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/resolve-path/-/resolve-path-1.4.0.tgz",
+      "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==",
+      "license": "MIT",
+      "dependencies": {
+        "http-errors": "~1.6.2",
+        "path-is-absolute": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/resolve-path/node_modules/depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/resolve-path/node_modules/http-errors": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz",
+      "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.0",
+        "statuses": ">= 1.4.0 < 2"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/resolve-path/node_modules/inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
+      "license": "ISC"
+    },
+    "node_modules/resolve-path/node_modules/setprototypeof": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz",
+      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+      "license": "ISC"
+    },
+    "node_modules/resolve-pkg-maps": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+      "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+      }
+    },
+    "node_modules/resolve/node_modules/is-core-module": {
+      "version": "2.16.1",
+      "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/retry-as-promised": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmmirror.com/retry-as-promised/-/retry-as-promised-7.0.4.tgz",
+      "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==",
+      "license": "MIT"
+    },
+    "node_modules/reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "iojs": ">=1.0.0",
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rfdc": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
+      "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+      "license": "MIT"
+    },
+    "node_modules/run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safe-stable-stringify": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+      "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/secure-json-parse": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+      "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
+      "dev": true,
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/sequelize": {
+      "version": "6.37.5",
+      "resolved": "https://registry.npmmirror.com/sequelize/-/sequelize-6.37.5.tgz",
+      "integrity": "sha512-10WA4poUb3XWnUROThqL2Apq9C2NhyV1xHPMZuybNMCucDsbbFuKg51jhmyvvAUyUqCiimwTZamc3AHhMoBr2Q==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/sequelize"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "@types/debug": "^4.1.8",
+        "@types/validator": "^13.7.17",
+        "debug": "^4.3.4",
+        "dottie": "^2.0.6",
+        "inflection": "^1.13.4",
+        "lodash": "^4.17.21",
+        "moment": "^2.29.4",
+        "moment-timezone": "^0.5.43",
+        "pg-connection-string": "^2.6.1",
+        "retry-as-promised": "^7.0.4",
+        "semver": "^7.5.4",
+        "sequelize-pool": "^7.1.0",
+        "toposort-class": "^1.0.1",
+        "uuid": "^8.3.2",
+        "validator": "^13.9.0",
+        "wkx": "^0.5.0"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependenciesMeta": {
+        "ibm_db": {
+          "optional": true
+        },
+        "mariadb": {
+          "optional": true
+        },
+        "mysql2": {
+          "optional": true
+        },
+        "oracledb": {
+          "optional": true
+        },
+        "pg": {
+          "optional": true
+        },
+        "pg-hstore": {
+          "optional": true
+        },
+        "snowflake-sdk": {
+          "optional": true
+        },
+        "sqlite3": {
+          "optional": true
+        },
+        "tedious": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/sequelize-pool": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmmirror.com/sequelize-pool/-/sequelize-pool-7.1.0.tgz",
+      "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/set-function-length": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
+      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "gopd": "^1.0.1",
+        "has-property-descriptors": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "license": "MIT",
+      "dependencies": {
+        "shebang-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz",
+      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/sift": {
+      "version": "17.1.3",
+      "resolved": "https://registry.npmmirror.com/sift/-/sift-17.1.3.tgz",
+      "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
+      "license": "MIT"
+    },
+    "node_modules/signal-exit": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz",
+      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/simple-concat": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz",
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/simple-get": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz",
+      "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "decompress-response": "^6.0.0",
+        "once": "^1.3.1",
+        "simple-concat": "^1.0.0"
+      }
+    },
+    "node_modules/slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/sonic-boom": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-2.8.0.tgz",
+      "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==",
+      "license": "MIT",
+      "dependencies": {
+        "atomic-sleep": "^1.0.0"
+      }
+    },
+    "node_modules/sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+      "license": "MIT",
+      "dependencies": {
+        "memory-pager": "^1.0.2"
+      }
+    },
+    "node_modules/split-on-first": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz",
+      "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 10.x"
+      }
+    },
+    "node_modules/standard-as-callback": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+      "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
+      "license": "MIT"
+    },
+    "node_modules/statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/stream-meter": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmmirror.com/stream-meter/-/stream-meter-1.0.4.tgz",
+      "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "readable-stream": "^2.1.4"
+      }
+    },
+    "node_modules/stream-meter/node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/stream-meter/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/stream-meter/node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "node_modules/stream-shift": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz",
+      "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
+      "license": "MIT"
+    },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/string-width-cjs": {
+      "name": "string-width",
+      "version": "4.2.3",
+      "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi-cjs": {
+      "name": "strip-ansi",
+      "version": "6.0.1",
+      "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/tar-fs": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.1.tgz",
+      "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chownr": "^1.1.1",
+        "mkdirp-classic": "^0.5.2",
+        "pump": "^3.0.0",
+        "tar-stream": "^2.1.4"
+      }
+    },
+    "node_modules/tar-stream": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz",
+      "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^4.0.3",
+        "end-of-stream": "^1.4.1",
+        "fs-constants": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/tar-stream/node_modules/bl": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz",
+      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
+    },
+    "node_modules/tar-stream/node_modules/buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "node_modules/thenify": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
+      "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+      "license": "MIT",
+      "dependencies": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "node_modules/thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+      "license": "MIT",
+      "dependencies": {
+        "thenify": ">= 3.1.0 < 4"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/thread-stream": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-3.1.0.tgz",
+      "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
+      "license": "MIT",
+      "dependencies": {
+        "real-require": "^0.2.0"
+      }
+    },
+    "node_modules/to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/toposort-class": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/toposort-class/-/toposort-class-1.0.1.tgz",
+      "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==",
+      "license": "MIT"
+    },
+    "node_modules/tr46": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmmirror.com/tr46/-/tr46-4.1.1.tgz",
+      "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
+      "license": "MIT",
+      "dependencies": {
+        "punycode": "^2.3.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/tr46/node_modules/punycode": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/tsc-alias": {
+      "version": "1.8.10",
+      "resolved": "https://registry.npmmirror.com/tsc-alias/-/tsc-alias-1.8.10.tgz",
+      "integrity": "sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^3.5.3",
+        "commander": "^9.0.0",
+        "globby": "^11.0.4",
+        "mylas": "^2.1.9",
+        "normalize-path": "^3.0.0",
+        "plimit-lit": "^1.2.6"
+      },
+      "bin": {
+        "tsc-alias": "dist/bin/index.js"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "license": "0BSD"
+    },
+    "node_modules/tsscmp": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmmirror.com/tsscmp/-/tsscmp-1.0.6.tgz",
+      "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6.x"
+      }
+    },
+    "node_modules/tsx": {
+      "version": "4.19.2",
+      "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.19.2.tgz",
+      "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "~0.23.0",
+        "get-tsconfig": "^4.7.5"
+      },
+      "bin": {
+        "tsx": "dist/cli.mjs"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+      "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/android-arm": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+      "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+      "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/android-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+      "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+      "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+      "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+      "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+      "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+      "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+      "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+      "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+      "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+      "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+      "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+      "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+      "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+      "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+      "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+      "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+      "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+      "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+      "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+      "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+      "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tsx/node_modules/esbuild": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.23.1.tgz",
+      "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.23.1",
+        "@esbuild/android-arm": "0.23.1",
+        "@esbuild/android-arm64": "0.23.1",
+        "@esbuild/android-x64": "0.23.1",
+        "@esbuild/darwin-arm64": "0.23.1",
+        "@esbuild/darwin-x64": "0.23.1",
+        "@esbuild/freebsd-arm64": "0.23.1",
+        "@esbuild/freebsd-x64": "0.23.1",
+        "@esbuild/linux-arm": "0.23.1",
+        "@esbuild/linux-arm64": "0.23.1",
+        "@esbuild/linux-ia32": "0.23.1",
+        "@esbuild/linux-loong64": "0.23.1",
+        "@esbuild/linux-mips64el": "0.23.1",
+        "@esbuild/linux-ppc64": "0.23.1",
+        "@esbuild/linux-riscv64": "0.23.1",
+        "@esbuild/linux-s390x": "0.23.1",
+        "@esbuild/linux-x64": "0.23.1",
+        "@esbuild/netbsd-x64": "0.23.1",
+        "@esbuild/openbsd-arm64": "0.23.1",
+        "@esbuild/openbsd-x64": "0.23.1",
+        "@esbuild/sunos-x64": "0.23.1",
+        "@esbuild/win32-arm64": "0.23.1",
+        "@esbuild/win32-ia32": "0.23.1",
+        "@esbuild/win32-x64": "0.23.1"
+      }
+    },
+    "node_modules/tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmmirror.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.3.1.tgz",
+      "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
+      "license": "(MIT OR CC0-1.0)",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+      "license": "MIT"
+    },
+    "node_modules/typescript": {
+      "version": "5.6.3",
+      "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz",
+      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/underscore": {
+      "version": "1.13.7",
+      "resolved": "https://registry.npmmirror.com/underscore/-/underscore-1.13.7.tgz",
+      "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
+      "license": "MIT"
+    },
+    "node_modules/undici-types": {
+      "version": "6.19.8",
+      "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+      "license": "MIT"
+    },
+    "node_modules/universalify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
+      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/uuid": {
+      "version": "8.3.2",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/validator": {
+      "version": "13.12.0",
+      "resolved": "https://registry.npmmirror.com/validator/-/validator-13.12.0.tgz",
+      "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/webidl-conversions": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/whatwg-url": {
+      "version": "13.0.0",
+      "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-13.0.0.tgz",
+      "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "^4.1.1",
+        "webidl-conversions": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
+    "node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/wkx": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmmirror.com/wkx/-/wkx-0.5.0.tgz",
+      "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/worker-timers": {
+      "version": "7.1.8",
+      "resolved": "https://registry.npmmirror.com/worker-timers/-/worker-timers-7.1.8.tgz",
+      "integrity": "sha512-R54psRKYVLuzff7c1OTFcq/4Hue5Vlz4bFtNEIarpSiCYhpifHU3aIQI29S84o1j87ePCYqbmEJPqwBTf+3sfw==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.24.5",
+        "tslib": "^2.6.2",
+        "worker-timers-broker": "^6.1.8",
+        "worker-timers-worker": "^7.0.71"
+      }
+    },
+    "node_modules/worker-timers-broker": {
+      "version": "6.1.8",
+      "resolved": "https://registry.npmmirror.com/worker-timers-broker/-/worker-timers-broker-6.1.8.tgz",
+      "integrity": "sha512-FUCJu9jlK3A8WqLTKXM9E6kAmI/dR1vAJ8dHYLMisLNB/n3GuaFIjJ7pn16ZcD1zCOf7P6H62lWIEBi+yz/zQQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.24.5",
+        "fast-unique-numbers": "^8.0.13",
+        "tslib": "^2.6.2",
+        "worker-timers-worker": "^7.0.71"
+      }
+    },
+    "node_modules/worker-timers-worker": {
+      "version": "7.0.71",
+      "resolved": "https://registry.npmmirror.com/worker-timers-worker/-/worker-timers-worker-7.0.71.tgz",
+      "integrity": "sha512-ks/5YKwZsto1c2vmljroppOKCivB/ma97g9y77MAAz2TBBjPPgpoOiS1qYQKIgvGTr2QYPT3XhJWIB6Rj2MVPQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.24.5",
+        "tslib": "^2.6.2"
+      }
+    },
+    "node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi-cjs": {
+      "name": "wrap-ansi",
+      "version": "7.0.0",
+      "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/ws": {
+      "version": "8.18.0",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "license": "ISC"
+    },
+    "node_modules/yargs": {
+      "version": "16.2.0",
+      "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz",
+      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^7.0.2",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.0",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^20.2.2"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "20.2.9",
+      "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz",
+      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+      "dev": true,
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/ylru": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/ylru/-/ylru-1.4.0.tgz",
+      "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
+    "node_modules/zod": {
+      "version": "3.23.8",
+      "resolved": "https://registry.npmmirror.com/zod/-/zod-3.23.8.tgz",
+      "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/colinhacks"
+      }
+    }
+  }
+}

+ 88 - 0
package.json

@@ -0,0 +1,88 @@
+{
+  "name": "bee-server",
+  "private": true,
+  "version": "1.0.0",
+  "description": "bee server",
+  "type": "module",
+  "module": "index.js",
+  "main": "index.js",
+  "scripts": {
+    "dev": "NODE_ENV=development nodemon --watch src -e ts,tsx --exec tsx ./src/index.ts | pino-pretty",
+    "pro": "NODE_ENV=production node ./dist/index.js | pino-pretty",
+    "build": "rm -rf dist && tsc && tsc-alias -f",
+    "pack": "NODE_ENV=production pkg .",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "bin": "dist/index.js",
+  "pkg": {
+    "scripts": "dist/**/*.js",
+    "assets": [
+      "dist/**/*.json",
+      ".env",
+      ".env.production"
+    ],
+    "targets": [
+      "node14-linux-arm64"
+    ],
+    "outputPath": "bin"
+  },
+  "keywords": [
+    "koa"
+  ],
+  "author": "jerry",
+  "license": "ISC",
+  "dependencies": {
+    "@bull-board/api": "^6.6.2",
+    "@bull-board/koa": "^6.6.2",
+    "axios": "^1.7.7",
+    "axios-retry": "^4.5.0",
+    "bcryptjs": "^2.4.3",
+    "bullmq": "^5.28.2",
+    "date-fns": "^4.1.0",
+    "dotenv": "^16.4.5",
+    "i18next": "^23.16.5",
+    "i18next-fs-backend": "^2.3.2",
+    "i18next-http-middleware": "^3.6.0",
+    "ioredis": "^5.4.1",
+    "jsonwebtoken": "^9.0.2",
+    "koa": "^2.15.3",
+    "koa-body": "^6.0.1",
+    "koa-i18next-detector": "^0.7.2",
+    "koa-jwt": "^4.0.4",
+    "koa-pino-logger": "^4.0.0",
+    "koa-router": "^13.0.1",
+    "koa-zod-errorhandler": "^1.0.0",
+    "koa2-cors": "^2.0.6",
+    "lodash": "^4.17.21",
+    "md5": "^2.3.0",
+    "mongoose": "^8.8.3",
+    "mqtt": "^5.10.3",
+    "pg": "^8.13.1",
+    "pg-hstore": "^2.3.4",
+    "pino": "^9.5.0",
+    "pino-http": "^10.3.0",
+    "qs": "^6.13.1",
+    "query-string": "^9.1.1",
+    "redis": "^4.7.0",
+    "redlock": "^5.0.0-beta.2",
+    "sequelize": "^6.37.5",
+    "zod": "^3.23.8"
+  },
+  "devDependencies": {
+    "@types/bcryptjs": "^2.4.6",
+    "@types/bull": "^3.15.9",
+    "@types/jsonwebtoken": "^9.0.7",
+    "@types/koa": "^2.15.0",
+    "@types/koa-pino-logger": "^3.0.4",
+    "@types/koa-router": "^7.4.8",
+    "@types/koa2-cors": "^2.0.5",
+    "@types/lodash": "^4.17.13",
+    "@types/md5": "^2.3.5",
+    "@types/node": "^22.9.0",
+    "pino-pretty": "^11.3.0",
+    "pkg": "^5.8.1",
+    "tsc-alias": "^1.8.10",
+    "tsx": "^4.19.2",
+    "typescript": "^5.6.3"
+  }
+}

+ 20 - 0
src/api/accountManage/index.ts

@@ -0,0 +1,20 @@
+import z from 'zod'
+import koa from 'koa'
+import { AccountManage } from './interface'
+
+export interface ReqSaveAccount extends AccountManage.ReqSaveAccount {}
+
+export const URL = {
+  getAccountInfo: '/account-info',
+  postAccountInfo: '/account-info',
+}
+
+export function checkReqSaveAccount(ctx: koa.Context): ReqSaveAccount {
+  const schemaReqSaveAccount = z.object({
+    server: z.coerce.string(),
+    fwh_username: z.coerce.string(),
+    fwh_password: z.coerce.string()
+  })
+
+  return schemaReqSaveAccount.parse(ctx.request.body)
+}

+ 18 - 0
src/api/accountManage/interface.ts

@@ -0,0 +1,18 @@
+// 账户信息管理模块
+export namespace AccountManage {
+  export interface OptionServer {
+    label: string;
+    value: string;
+  }
+  export interface ResAccountInfo {
+    server: string;
+    server_opt: OptionServer[];
+    fwh_username: string;
+    fwh_password_length: number;
+  }
+  export interface ReqSaveAccount {
+    server: string;
+    fwh_username: string;
+    fwh_password: string;
+  }
+}

+ 4 - 0
src/api/auth/index.ts

@@ -0,0 +1,4 @@
+export const URL = {
+  getMenuAuth: '/menu/list',
+  getButtonAuth: '/auth/buttons'
+}

+ 21 - 0
src/api/auth/interface.ts

@@ -0,0 +1,21 @@
+/* Menu */
+export namespace Menu {
+  export interface MenuOptions {
+    path: string;
+    name: string;
+    component?: string | (() => Promise<unknown>);
+    redirect?: string;
+    meta: MetaProps;
+    children?: MenuOptions[];
+  }
+  interface MetaProps {
+    icon: string;
+    title: string;
+    activeMenu?: string;
+    isLink?: string;
+    isHide: boolean;
+    isFull: boolean;
+    isAffix: boolean;
+    isKeepAlive: boolean;
+  }
+}

+ 62 - 0
src/api/cloudLog/index.ts

@@ -0,0 +1,62 @@
+import z from 'zod'
+import koa from 'koa'
+import { CloudLog } from './interface'
+
+export interface ReqCloudLogParams extends CloudLog.ReqCloudLogParams {}
+export interface ReqCreateLogParams extends CloudLog.ReqCreateLogParams {}
+export interface ReqCloudLogData extends CloudLog.ReqCloudLogData {}
+export interface ReqCloudLogDataPage extends CloudLog.ReqCloudLogDataPage {}
+
+export const URL = {
+  getCloudLogList: '/cloud-data/cloud-log',
+  postCloudLog: '/cloud-data/cloud-log',
+  getLogType: '/cloud-data/cloud-log/log-type',
+  getCloudLogData: '/cloud-data/cloud-log/site-data',
+  getCloudLogDataPage: '/cloud-data/cloud-log/site-data-page'
+}
+
+export function checkReqCloudLogParams(ctx: koa.Context): ReqCloudLogParams {
+  const schemaReqCloudLogParams = z.object({
+    serialNumber: z.coerce.string().optional(),
+    status: z.coerce.number().optional(),
+    pageNum: z.coerce.number(),
+    pageSize: z.coerce.number()
+  })
+
+  return schemaReqCloudLogParams.parse(ctx.request.body)
+}
+
+export function checkReqCreateLogParams(ctx: koa.Context): ReqCreateLogParams {
+  const schemaReqCreateLogParams = z.object({
+    serialNumberList: z.coerce.string().array(),
+    logTypes: z.coerce.string().array(),
+    startTime: z.coerce.date(),
+    endTime: z.coerce.date()
+  })
+
+  return schemaReqCreateLogParams.parse(ctx.request.body)
+}
+
+export function checkReqCloudLogData(ctx: koa.Context): ReqCloudLogData {
+  const schemaReqCloudLogData = z.object({
+    serialNumber: z.coerce.string(),
+    logTypes: z.coerce.string().array(),
+    startTime: z.coerce.date(),
+    endTime: z.coerce.date()
+  })
+  
+  return schemaReqCloudLogData.parse(ctx.request.body)
+}
+
+export function checkReqCloudLogDataPage(ctx: koa.Context): ReqCloudLogDataPage {
+  const schemaReqCloudLogParams = z.object({
+    serialNumber: z.coerce.string(),
+    logTypes: z.coerce.string().array().optional(),
+    startTime: z.coerce.date().optional(),
+    endTime: z.coerce.date().optional(),
+    pageNum: z.coerce.number(),
+    pageSize: z.coerce.number()
+  })
+
+  return schemaReqCloudLogParams.parse(ctx.request.body)
+}

+ 49 - 0
src/api/cloudLog/interface.ts

@@ -0,0 +1,49 @@
+import { ReqPage, ResPage } from "../public";
+
+// 云日志模块
+export namespace CloudLog {
+  export interface ReqCloudLogParams extends ReqPage {
+    serialNumber?: string;
+    status?: number;
+  }
+  export interface CloudLogListItem {
+    serialNumber: string;
+    count: number;
+    updateTime: Date;
+    status: number;
+  }
+  export interface ResCloudLogListPage extends ResPage<CloudLogListItem>  {}
+  export interface ResLogType {
+    label: string;
+    value: number;
+  }
+  export interface ReqCreateLogParams {
+    serialNumberList: string[];
+    logTypes: string[];
+    startTime: Date;
+    endTime: Date;
+  }
+  export interface ReqCloudLogData {
+    serialNumber: string;
+    logTypes: string[];
+    startTime: Date;
+    endTime: Date;
+  }
+  export interface ResCloudLogData {
+    type: number;
+    time: Date;
+    data: any;
+  }
+  export interface ReqCloudLogDataPage extends ReqPage {
+    serialNumber: string;
+    logTypes?: string[];
+    startTime?: Date;
+    endTime?: Date;
+  }
+  export interface ResCloudLogDataPage {
+    type: number;
+    typeName: string;
+    time: Date;
+    data: any;
+  }
+}

+ 20 - 0
src/api/emsLog/index.ts

@@ -0,0 +1,20 @@
+import z from 'zod'
+import koa from 'koa'
+import { EmsLog } from './interface'
+
+export interface ReqEmsLogListPage extends EmsLog.ReqEmsLogListPage {}
+
+export const URL = {
+  getEmsLogList: '/ems-data/ems-log'
+}
+
+export function checkReqEmsLogListPage(ctx: koa.Context): ReqEmsLogListPage {
+  const schemaReqEmsLogListPage = z.object({
+    serialNumber: z.coerce.string().optional(),
+    status: z.coerce.number().optional(),
+    pageNum: z.coerce.number(),
+    pageSize: z.coerce.number()
+  })
+
+  return schemaReqEmsLogListPage.parse(ctx.request.body)
+}

+ 11 - 0
src/api/emsLog/interface.ts

@@ -0,0 +1,11 @@
+import { ReqPage } from "../public";
+import { DownloadInfo } from '@/external/fwh/api/getEmsLog/dataType'
+
+// EMS日志模块
+export namespace EmsLog {
+  export interface ReqEmsLogListPage extends ReqPage {
+    serialNumber?: string;
+    status?: number; // -1 - 失败; 0 - 召唤中; 1 - 成功
+  }
+  export interface ResEmsLogListData extends DownloadInfo {}
+}

+ 52 - 0
src/api/public.ts

@@ -0,0 +1,52 @@
+// 请求响应参数(不包含data)
+export interface Result {
+  code: number;
+  msg: string;
+}
+
+// 请求响应参数(包含data)
+export interface ResultData<T = any> extends Result {
+  data: T;
+}
+
+// 分页响应参数
+export interface ResPage<T> {
+  list: T[];
+  pageNum: number;
+  pageSize: number;
+  total: number;
+}
+
+// 分页请求参数
+export interface ReqPage {
+  pageNum: number;
+  pageSize: number;
+}
+
+// 文件上传模块
+export namespace Upload {
+  export interface ResFileUrl {
+    fileUrl: string;
+  }
+}
+
+// 自定义函数处理数组查询参数
+export function parseQueryArrays(query: any) {
+  const result: any = {};
+
+  for (const [key, value] of Object.entries(query)) {
+    // 如果查询参数的键以 [] 结尾,则将其处理为数组
+    if (key.endsWith('[]')) {
+      const newKey = key.slice(0, -2);  // 去掉 "[]"
+      if (!result[newKey]) {
+        result[newKey] = [];
+      }
+      // 处理多个相同名称的参数
+      result[newKey].push(value);
+    } else {
+      result[key] = value;
+    }
+  }
+
+  return result;
+}

+ 43 - 0
src/api/siteManage/index.ts

@@ -0,0 +1,43 @@
+import z from 'zod'
+import koa from 'koa'
+import { SiteManage } from './interface'
+
+export interface ReqSiteInfo extends SiteManage.ReqSiteInfo {}
+export interface ReqSiteTags extends SiteManage.ReqSiteTags {}
+
+export const URL = {
+  getSiteManageInfo: '/operations/operate/site-manage',
+  postRefreshSiteInfo: '/operations/operate/site-manage',
+  getSiteOfflineInfo: '/operations/operate/site-manage-offline',
+  postRefreshOfflineSiteInfo: '/operations/operate/site-manage-offline',
+  eventsRefreshSiteInfo: '/operations/operate/site-manage/events',
+  getSiteInfo: '/operations/operate/site-info',
+  postSiteTags: '/operations/operate/site-tags',
+  getSiteMap: '/operations/operate/site-map',
+}
+
+export function checkReqSiteInfoParams(ctx: koa.Context): ReqSiteInfo {
+  const schemaReqSiteInfoParams = z.object({
+    serialNumber: z.coerce.string().optional(),
+    apowerSn: z.coerce.string().optional(),
+    apowerNum: z.coerce.number().optional(),
+    activeStatus: z.coerce.number().optional(),
+    networkStatus: z.coerce.number().optional(),
+    country: z.coerce.string().optional(),
+    province: z.coerce.string().array().optional(),
+    tags: z.coerce.string().array().optional(),
+    pageNum: z.coerce.number(),
+    pageSize: z.coerce.number()
+  })
+
+  return schemaReqSiteInfoParams.parse(ctx.request.body)
+}
+
+export function checkReqSiteTagsParams(ctx: koa.Context): ReqSiteTags {
+  const schemaReqSiteTagsParams = z.object({
+    serialNumber: z.coerce.string(),
+    tags: z.coerce.string().array()
+  })
+
+  return schemaReqSiteTagsParams.parse(ctx.request.body)
+}

+ 54 - 0
src/api/siteManage/interface.ts

@@ -0,0 +1,54 @@
+import { ReqPage } from "../public";
+
+// 站点管理模块
+export namespace SiteManage {
+  export type RefreshStatus = "RefreshFailed" | "NotInProgress" | "Refreshing" | "RefreshCompleted";
+
+  export interface ResSiteManageInfo {
+    updateTime: Date;
+    siteCount: number;
+    updateStatus: RefreshStatus;
+  }
+
+  export interface ReqSiteInfo extends ReqPage {
+    serialNumber?: string;
+    apowerSn?: string;
+    apowerNum?: number;
+    activeStatus?: number;
+    networkStatus?: number;
+    country?: string;
+    province?: string[];
+    tags?: string[];
+  }
+
+  export interface ResSiteInfo {
+    serialNumber: string;
+    activeStatus: number; // 激活状态:0-未激活,1-已激活
+    networkStatus: number; // 网络状态:0-离线,1-在线
+    iccId: string;
+    imei: string;
+    apowerSn: string[];
+    apowerNum: number;
+    timeZone: number;
+    zoneInfo: string;
+    country: string;
+    province: string;
+    city: string;
+    deviceAddress: string;
+    tags?: string[];
+
+    rawData: object;
+  }
+
+  export interface ReqSiteTags {
+    serialNumber: string;
+    tags: string[];
+  }
+
+  // 请求站点地图数据
+  export interface ResSiteMapData {
+    state: string;
+    online: number;
+    offline: number;
+  }
+}

+ 21 - 0
src/api/user/index.ts

@@ -0,0 +1,21 @@
+import z from 'zod'
+import koa from 'koa'
+import { Login } from './interface'
+
+export interface ReqLogin extends Login.ReqLoginForm {}
+
+export const URL = {
+  getKeyPair: '/key-pair',
+  postRegister: '/register',
+  postLogin: '/login',
+  postLogout: '/logout'
+}
+
+export function checkReqLogin(ctx: koa.Context): ReqLogin {
+  const schemaReqLogin = z.object({
+    username: z.coerce.string(),
+    password: z.coerce.string()
+  })
+
+  return schemaReqLogin.parse(ctx.request.body)
+}

+ 67 - 0
src/api/user/interface.ts

@@ -0,0 +1,67 @@
+import { ReqPage } from "../public"
+
+// 登录模块
+export namespace Login {
+  export interface ReqLoginForm {
+    username: string;
+    password: string;
+  }
+  export interface ResLogin {
+    success: boolean;
+    msg: string;
+    access_token?: string;
+  }
+  export interface ResAuthButtons {
+    [key: string]: string[];
+  }
+  export interface ReqRegisterForm extends ReqLoginForm {}
+  export interface ResRegister {
+    success: boolean;
+    msg: string;
+  }
+}
+
+// 用户管理模块
+export namespace User {
+  export interface ReqUserParams extends ReqPage {
+    username: string;
+    gender: number;
+    idCard: string;
+    email: string;
+    address: string;
+    createTime: string[];
+    status: number;
+  }
+  export interface ResUserList {
+    id: string;
+    username: string;
+    gender: number;
+    user: { detail: { age: number } };
+    idCard: string;
+    email: string;
+    address: string;
+    createTime: string;
+    status: number;
+    avatar: string;
+    photo: any[];
+    children?: ResUserList[];
+  }
+  export interface ResStatus {
+    userLabel: string;
+    userValue: number;
+  }
+  export interface ResGender {
+    genderLabel: string;
+    genderValue: number;
+  }
+  export interface ResDepartment {
+    id: string;
+    name: string;
+    children?: ResDepartment[];
+  }
+  export interface ResRole {
+    id: string;
+    name: string;
+    children?: ResDepartment[];
+  }
+}

+ 22 - 0
src/api/userInfo/index.ts

@@ -0,0 +1,22 @@
+import z from 'zod'
+import koa from 'koa'
+import { UserInfo } from './interface'
+
+export interface ReqSaveUserInfo extends UserInfo.ReqSaveUserInfo {}
+
+export const URL = {
+  getUserInfo: '/user/info',
+  postUserInfo: '/user/info'
+}
+
+export function checkReqSaveUserInfo(ctx: koa.Context): ReqSaveUserInfo {
+  const schemaReqSaveUserInfo = z.object({
+    fwhAccount: z.object({
+      server: z.coerce.string(),
+      fwh_username: z.coerce.string(),
+      fwh_password: z.coerce.string()
+    })
+  })
+
+  return schemaReqSaveUserInfo.parse(ctx.request.body)
+}

+ 28 - 0
src/api/userInfo/interface.ts

@@ -0,0 +1,28 @@
+// 用户信息模块
+export namespace UserInfo {
+  export interface ResUserInfo {
+    username: string;
+    fwhAccount: FwhAccountInfo;
+  }
+  export interface ReqSaveUserInfo {
+    fwhAccount: SaveFwhAccount;
+  }
+
+  export interface OptionServer {
+    label: string;
+    value: string;
+  }
+  export interface FwhAccountInfo {
+    server: string;
+    server_opt: OptionServer[];
+    fwh_pro_username: string;
+    fwh_pro_password_length: number;
+    fwh_tst_username: string;
+    fwh_tst_password_length: number;
+  }
+  export interface SaveFwhAccount {
+    server: string;
+    fwh_username: string;
+    fwh_password: string;
+  }
+}

+ 88 - 0
src/app/index.ts

@@ -0,0 +1,88 @@
+import koa from 'koa'
+import { koaBody } from 'koa-body'
+import koaJwt from 'koa-jwt'
+import { SECRET_KEY } from '@/utils/jwt'
+import cors from 'koa2-cors'
+import logger, { koaLoggerMiddleware } from '@/logger'
+import i18n from '@/utils/i18next'
+import { zodErrorHandler } from 'koa-zod-errorhandler'
+import qs from 'qs'
+import BullBoard from '@/queue/board.queue'
+
+import router from '@/router'
+
+import handleError from '@/constant/err.type'
+
+// 暂时不需要使用到MQTT
+// import mqttClient from '@/utils/mqtt'
+
+const app = new koa()
+
+app.use(koaLoggerMiddleware)
+
+// 打印每次收到的请求和回复的消息
+app.use(async (ctx, next) => {
+  logger.debug<object>({ request: ctx.request }, 'Request received.')
+  await next()
+  logger.debug<object>({ response: ctx.response }, 'Response Information.')
+})
+
+app.use(BullBoard.registerPlugin())
+
+app.use(cors({
+  origin: '*',  // 允许所有来源访问
+  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],  // 允许的方法
+  allowHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'],  // 允许的请求头
+}))
+
+app.use(
+	koaBody({
+		multipart: true,    //解析多个文件
+		formidable: {
+			maxFileSize: 10 * 1024 * 1024,      // 设置上传文件大小最大限制,默认2M
+			// uploadDir: 可以填写一个路径,不填默认为 os.tmpDir()
+		}
+	})
+)
+
+// 将 x-access-token 转换为 Authorization 请求头(兼容x-access-token)
+app.use(async (ctx, next) => {
+  if (ctx.headers['x-access-token']) {
+    // 如果 x-access-token 存在,转换为 Authorization: Bearer <token>
+    ctx.headers['authorization'] = 'Bearer ' + ctx.headers['x-access-token'];
+    delete ctx.headers['x-access-token']; // 删除原来的 x-access-token
+  }
+  await next();
+})
+
+// 将get请求的query解析到body
+app.use(async (ctx, next) => {
+  if (ctx.request.method === 'GET') {
+    const parsedQuery = qs.parse(ctx.querystring); // 使用 qs 库解析查询字符串
+    console.log("ctx.querystring: ", ctx.querystring)
+    console.log("parsedQuery: ", parsedQuery)
+    ctx.request.body = parsedQuery;
+  }
+  console.log("ctx.request.body: ", ctx.request.body)
+  await next();
+})
+
+// 保护所有路由:使用 koa-jwt 中间件
+app.use(koaJwt({ secret: SECRET_KEY, key: 'jwtData' }).unless({
+  path: [/^\/bee\/login$/, /^\/bee\/register$/]  // 排除 /login 和 /register 路由
+}))
+
+app.use(zodErrorHandler({
+  log: (error, ctx) => {
+    logger.error({ Context: ctx }, `ZOD error : ${error.toString()}`)
+  }
+}))
+
+app.use(i18n)
+
+app.use(router.routes()).use(router.allowedMethods())
+
+// 统一的错误处理
+app.on('app error', handleError)
+
+export default app

+ 98 - 0
src/config/config.default.ts

@@ -0,0 +1,98 @@
+import dotenv from 'dotenv'
+import { fileURLToPath, URL } from 'node:url'
+
+dotenv.config()
+
+// 获取当前环境
+const env = process.env.NODE_ENV? process.env.NODE_ENV : 'development'
+
+// 根据环境加载对应的 .env 文件
+const envFile = `../../.env.${env}`;
+dotenv.config({ path: fileURLToPath(new URL(envFile, import.meta.url)) })
+console.log({ env: process.env }, 'Environment variable loading')
+
+type HostConfig = {
+  APP_HOST: string
+  APP_PORT: number
+}
+
+type DbConfig = {
+  DB_TYPE: string
+  DB_HOST: string
+  DB_PORT: number
+  DB_USER: string
+  DB_PWD: string
+  DB_NAME: string
+}
+
+type LogConfig = {
+  LOG_LEVEL: string
+}
+
+type RedisConfig = {
+  RDS_HOST: string
+  RDS_PORT: number
+  RDS_PWD: string | undefined
+
+  RDS_DB_QUEUE: number
+  RDS_DB_CACHE: number
+  RDS_DB_LOCK: number
+}
+
+const { 
+  APP_HOST, 
+  APP_PORT,
+
+  // db config
+  DB_TYPE, 
+  DB_HOST, 
+  DB_PORT, 
+  DB_USER, 
+  DB_PWD, 
+  DB_NAME,
+
+  // log config
+  LOG_LEVEL,
+
+  // redis
+  RDS_HOST,
+  RDS_PORT,
+  RDS_PWD,
+  RDS_DB_QUEUE,
+  RDS_DB_CACHE,
+  RDS_DB_LOCK,
+} = process.env;
+
+function assertUndefined(value: string | undefined, key: string) : string {
+  if (value === undefined) {
+    throw new Error(`Please define ${key} in .env file.`)
+  }
+  return value
+}
+
+export const hostConfig: HostConfig = { 
+  APP_HOST: assertUndefined(APP_HOST, "APP_HOST"), 
+  APP_PORT: Number(assertUndefined(APP_PORT, "APP_PORT"))
+}
+
+export const dbConfig: DbConfig = { 
+  DB_TYPE: assertUndefined(DB_TYPE, "DB_TYPE"), 
+  DB_HOST: assertUndefined(DB_HOST, "DB_HOST"), 
+  DB_PORT: Number(assertUndefined(DB_PORT, "DB_PORT")), 
+  DB_USER: assertUndefined(DB_USER, "DB_USER"), 
+  DB_PWD: assertUndefined(DB_PWD, "DB_PWD"), 
+  DB_NAME: assertUndefined(DB_NAME, "DB_NAME") 
+}
+
+export const logConfig: LogConfig = {
+  LOG_LEVEL: assertUndefined(LOG_LEVEL, "LOG_LEVEL")
+}
+
+export const redisConfig: RedisConfig = {
+  RDS_HOST: assertUndefined(RDS_HOST, "RDS_HOST"),
+  RDS_PORT: Number(assertUndefined(RDS_PORT, "RDS_PORT")),
+  RDS_PWD,
+  RDS_DB_QUEUE: Number(assertUndefined(RDS_DB_QUEUE, "RDS_DB_QUEUE")),
+  RDS_DB_CACHE: Number(assertUndefined(RDS_DB_CACHE, "RDS_DB_CACHE")),
+  RDS_DB_LOCK: Number(assertUndefined(RDS_DB_CACHE, "RDS_DB_LOCK")),
+}

+ 14 - 0
src/constant/auth/about/menu.json

@@ -0,0 +1,14 @@
+{
+    "path": "/about/index",
+    "name": "about",
+    "component": "/about/index",
+    "meta": {
+        "icon": "InfoFilled",
+        "title": "关于项目",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    }
+}

+ 173 - 0
src/constant/auth/assembly/menu.json

@@ -0,0 +1,173 @@
+{
+    "path": "/assembly",
+    "name": "assembly",
+    "redirect": "/assembly/guide",
+    "meta": {
+        "icon": "Briefcase",
+        "title": "常用组件",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/assembly/guide",
+            "name": "guide",
+            "component": "/assembly/guide/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "引导页",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/tabs",
+            "name": "tabs",
+            "component": "/assembly/tabs/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "标签页操作",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            },
+            "children": [
+                {
+                    "path": "/assembly/tabs/detail/:id",
+                    "name": "tabsDetail",
+                    "component": "/assembly/tabs/detail",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "Tab 详情",
+                        "activeMenu": "/assembly/tabs",
+                        "isLink": "",
+                        "isHide": true,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true
+                    }
+                }
+            ]
+        },
+        {
+            "path": "/assembly/selectIcon",
+            "name": "selectIcon",
+            "component": "/assembly/selectIcon/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "图标选择器",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/selectFilter",
+            "name": "selectFilter",
+            "component": "/assembly/selectFilter/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "分类筛选器",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/treeFilter",
+            "name": "treeFilter",
+            "component": "/assembly/treeFilter/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "树形筛选器",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/svgIcon",
+            "name": "svgIcon",
+            "component": "/assembly/svgIcon/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "SVG 图标",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/uploadFile",
+            "name": "uploadFile",
+            "component": "/assembly/uploadFile/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "文件上传",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/batchImport",
+            "name": "batchImport",
+            "component": "/assembly/batchImport/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "批量添加数据",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/wangEditor",
+            "name": "wangEditor",
+            "component": "/assembly/wangEditor/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "富文本编辑器",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/assembly/draggable",
+            "name": "draggable",
+            "component": "/assembly/draggable/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "拖拽组件",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 9 - 0
src/constant/auth/auth/button_admin.json

@@ -0,0 +1,9 @@
+{
+    "authButton": [
+        "add",
+        "edit",
+        "delete",
+        "import",
+        "export"
+    ]
+}

+ 44 - 0
src/constant/auth/auth/menu.json

@@ -0,0 +1,44 @@
+{
+    "path": "/auth",
+    "name": "auth",
+    "redirect": "/auth/menu",
+    "meta": {
+        "icon": "Lock",
+        "title": "权限管理",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/auth/menu",
+            "name": "authMenu",
+            "component": "/auth/menu/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "菜单权限",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/auth/button",
+            "name": "authButton",
+            "component": "/auth/button/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "按钮权限",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 9 - 0
src/constant/auth/cloudData/button_admin.json

@@ -0,0 +1,9 @@
+{
+    "cloudLog": [
+        "add",
+        "batchAdd",
+        "export",
+        "batchDelete",
+        "status"
+    ]
+}

+ 77 - 0
src/constant/auth/cloudData/menu.json

@@ -0,0 +1,77 @@
+{
+    "path": "/cloudData",
+    "name": "cloudData",
+    "redirect": "/cloudData/cloudLog",
+    "meta": {
+        "icon": "MessageBox",
+        "title": "云数据",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/cloudData/cloudLog",
+            "name": "cloudLog",
+            "component": "/cloudData/cloudLog/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "云日志",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            },
+            "children": [
+                {
+                    "path": "/cloudData/cloudLog/detail/:id",
+                    "name": "cloudLogDetail",
+                    "component": "/cloudData/cloudLog/detail",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "云日志详情",
+                        "isLink": "",
+                        "isHide": true,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true,
+                        "activeMenu": "/cloudData/cloudLog"
+                    }
+                },
+                {
+                    "path": "/cloudData/cloudLog/siteView/:id",
+                    "name": "SiteView",
+                    "component": "/cloudData/cloudLog/siteView/index",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "站点信息",
+                        "isLink": "",
+                        "isHide": true,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true,
+                        "activeMenu": "/cloudData/cloudLog"
+                    }
+                },
+                {
+                    "path": "/cloudData/cloudLog/siteData/:id",
+                    "name": "SiteData",
+                    "component": "/cloudData/cloudLog/siteData/index",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "站点数据",
+                        "isLink": "",
+                        "isHide": true,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true,
+                        "activeMenu": "/cloudData/cloudLog"
+                    }
+                }
+            ]
+        }
+    ]
+}

+ 30 - 0
src/constant/auth/dashboard/menu.json

@@ -0,0 +1,30 @@
+{
+    "path": "/dashboard",
+    "name": "dashboard",
+    "redirect": "/dashboard/dataVisualize",
+    "meta": {
+        "icon": "Odometer",
+        "title": "Dashboard",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/dashboard/dataVisualize",
+            "name": "dataVisualize",
+            "component": "/dashboard/dataVisualize/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "数据可视化",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 14 - 0
src/constant/auth/dataScreen/menu.json

@@ -0,0 +1,14 @@
+{
+    "path": "/dataScreen",
+    "name": "dataScreen",
+    "component": "/dataScreen/index",
+    "meta": {
+        "icon": "Histogram",
+        "title": "数据大屏",
+        "isLink": "",
+        "isHide": false,
+        "isFull": true,
+        "isAffix": false,
+        "isKeepAlive": true
+    }
+}

+ 100 - 0
src/constant/auth/directives/menu.json

@@ -0,0 +1,100 @@
+{
+    "path": "/directives",
+    "name": "directives",
+    "redirect": "/directives/copyDirect",
+    "meta": {
+        "icon": "Stamp",
+        "title": "自定义指令",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/directives/copyDirect",
+            "name": "copyDirect",
+            "component": "/directives/copyDirect/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "复制指令",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/directives/watermarkDirect",
+            "name": "watermarkDirect",
+            "component": "/directives/watermarkDirect/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "水印指令",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/directives/dragDirect",
+            "name": "dragDirect",
+            "component": "/directives/dragDirect/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "拖拽指令",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/directives/debounceDirect",
+            "name": "debounceDirect",
+            "component": "/directives/debounceDirect/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "防抖指令",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/directives/throttleDirect",
+            "name": "throttleDirect",
+            "component": "/directives/throttleDirect/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "节流指令",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/directives/longpressDirect",
+            "name": "longpressDirect",
+            "component": "/directives/longpressDirect/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "长按指令",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 100 - 0
src/constant/auth/echarts/menu.json

@@ -0,0 +1,100 @@
+{
+    "path": "/echarts",
+    "name": "echarts",
+    "redirect": "/echarts/waterChart",
+    "meta": {
+        "icon": "TrendCharts",
+        "title": "ECharts",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/echarts/waterChart",
+            "name": "waterChart",
+            "component": "/echarts/waterChart/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "水型图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/echarts/columnChart",
+            "name": "columnChart",
+            "component": "/echarts/columnChart/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "柱状图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/echarts/lineChart",
+            "name": "lineChart",
+            "component": "/echarts/lineChart/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "折线图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/echarts/pieChart",
+            "name": "pieChart",
+            "component": "/echarts/pieChart/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "饼图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/echarts/radarChart",
+            "name": "radarChart",
+            "component": "/echarts/radarChart/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "雷达图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/echarts/nestedChart",
+            "name": "nestedChart",
+            "component": "/echarts/nestedChart/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "嵌套环形图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 9 - 0
src/constant/auth/emsData/button_admin.json

@@ -0,0 +1,9 @@
+{
+    "cloudLog": [
+        "add",
+        "batchAdd",
+        "export",
+        "batchDelete",
+        "status"
+    ]
+}

+ 30 - 0
src/constant/auth/emsData/menu.json

@@ -0,0 +1,30 @@
+{
+    "path": "/emsData",
+    "name": "emsData",
+    "redirect": "/emsData/emsLog",
+    "meta": {
+        "icon": "MessageBox",
+        "title": "EMS数据",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/emsData/emsLog",
+            "name": "emsLog",
+            "component": "/emsData/emsLog/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "EMS日志",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 72 - 0
src/constant/auth/form/menu.json

@@ -0,0 +1,72 @@
+{
+    "path": "/form",
+    "name": "form",
+    "redirect": "/form/proForm",
+    "meta": {
+        "icon": "Tickets",
+        "title": "表单 Form",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/form/proForm",
+            "name": "proForm",
+            "component": "/form/proForm/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "超级 Form",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/form/basicForm",
+            "name": "basicForm",
+            "component": "/form/basicForm/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "基础 Form",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/form/validateForm",
+            "name": "validateForm",
+            "component": "/form/validateForm/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "校验 Form",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/form/dynamicForm",
+            "name": "dynamicForm",
+            "component": "/form/dynamicForm/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "动态 Form",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 14 - 0
src/constant/auth/home/menu.json

@@ -0,0 +1,14 @@
+{
+    "path": "/home/index",
+    "name": "home",
+    "component": "/home/index",
+    "meta": {
+        "icon": "HomeFilled",
+        "title": "首页",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": true,
+        "isKeepAlive": true
+    }
+}

+ 86 - 0
src/constant/auth/link/menu.json

@@ -0,0 +1,86 @@
+{
+    "path": "/link",
+    "name": "link",
+    "redirect": "/link/bing",
+    "meta": {
+        "icon": "Paperclip",
+        "title": "外部链接",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/link/bing",
+            "name": "bing",
+            "component": "/link/bing/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "Bing 内嵌",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/link/gitee",
+            "name": "gitee",
+            "component": "/link/gitee/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "Gitee 仓库",
+                "isLink": "https://gitee.com/HalseySpicy/Geeker-Admin",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/link/github",
+            "name": "github",
+            "component": "/link/github/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "GitHub 仓库",
+                "isLink": "https://github.com/HalseySpicy/Geeker-Admin",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/link/docs",
+            "name": "docs",
+            "component": "/link/docs/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "项目文档",
+                "isLink": "https://docs.spicyboy.cn",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/link/juejin",
+            "name": "juejin",
+            "component": "/link/juejin/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "掘金主页",
+                "isLink": "https://juejin.cn/user/3263814531551816/posts",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 132 - 0
src/constant/auth/menu/menu.json

@@ -0,0 +1,132 @@
+{
+    "path": "/menu",
+    "name": "menu",
+    "redirect": "/menu/menu1",
+    "meta": {
+        "icon": "List",
+        "title": "菜单嵌套",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/menu/menu1",
+            "name": "menu1",
+            "component": "/menu/menu1/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "菜单1",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/menu/menu2",
+            "name": "menu2",
+            "redirect": "/menu/menu2/menu21",
+            "meta": {
+                "icon": "Menu",
+                "title": "菜单2",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            },
+            "children": [
+                {
+                    "path": "/menu/menu2/menu21",
+                    "name": "menu21",
+                    "component": "/menu/menu2/menu21/index",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "菜单2-1",
+                        "isLink": "",
+                        "isHide": false,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true
+                    }
+                },
+                {
+                    "path": "/menu/menu2/menu22",
+                    "name": "menu22",
+                    "redirect": "/menu/menu2/menu22/menu221",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "菜单2-2",
+                        "isLink": "",
+                        "isHide": false,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true
+                    },
+                    "children": [
+                        {
+                            "path": "/menu/menu2/menu22/menu221",
+                            "name": "menu221",
+                            "component": "/menu/menu2/menu22/menu221/index",
+                            "meta": {
+                                "icon": "Menu",
+                                "title": "菜单2-2-1",
+                                "isLink": "",
+                                "isHide": false,
+                                "isFull": false,
+                                "isAffix": false,
+                                "isKeepAlive": true
+                            }
+                        },
+                        {
+                            "path": "/menu/menu2/menu22/menu222",
+                            "name": "menu222",
+                            "component": "/menu/menu2/menu22/menu222/index",
+                            "meta": {
+                                "icon": "Menu",
+                                "title": "菜单2-2-2",
+                                "isLink": "",
+                                "isHide": false,
+                                "isFull": false,
+                                "isAffix": false,
+                                "isKeepAlive": true
+                            }
+                        }
+                    ]
+                },
+                {
+                    "path": "/menu/menu2/menu23",
+                    "name": "menu23",
+                    "component": "/menu/menu2/menu23/index",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "菜单2-3",
+                        "isLink": "",
+                        "isHide": false,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true
+                    }
+                }
+            ]
+        },
+        {
+            "path": "/menu/menu3",
+            "name": "menu3",
+            "component": "/menu/menu3/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "菜单3",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 9 - 0
src/constant/auth/operations/button_admin.json

@@ -0,0 +1,9 @@
+{
+    "operations": [
+        "add",
+        "batchAdd",
+        "export",
+        "batchDelete",
+        "status"
+    ]
+}

+ 30 - 0
src/constant/auth/operations/menu.json

@@ -0,0 +1,30 @@
+{
+    "path": "/operations",
+    "name": "operations",
+    "redirect": "/operations/operate",
+    "meta": {
+        "icon": "Operation",
+        "title": "维护",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/operations/operate",
+            "name": "operate",
+            "component": "/operations/operate/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "操作",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 9 - 0
src/constant/auth/proTable/button.json

@@ -0,0 +1,9 @@
+{
+    "useProTable": [
+        "add",
+        "batchAdd",
+        "export",
+        "batchDelete",
+        "status"
+    ]
+}

+ 132 - 0
src/constant/auth/proTable/menu.json

@@ -0,0 +1,132 @@
+{
+    "path": "/proTable",
+    "name": "proTable",
+    "redirect": "/proTable/useProTable",
+    "meta": {
+        "icon": "MessageBox",
+        "title": "超级表格",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/proTable/useProTable",
+            "name": "useProTable",
+            "component": "/proTable/useProTable/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "使用 ProTable",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            },
+            "children": [
+                {
+                    "path": "/proTable/useProTable/detail/:id",
+                    "name": "useProTableDetail",
+                    "component": "/proTable/useProTable/detail",
+                    "meta": {
+                        "icon": "Menu",
+                        "title": "ProTable 详情",
+                        "isLink": "",
+                        "isHide": true,
+                        "isFull": false,
+                        "isAffix": false,
+                        "isKeepAlive": true,
+                        "activeMenu": "/proTable/useProTable"
+                    }
+                }
+            ]
+        },
+        {
+            "path": "/proTable/useTreeFilter",
+            "name": "useTreeFilter",
+            "component": "/proTable/useTreeFilter/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "使用 TreeFilter",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/proTable/useTreeFilter/detail/:id",
+            "name": "useTreeFilterDetail",
+            "component": "/proTable/useTreeFilter/detail",
+            "meta": {
+                "icon": "Menu",
+                "title": "TreeFilter 详情",
+                "activeMenu": "/proTable/useTreeFilter",
+                "isLink": "",
+                "isHide": true,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/proTable/useSelectFilter",
+            "name": "useSelectFilter",
+            "component": "/proTable/useSelectFilter/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "使用 SelectFilter",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/proTable/treeProTable",
+            "name": "treeProTable",
+            "component": "/proTable/treeProTable/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "树形 ProTable",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/proTable/complexProTable",
+            "name": "complexProTable",
+            "component": "/proTable/complexProTable/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "复杂 ProTable",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/proTable/document",
+            "name": "proTableDocument",
+            "component": "/proTable/document/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "ProTable 文档",
+                "isLink": "https://juejin.cn/post/7166068828202336263/#heading-14",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 86 - 0
src/constant/auth/system/menu.json

@@ -0,0 +1,86 @@
+{
+    "path": "/system",
+    "name": "system",
+    "redirect": "/system/accountManage",
+    "meta": {
+        "icon": "Tools",
+        "title": "系统管理",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/system/accountManage",
+            "name": "accountManage",
+            "component": "/system/accountManage/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "账号管理",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/system/roleManage",
+            "name": "roleManage",
+            "component": "/system/roleManage/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "角色管理",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/system/menuMange",
+            "name": "menuMange",
+            "component": "/system/menuMange/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "菜单管理",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/system/timingTask",
+            "name": "timingTask",
+            "component": "/system/timingTask/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "定时任务",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/system/systemLog",
+            "name": "systemLog",
+            "component": "/system/systemLog/index",
+            "meta": {
+                "icon": "Menu",
+                "title": "系统日志",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 58 - 0
src/constant/auth/toolbox/menu.json

@@ -0,0 +1,58 @@
+{
+    "path": "/toolbox",
+    "name": "toolbox",
+    "redirect": "/toolbox/dataPlotting",
+    "meta": {
+        "icon": "Tools",
+        "title": "工具箱",
+        "isLink": "",
+        "isHide": false,
+        "isFull": false,
+        "isAffix": false,
+        "isKeepAlive": true
+    },
+    "children": [
+        {
+            "path": "/toolbox/dataPlotting",
+            "name": "dataPlotting",
+            "component": "/toolbox/dataPlotting/index",
+            "meta": {
+                "icon": "DataAnalysis",
+                "title": "数据绘图",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/toolbox/logExtract",
+            "name": "logExtract",
+            "component": "/toolbox/logExtract/index",
+            "meta": {
+                "icon": "MagicStick",
+                "title": "日志提取",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        },
+        {
+            "path": "/toolbox/ratePackages",
+            "name": "RatePackages",
+            "component": "/toolbox/ratePackages/index",
+            "meta": {
+                "icon": "Money",
+                "title": "资费套餐",
+                "isLink": "",
+                "isHide": false,
+                "isFull": false,
+                "isAffix": false,
+                "isKeepAlive": true
+            }
+        }
+    ]
+}

+ 189 - 0
src/constant/cloudData/log.type.ts

@@ -0,0 +1,189 @@
+import Koa from 'koa'
+
+export default (ctx: Koa.Context) => {
+
+  return [
+    { label: ctx.t('cloudLog.logType.cmd_0'), value: 0 },
+    { label: ctx.t('cloudLog.logType.cmd_101'), value: 101 },
+    { label: ctx.t('cloudLog.logType.cmd_102'), value: 102 },
+    { label: ctx.t('cloudLog.logType.cmd_103'), value: 103 },
+    { label: ctx.t('cloudLog.logType.cmd_104'), value: 104 },
+    { label: ctx.t('cloudLog.logType.cmd_105'), value: 105 },
+    { label: ctx.t('cloudLog.logType.cmd_106'), value: 106 },
+    { label: ctx.t('cloudLog.logType.cmd_107'), value: 107 },
+    { label: ctx.t('cloudLog.logType.cmd_108'), value: 108 },
+    { label: ctx.t('cloudLog.logType.cmd_109'), value: 109 },
+    { label: ctx.t('cloudLog.logType.cmd_110'), value: 110 },
+    { label: ctx.t('cloudLog.logType.cmd_201'), value: 201 },
+    { label: ctx.t('cloudLog.logType.cmd_202'), value: 202 },
+    { label: ctx.t('cloudLog.logType.cmd_203'), value: 203 },
+    { label: ctx.t('cloudLog.logType.cmd_204'), value: 204 },
+    { label: ctx.t('cloudLog.logType.cmd_205'), value: 205 },
+    { label: ctx.t('cloudLog.logType.cmd_206'), value: 206 },
+    { label: ctx.t('cloudLog.logType.cmd_207'), value: 207 },
+    { label: ctx.t('cloudLog.logType.cmd_208'), value: 208 },
+    { label: ctx.t('cloudLog.logType.cmd_209'), value: 209 },
+    { label: ctx.t('cloudLog.logType.cmd_210'), value: 210 },
+    { label: ctx.t('cloudLog.logType.cmd_211'), value: 211 },
+    { label: ctx.t('cloudLog.logType.cmd_212'), value: 212 },
+    { label: ctx.t('cloudLog.logType.cmd_213'), value: 213 },
+    { label: ctx.t('cloudLog.logType.cmd_214'), value: 214 },
+    { label: ctx.t('cloudLog.logType.cmd_215'), value: 215 },
+    { label: ctx.t('cloudLog.logType.cmd_216'), value: 216 },
+    { label: ctx.t('cloudLog.logType.cmd_217'), value: 217 },
+    { label: ctx.t('cloudLog.logType.cmd_218'), value: 218 },
+    { label: ctx.t('cloudLog.logType.cmd_219'), value: 219 },
+    { label: ctx.t('cloudLog.logType.cmd_220'), value: 220 },
+    { label: ctx.t('cloudLog.logType.cmd_221'), value: 221 },
+    { label: ctx.t('cloudLog.logType.cmd_223'), value: 223 },
+    { label: ctx.t('cloudLog.logType.cmd_224'), value: 224 },
+    { label: ctx.t('cloudLog.logType.cmd_227'), value: 227 },
+    { label: ctx.t('cloudLog.logType.cmd_229'), value: 229 },
+    { label: ctx.t('cloudLog.logType.cmd_230'), value: 230 },
+    { label: ctx.t('cloudLog.logType.cmd_301'), value: 301 },
+    { label: ctx.t('cloudLog.logType.cmd_302'), value: 302 },
+    { label: ctx.t('cloudLog.logType.cmd_303'), value: 303 },
+    { label: ctx.t('cloudLog.logType.cmd_304'), value: 304 },
+    { label: ctx.t('cloudLog.logType.cmd_305'), value: 305 },
+    { label: ctx.t('cloudLog.logType.cmd_306'), value: 306 },
+    { label: ctx.t('cloudLog.logType.cmd_307'), value: 307 },
+    { label: ctx.t('cloudLog.logType.cmd_308'), value: 308 },
+    { label: ctx.t('cloudLog.logType.cmd_309'), value: 309 },
+    { label: ctx.t('cloudLog.logType.cmd_310'), value: 310 },
+    { label: ctx.t('cloudLog.logType.cmd_311'), value: 311 },
+    { label: ctx.t('cloudLog.logType.cmd_312'), value: 312 },
+    { label: ctx.t('cloudLog.logType.cmd_313'), value: 313 },
+    { label: ctx.t('cloudLog.logType.cmd_314'), value: 314 },
+    { label: ctx.t('cloudLog.logType.cmd_315'), value: 315 },
+    { label: ctx.t('cloudLog.logType.cmd_316'), value: 316 },
+    { label: ctx.t('cloudLog.logType.cmd_317'), value: 317 },
+    { label: ctx.t('cloudLog.logType.cmd_318'), value: 318 },
+    { label: ctx.t('cloudLog.logType.cmd_319'), value: 319 },
+    { label: ctx.t('cloudLog.logType.cmd_320'), value: 320 },
+    { label: ctx.t('cloudLog.logType.cmd_321'), value: 321 },
+    { label: ctx.t('cloudLog.logType.cmd_322'), value: 322 },
+    { label: ctx.t('cloudLog.logType.cmd_323'), value: 323 },
+    { label: ctx.t('cloudLog.logType.cmd_324'), value: 324 },
+    { label: ctx.t('cloudLog.logType.cmd_325'), value: 325 },
+    { label: ctx.t('cloudLog.logType.cmd_326'), value: 326 },
+    { label: ctx.t('cloudLog.logType.cmd_327'), value: 327 },
+    { label: ctx.t('cloudLog.logType.cmd_328'), value: 328 },
+    { label: ctx.t('cloudLog.logType.cmd_329'), value: 329 },
+    { label: ctx.t('cloudLog.logType.cmd_330'), value: 330 },
+    { label: ctx.t('cloudLog.logType.cmd_331'), value: 331 },
+    { label: ctx.t('cloudLog.logType.cmd_332'), value: 332 },
+    { label: ctx.t('cloudLog.logType.cmd_333'), value: 333 },
+    { label: ctx.t('cloudLog.logType.cmd_334'), value: 334 },
+    { label: ctx.t('cloudLog.logType.cmd_335'), value: 335 },
+    { label: ctx.t('cloudLog.logType.cmd_336'), value: 336 },
+    { label: ctx.t('cloudLog.logType.cmd_337'), value: 337 },
+    { label: ctx.t('cloudLog.logType.cmd_338'), value: 338 },
+    { label: ctx.t('cloudLog.logType.cmd_339'), value: 339 },
+    { label: ctx.t('cloudLog.logType.cmd_340'), value: 340 },
+    { label: ctx.t('cloudLog.logType.cmd_341'), value: 341 },
+    { label: ctx.t('cloudLog.logType.cmd_342'), value: 342 },
+    { label: ctx.t('cloudLog.logType.cmd_343'), value: 343 },
+    { label: ctx.t('cloudLog.logType.cmd_344'), value: 344 },
+    { label: ctx.t('cloudLog.logType.cmd_345'), value: 345 },
+    { label: ctx.t('cloudLog.logType.cmd_346'), value: 346 },
+    { label: ctx.t('cloudLog.logType.cmd_347'), value: 347 },
+    { label: ctx.t('cloudLog.logType.cmd_348'), value: 348 },
+    { label: ctx.t('cloudLog.logType.cmd_349'), value: 349 },
+    { label: ctx.t('cloudLog.logType.cmd_350'), value: 350 },
+    { label: ctx.t('cloudLog.logType.cmd_351'), value: 351 },
+    { label: ctx.t('cloudLog.logType.cmd_352'), value: 352 },
+    { label: ctx.t('cloudLog.logType.cmd_353'), value: 353 },
+    { label: ctx.t('cloudLog.logType.cmd_354'), value: 354 },
+    { label: ctx.t('cloudLog.logType.cmd_355'), value: 355 },
+    { label: ctx.t('cloudLog.logType.cmd_356'), value: 356 },
+    { label: ctx.t('cloudLog.logType.cmd_357'), value: 357 },
+    { label: ctx.t('cloudLog.logType.cmd_358'), value: 358 },
+    { label: ctx.t('cloudLog.logType.cmd_359'), value: 359 },
+    { label: ctx.t('cloudLog.logType.cmd_360'), value: 360 },
+    { label: ctx.t('cloudLog.logType.cmd_361'), value: 361 },
+    { label: ctx.t('cloudLog.logType.cmd_362'), value: 362 },
+    { label: ctx.t('cloudLog.logType.cmd_363'), value: 363 },
+    { label: ctx.t('cloudLog.logType.cmd_364'), value: 364 },
+    { label: ctx.t('cloudLog.logType.cmd_365'), value: 365 },
+    { label: ctx.t('cloudLog.logType.cmd_366'), value: 366 },
+    { label: ctx.t('cloudLog.logType.cmd_367'), value: 367 },
+    { label: ctx.t('cloudLog.logType.cmd_368'), value: 368 },
+    { label: ctx.t('cloudLog.logType.cmd_369'), value: 369 },
+    { label: ctx.t('cloudLog.logType.cmd_370'), value: 370 },
+    { label: ctx.t('cloudLog.logType.cmd_371'), value: 371 },
+    { label: ctx.t('cloudLog.logType.cmd_372'), value: 372 },
+    { label: ctx.t('cloudLog.logType.cmd_373'), value: 373 },
+    { label: ctx.t('cloudLog.logType.cmd_374'), value: 374 },
+    { label: ctx.t('cloudLog.logType.cmd_375'), value: 375 },
+    { label: ctx.t('cloudLog.logType.cmd_376'), value: 376 },
+    { label: ctx.t('cloudLog.logType.cmd_377'), value: 377 },
+    { label: ctx.t('cloudLog.logType.cmd_378'), value: 378 },
+    { label: ctx.t('cloudLog.logType.cmd_379'), value: 379 },
+    { label: ctx.t('cloudLog.logType.cmd_380'), value: 380 },
+    { label: ctx.t('cloudLog.logType.cmd_381'), value: 381 },
+    { label: ctx.t('cloudLog.logType.cmd_382'), value: 382 },
+    { label: ctx.t('cloudLog.logType.cmd_383'), value: 383 },
+    { label: ctx.t('cloudLog.logType.cmd_384'), value: 384 },
+    { label: ctx.t('cloudLog.logType.cmd_385'), value: 385 },
+    { label: ctx.t('cloudLog.logType.cmd_386'), value: 386 },
+    { label: ctx.t('cloudLog.logType.cmd_387'), value: 387 },
+    { label: ctx.t('cloudLog.logType.cmd_388'), value: 388 },
+    { label: ctx.t('cloudLog.logType.cmd_389'), value: 389 },
+    { label: ctx.t('cloudLog.logType.cmd_390'), value: 390 },
+    { label: ctx.t('cloudLog.logType.cmd_401'), value: 401 },
+    { label: ctx.t('cloudLog.logType.cmd_402'), value: 402 },
+    { label: ctx.t('cloudLog.logType.cmd_403'), value: 403 },
+    { label: ctx.t('cloudLog.logType.cmd_404'), value: 404 },
+    { label: ctx.t('cloudLog.logType.cmd_405'), value: 405 },
+    { label: ctx.t('cloudLog.logType.cmd_406'), value: 406 },
+    { label: ctx.t('cloudLog.logType.cmd_407'), value: 407 },
+    { label: ctx.t('cloudLog.logType.cmd_408'), value: 408 },
+    { label: ctx.t('cloudLog.logType.cmd_409'), value: 409 },
+    { label: ctx.t('cloudLog.logType.cmd_410'), value: 410 },
+    { label: ctx.t('cloudLog.logType.cmd_411'), value: 411 },
+    { label: ctx.t('cloudLog.logType.cmd_412'), value: 412 },
+    { label: ctx.t('cloudLog.logType.cmd_413'), value: 413 },
+    { label: ctx.t('cloudLog.logType.cmd_414'), value: 414 },
+    { label: ctx.t('cloudLog.logType.cmd_501'), value: 501 },
+    { label: ctx.t('cloudLog.logType.cmd_502'), value: 502 },
+    { label: ctx.t('cloudLog.logType.cmd_503'), value: 503 },
+    { label: ctx.t('cloudLog.logType.cmd_504'), value: 504 },
+    { label: ctx.t('cloudLog.logType.cmd_505'), value: 505 },
+    { label: ctx.t('cloudLog.logType.cmd_506'), value: 506 },
+    { label: ctx.t('cloudLog.logType.cmd_507'), value: 507 },
+    { label: ctx.t('cloudLog.logType.cmd_508'), value: 508 },
+    { label: ctx.t('cloudLog.logType.cmd_509'), value: 509 },
+    { label: ctx.t('cloudLog.logType.cmd_510'), value: 510 },
+    { label: ctx.t('cloudLog.logType.cmd_511'), value: 511 },
+    { label: ctx.t('cloudLog.logType.cmd_512'), value: 512 },
+    { label: ctx.t('cloudLog.logType.cmd_513'), value: 513 },
+    { label: ctx.t('cloudLog.logType.cmd_514'), value: 514 },
+    { label: ctx.t('cloudLog.logType.cmd_515'), value: 515 },
+    { label: ctx.t('cloudLog.logType.cmd_516'), value: 516 },
+    { label: ctx.t('cloudLog.logType.cmd_517'), value: 517 },
+    { label: ctx.t('cloudLog.logType.cmd_518'), value: 518 },
+    { label: ctx.t('cloudLog.logType.cmd_601'), value: 601 },
+    { label: ctx.t('cloudLog.logType.cmd_602'), value: 602 },
+    { label: ctx.t('cloudLog.logType.cmd_603'), value: 603 },
+    { label: ctx.t('cloudLog.logType.cmd_604'), value: 604 },
+    { label: ctx.t('cloudLog.logType.cmd_605'), value: 605 },
+    { label: ctx.t('cloudLog.logType.cmd_606'), value: 606 },
+    { label: ctx.t('cloudLog.logType.cmd_607'), value: 607 },
+    { label: ctx.t('cloudLog.logType.cmd_608'), value: 608 },
+    { label: ctx.t('cloudLog.logType.cmd_609'), value: 609 },
+    { label: ctx.t('cloudLog.logType.cmd_610'), value: 610 },
+    { label: ctx.t('cloudLog.logType.cmd_611'), value: 611 },
+    { label: ctx.t('cloudLog.logType.cmd_612'), value: 612 },
+    { label: ctx.t('cloudLog.logType.cmd_613'), value: 613 },
+    { label: ctx.t('cloudLog.logType.cmd_614'), value: 614 },
+    { label: ctx.t('cloudLog.logType.cmd_701'), value: 701 },
+    { label: ctx.t('cloudLog.logType.cmd_702'), value: 702 },
+    { label: ctx.t('cloudLog.logType.cmd_703'), value: 703 },
+    { label: ctx.t('cloudLog.logType.cmd_704'), value: 704 },
+    { label: ctx.t('cloudLog.logType.cmd_705'), value: 705 },
+    { label: ctx.t('cloudLog.logType.cmd_706'), value: 706 },
+    { label: ctx.t('cloudLog.logType.cmd_801'), value: 801 },
+    { label: ctx.t('cloudLog.logType.cmd_802'), value: 802 },
+    { label: ctx.t('cloudLog.logType.cmd_803'), value: 803 },
+  ]
+}

+ 140 - 0
src/constant/err.type.ts

@@ -0,0 +1,140 @@
+import Koa from 'koa'
+
+export enum ErrorCode {
+  NoError = 0,
+
+  // Public
+  RequestParameterError,
+  DatabaseReadFailed,
+  DatabaseUpdateFailed,
+
+  // JWT
+  JwtTokenNotExited,
+  JwtTokenInvalid,
+
+  // 用户登录、信息相关错误
+  UserFormateError,
+  UserAlreadyExited,
+  UserRegisterError,
+  UserNotExited,
+  UserPasswordError,
+  UserNotReviewed,
+
+  // Fwh账户
+  FwhAccountNotExist,
+  FwhAccountSaveFailed,
+  FwhLoginFailed,
+  FwhServerNotExist,
+  FwhServerRequestFailed,
+
+  // CloudApi模块相关错误
+  CloudApiRequestFailed,
+  CloudSerialNumberNotExist,
+  CloudCmdTypeNotExist,
+  CloudTimeRangeError,
+}
+
+export default (errCode: ErrorCode, ctx: Koa.Context) => {
+  let status: number
+  let errMsg: string
+  switch (errCode) {
+    // Public
+    case ErrorCode.RequestParameterError:
+      status = 400
+      errMsg = 'Request parameter error.'
+      break
+    case ErrorCode.DatabaseReadFailed:
+      status = 500
+      errMsg = 'Database Read Failed.'
+      break
+    case ErrorCode.DatabaseUpdateFailed:
+      status = 500
+      errMsg = 'Database Update Failed.'
+      break
+
+    // JWT
+    case ErrorCode.JwtTokenNotExited:
+      status = 401
+      errMsg = 'JWT No token provided.'
+      break
+    case ErrorCode.JwtTokenInvalid:
+      status = 401
+      errMsg = 'JWT Invalid or expired token.'
+      break
+
+    // User
+    case ErrorCode.UserFormateError:
+      status = 400
+      errMsg = 'Username or password is empty.'
+      break
+    case ErrorCode.UserAlreadyExited:
+      status = 409
+      errMsg = 'User already exists.'
+      break
+    case ErrorCode.UserRegisterError:
+      status = 500
+      errMsg = 'Failed to obtain user information.'
+      break
+    case ErrorCode.UserNotExited:
+      status = 500
+      errMsg = 'Invalid user name.'
+      break
+    case ErrorCode.UserPasswordError:
+      status = 500
+      errMsg = 'Invalid password.'
+      break
+    case ErrorCode.UserNotReviewed:
+      status = 500
+      errMsg = 'User not reviewed.'
+      break
+
+    // Fwh Account
+    case ErrorCode.FwhAccountNotExist:
+      status = 500
+      errMsg = 'Fwh account is not exist.'
+      break
+    case ErrorCode.FwhAccountSaveFailed:
+      status = 500
+      errMsg = 'Fwh account save failed.'
+      break
+    case ErrorCode.FwhLoginFailed:
+      status = 500
+      errMsg = 'Fwh account username or password error.'
+      break
+    case ErrorCode.FwhServerNotExist:
+      status = 500
+      errMsg = 'Fwh server is not exist.'
+      break
+    case ErrorCode.FwhServerRequestFailed:
+      status = 500
+      errMsg = 'Fwh server request failed.'
+      break
+
+    // Cloud API
+    case ErrorCode.CloudApiRequestFailed:
+      status = 500
+      errMsg = 'Cloud API Request Failed.'
+      break
+    case ErrorCode.CloudSerialNumberNotExist:
+      status = 404
+      errMsg = 'Cloud Serial number does not exist.'
+      break
+    case ErrorCode.CloudCmdTypeNotExist:
+      status = 404
+      errMsg = 'Cloud cmd type does not exist.'
+      break
+    case ErrorCode.CloudTimeRangeError:
+      status = 404
+      errMsg = 'Cloud wrong time range.'
+      break
+    
+    default:
+      status = 404
+      errMsg = 'Unknown error.'
+  }
+  ctx.status = status
+  ctx.body = errMsg
+  console.log(`error code : ${errCode}`)
+  console.log(`error status : ${status}`)
+  console.log(`error msg : ${errMsg}`)
+}

+ 34 - 0
src/constant/fwh/serverConfig.ts

@@ -0,0 +1,34 @@
+import logger from "@/logger"
+import { ErrorCode } from "../err.type"
+
+const productionServer = {
+  login: "https://oss.franklinwh.com/",
+  access: "https://energy.franklinwh.com/"
+}
+
+const testingServer = {
+  login: "https://production.franklinwh.com/",
+  access: "https://test.franklinwh.com/"
+}
+
+export function getLoginUrl(server: string) {
+  if (server === 'production') {
+    return productionServer.login
+  } else if (server === 'testing') {
+    return testingServer.login
+  } else {
+    logger.error(`error : fwh server is ${server}.`)
+    return ErrorCode.FwhServerNotExist
+  }
+}
+
+export function getAccessUrl(server: string) {
+  if (server === 'production') {
+    return productionServer.access
+  } else if (server === 'testing') {
+    return testingServer.access
+  } else {
+    logger.error(`error : fwh server is ${server}.`)
+    return ErrorCode.FwhServerNotExist
+  }
+}

+ 82 - 0
src/controller/accountManage.controller.ts

@@ -0,0 +1,82 @@
+import Koa from 'koa'
+import accountManageService from '@/service/accountManage.service'
+import { addResources } from '@/utils/i18next'
+import { removeLoginCache } from '@/external/fwh/login'
+import { ErrorCode } from '@/constant/err.type'
+import { JwtInfo } from '@/core/type'
+import { checkReqSaveAccount } from '@/api/accountManage'
+import { ResultData } from '@/api/public'
+import { AccountManage } from '@/api/accountManage/interface'
+
+// 动态添加翻译
+addResources('en', 'AccountManage', {
+  'pro_server': 'Production Server',
+  'tst_server': 'Test Server'
+})
+addResources('zh', 'AccountManage', {
+  'pro_server': '生产服务器',
+  'tst_server': '测试服务器'
+})
+
+// 添加服务器选项,需求同时修改数据库
+const ServerOption = (ctx: Koa.Context) => {
+  return [
+    { label: ctx.t('AccountManage:pro_server'), value: 'production'},
+    { label: ctx.t('AccountManage:tst_server'), value: 'testing'}
+  ]
+}
+
+class AccountManageController {
+  async getAccountInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { username } = ctx.state.jwtData as JwtInfo
+
+    const info = await accountManageService.getAccountInfo(username)
+
+    if (info === undefined) {
+      // 账户信息获取失败
+      ctx.app.emit('app error', ErrorCode.FwhAccountNotExist, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<AccountManage.ResAccountInfo> = {
+      "code": 200,
+      "msg": "成功",
+      "data": {
+        server: info.server,
+        server_opt: ServerOption(ctx),
+        fwh_username: info.fwh_username,
+        fwh_password_length: info.fwh_password.length
+      }
+    }
+    ctx.body = body
+  }
+
+  async saveAccountInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { id, username } = ctx.state.jwtData as JwtInfo
+    // 获取要保存的数据
+    const { server, fwh_username, fwh_password } = checkReqSaveAccount(ctx)
+
+    const res = await accountManageService.saveAccountInfo(id, username, server, fwh_username, fwh_password)
+    if (!res) {
+      // 账户信息保存失败
+      ctx.app.emit('app error', ErrorCode.FwhAccountSaveFailed, ctx)
+      return
+    }
+
+    // 保存fwh账号信息后需要清除登录缓存
+    removeLoginCache(username)
+
+    // 返回结果
+    const body: ResultData<undefined> = {
+      code: 200,
+      msg: "成功",
+      data: undefined
+    }
+    ctx.body = body
+  }
+}
+
+export default new AccountManageController()

+ 53 - 0
src/controller/auth.controller.ts

@@ -0,0 +1,53 @@
+import Koa from 'koa'
+import authService from '@/service/auth.service'
+import { JwtInfo } from '@/core/type'
+import { ResultData } from '@/api/public'
+import { Menu } from '@/api/auth/interface'
+import { Login } from '@/api/user/interface'
+import { ErrorCode } from '@/constant/err.type'
+
+class AuthController {
+  async getMenuAuth(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { id, username, admin, enable } = ctx.state.jwtData as JwtInfo
+
+    if (!enable) {
+      // 用户未审核
+      ctx.app.emit('app error', ErrorCode.UserNotReviewed, ctx);
+      return
+    }
+
+    const menuAuth = authService.getMenuAuth(admin)
+
+    // 返回结果
+    const body: ResultData<Menu.MenuOptions[]> = {
+      "code": 200,
+      "msg": "成功",
+      "data": [...menuAuth]
+    }
+    ctx.body = body
+  }
+
+  async getButtonAuth(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { id, username, admin, enable } = ctx.state.jwtData as JwtInfo
+
+    if (!enable) {
+      // 用户未审核
+      ctx.app.emit('app error', ErrorCode.UserNotReviewed, ctx);
+      return
+    }
+
+    const buttonAuth = authService.getButtonAuth(admin)
+
+    // 返回结果
+    const body: ResultData<Login.ResAuthButtons> = {
+      "code": 200,
+      "msg": "成功",
+      "data": {...buttonAuth}
+    }
+    ctx.body = body
+  }
+}
+
+export default new AuthController()

+ 133 - 0
src/controller/cloudLog.controller.ts

@@ -0,0 +1,133 @@
+import Koa from 'koa'
+import cloudLogService from '@/service/cloudLog.service'
+import logType from '@/constant/cloudData/log.type'
+import { ErrorCode } from '@/constant/err.type'
+import { JwtInfo } from '@/core/type'
+import { checkReqCloudLogParams, checkReqCreateLogParams, checkReqCloudLogData, checkReqCloudLogDataPage } from '@/api/cloudLog'
+import { ResultData, ResPage } from '@/api/public'
+import { CloudLog } from '@/api/cloudLog/interface'
+
+class CloudLogController {
+  async getList(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const { serialNumber, status, pageNum, pageSize } = checkReqCloudLogParams(ctx)
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    // 检查参数
+    if ((typeof serialNumber === 'object')
+      || (typeof status === 'object')
+      || (typeof pageNum === 'object')
+      || (typeof pageSize === 'object')
+    ) {
+      ctx.app.emit('app error', ErrorCode.RequestParameterError, ctx)
+      return
+    }
+
+    console.log(`serialNumber: ${serialNumber}, status: ${status}, pageNum: ${pageNum}, pageSize: ${pageSize}`)
+
+    const list = await cloudLogService.getSiteList(server, serialNumber, status ? Number(status) : undefined, pageNum ? Number(pageNum) : undefined, pageSize ? Number(pageSize) : undefined)
+
+    // 返回结果
+    const body: ResultData<CloudLog.ResCloudLogListPage> = {
+      code: 0,
+      msg: 'get successfully',
+      data: {
+        list: [ // 返回数据列
+          ...list
+        ],
+        pageNum,
+        pageSize,
+        total: list.length, // 返回数据总数
+      }
+    }
+    ctx.body = body
+  }
+
+  async create(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const { serialNumberList, logTypes, startTime, endTime } = checkReqCreateLogParams(ctx)
+    // 获取jwt数据
+    const { username, server } = ctx.state.jwtData as JwtInfo
+
+    ctx.log.info("add cloud log", {serialNumberList, logTypes, startTime, endTime})
+
+    // 执行操作
+    await cloudLogService.createCloudLog(ctx.state, username, server, serialNumberList, logTypes, startTime, endTime)
+
+    // 返回结果
+    const body: ResultData<undefined> = {
+      code: 0,
+      msg: 'created successfully',
+      data: undefined
+    }
+    ctx.body = body
+  }
+
+  async getCmdTypeList(ctx: Koa.Context, next: Koa.Next) {
+    // 返回结果
+    const body: ResultData<CloudLog.ResLogType[]> = {
+      code: 0,
+      msg: 'get successfully',
+      data: [ // 返回数据列
+        ...logType(ctx)
+      ],
+    }
+    ctx.body = body
+  }
+
+  async getCloudLogData(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const { serialNumber, logTypes, startTime, endTime } = checkReqCloudLogData(ctx)
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const data = await cloudLogService.getCloudLogData(server, serialNumber, logTypes, startTime, endTime);
+
+    if (data === undefined) {
+      ctx.app.emit('app error', ErrorCode.RequestParameterError, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<CloudLog.ResCloudLogData[]> = {
+      code: 0,
+      msg: 'get successfully',
+      data: [ // 返回数据列
+        ...data
+      ]
+    }
+    ctx.body = body
+  }
+
+  async getCloudLogDataPage(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const { serialNumber, logTypes, startTime, endTime, pageNum, pageSize } = checkReqCloudLogDataPage(ctx)
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const data = await cloudLogService.getCloudLogDataPage(server, serialNumber, logTypes, startTime, endTime, pageNum, pageSize);
+
+    if (data === undefined) {
+      ctx.app.emit('app error', ErrorCode.RequestParameterError, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<ResPage<CloudLog.ResCloudLogDataPage>> = {
+      code: 0,
+      msg: 'get successfully',
+      data: {
+        list: [ // 返回数据列
+          ...data
+        ],
+        pageNum,
+        pageSize,
+        total: await cloudLogService.getCloudLogDataPageTotal(server, serialNumber, logTypes, startTime, endTime), // 返回数据总数
+      }
+    }
+    ctx.body = body
+  }
+}
+
+export default new CloudLogController()

+ 45 - 0
src/controller/emsLog.controller.ts

@@ -0,0 +1,45 @@
+import Koa from 'koa'
+import { queryEmsLogList } from '@/external/fwh/api/getEmsLog'
+import { JwtInfo } from '@/core/type'
+import { checkReqEmsLogListPage } from '@/api/emsLog'
+import { ResultData, ResPage } from '@/api/public'
+import { EmsLog } from '@/api/emsLog/interface'
+
+class EmsLogController {
+  async getList(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const { serialNumber, status, pageNum, pageSize } = checkReqEmsLogListPage(ctx)
+    // 获取jwt数据
+    const { username } = ctx.state.jwtData as JwtInfo
+
+    const request = await queryEmsLogList({
+      username, 
+      serialNumber, 
+      status, 
+      pageNum, 
+      pageSize
+    })
+
+    if (typeof request !== 'object') {
+      ctx.app.emit('app error', request, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<ResPage<EmsLog.ResEmsLogListData>> = {
+      code: 0,
+      msg: 'get successfully',
+      data: {
+        list: [ // 返回数据列
+          ...request.result
+        ],
+        pageNum,
+        pageSize,
+        total: request.total, // 返回数据总数
+      }
+    }
+    ctx.body = body
+  }
+}
+
+export default new EmsLogController()

+ 180 - 0
src/controller/siteManage.controller.ts

@@ -0,0 +1,180 @@
+import Koa from 'koa'
+import siteManageService from '@/service/siteManage.service'
+import { JwtInfo } from '@/core/type'
+import { checkReqSiteInfoParams, checkReqSiteTagsParams } from "@/api/siteManage"
+import { ErrorCode } from '@/constant/err.type'
+import { ResPage, ResultData } from '@/api/public'
+import { SiteManage } from '@/api/siteManage/interface'
+
+class SiteManageController {
+  async getInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const info = await siteManageService.getInfo(server)
+    if (typeof info !== 'object') {
+      ctx.app.emit('app error', info, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<SiteManage.ResSiteManageInfo> = {
+      code: 0,
+      msg: 'get successfully',
+      data: {
+        ...info
+      }
+    }
+    ctx.body = body
+  }
+
+  async refresh(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { username, server } = ctx.state.jwtData as JwtInfo
+
+    await siteManageService.refresh(username, server)
+    const info = await siteManageService.getInfo(server)
+    if (typeof info !== 'object') {
+      ctx.app.emit('app error', info, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<SiteManage.ResSiteManageInfo> = {
+      code: 0,
+      msg: 'created successfully',
+      data: {
+        ...info
+      }
+    }
+    ctx.body = body
+  }
+
+  async getOfflineInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const info = await siteManageService.getOfflineInfo(server)
+    if (typeof info !== 'object') {
+      ctx.app.emit('app error', info, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<SiteManage.ResSiteManageInfo> = {
+      code: 0,
+      msg: 'get successfully',
+      data: {
+        ...info
+      }
+    }
+    ctx.body = body
+  }
+
+  async refreshOffline(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { username, server } = ctx.state.jwtData as JwtInfo
+
+    await siteManageService.refreshOffline(username, server)
+    const info = await siteManageService.getOfflineInfo(server)
+    if (typeof info !== 'object') {
+      ctx.app.emit('app error', info, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<SiteManage.ResSiteManageInfo> = {
+      code: 0,
+      msg: 'created successfully',
+      data: {
+        ...info
+      }
+    }
+    ctx.body = body
+  }
+
+  async getSiteInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const reqParams = checkReqSiteInfoParams(ctx)
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const data = await siteManageService.getSiteInfo(server, reqParams)
+    const list = data.map((item) => {
+      return {
+        serialNumber: item.serialNumber,
+        activeStatus: item.activeStatus,
+        networkStatus: item.networkStatus,
+        iccId: item.iccId,
+        imei: item.imei,
+        apowerSn: item.apowerSn,
+        apowerNum: item.apowerNum,
+        timeZone: item.timeZone,
+        zoneInfo: item.zoneInfo,
+        country: item.country,
+        province: item.province,
+        city: item.city,
+        deviceAddress: item.deviceAddress,
+        tags: item.tags,
+        rawData: item.rawData
+      }
+    })
+    
+    // 返回结果
+    const body: ResultData<ResPage<SiteManage.ResSiteInfo>> = {
+      code: 0,
+      msg: 'get successfully',
+      data: {
+        list: [ // 返回数据列
+          ...list
+        ],
+        pageNum: reqParams.pageNum,
+        pageSize: reqParams.pageSize,
+        total: await siteManageService.getSiteInfoTotal(server, reqParams), // 返回数据总数
+      }
+    }
+    ctx.body = body
+  }
+
+  async postSiteTags(ctx: Koa.Context, next: Koa.Next) {
+    // 获取数据
+    const reqParams = checkReqSiteTagsParams(ctx)
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const res = await siteManageService.setSiteTags(server, reqParams.serialNumber, reqParams.tags)
+    if (!res) {
+      ctx.app.emit('app error', ErrorCode.DatabaseUpdateFailed, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<undefined> = {
+      code: 0,
+      msg: 'post successfully',
+      data: undefined
+    }
+    ctx.body = body
+  }
+
+  async getSiteMap(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { server } = ctx.state.jwtData as JwtInfo
+
+    const mapList = await siteManageService.getSiteMap(server)
+    if (mapList.length === 0) {
+      ctx.app.emit('app error', ErrorCode.DatabaseReadFailed, ctx)
+      return
+    }
+
+    // 返回结果
+    const body: ResultData<SiteManage.ResSiteMapData[]> = {
+      code: 0,
+      msg: 'get successfully',
+      data: mapList
+    }
+    ctx.body = body
+  }
+}
+
+export default new SiteManageController()

+ 115 - 0
src/controller/user.controller.ts

@@ -0,0 +1,115 @@
+import Koa from 'koa'
+import bcrypt from 'bcryptjs'
+import userService from '@/service/user.service'
+import { generateToken } from '@/utils/jwt'
+import { ErrorCode } from '@/constant/err.type'
+import { UserInfo } from '@/core/type'
+import { checkReqLogin } from '@/api/user'
+import { ResultData } from '@/api/public'
+import { Login } from '@/api/user/interface'
+import logger from '@/logger'
+
+// 发起请求 -》 controller -》 service --》 model -》 操作数据 -》 
+//使用了 sequelize 完成对数据的保存操作
+class UserController {
+
+  // 方法被 async 修饰了,调用处必须加上await
+  async register(ctx: Koa.Context, next: Koa.Next) {
+    // 1. 获取数据
+    const { username, password } = checkReqLogin(ctx)
+
+    // 2. 操作数据库
+    const res = await userService.createUser(username, password)
+    logger.debug({ registerResult: res }, 'User registration results')
+
+    // 3. 返回结果
+    const body: ResultData<Login.ResRegister> = {
+      code: 200,
+      msg: '用户注册成功',
+      data: {
+        success: true,
+        msg: '用户注册成功'
+      },
+    }
+    ctx.body = body
+  }
+
+  async login(ctx: Koa.Context, next: Koa.Next) {
+    // 登录控制器,验证用户并生成 token
+    const { username, password } = checkReqLogin(ctx)
+
+    const userInfo: UserInfo | undefined = await userService.getUserInfo({username: username})
+    logger.info({ userInfo: userInfo }, 'login info')
+
+    let body: ResultData<Login.ResLogin> = {
+      code: 200,
+      msg: '',
+      data: {
+        success: false,
+        msg: ''
+      }
+    }
+
+    if (userInfo === undefined) {
+      // 用户不存在
+      // ctx.app.emit('app error', ErrorCode.UserNotExited, ctx)
+      // 改用正常回复返回失败原因
+      body = Object.assign(body, {
+        code: 200,
+        msg: '登录失败',
+        data: {
+          success: false,
+          msg: '用户不存在'
+        }
+      })
+    } else if (!bcrypt.compareSync(password, userInfo.password)) {
+      // 密码错误
+      // ctx.app.emit('app error', ErrorCode.UserPasswordError, ctx)
+      // 改用正常回复返回失败原因
+      body = Object.assign(body, {
+        code: 200,
+        msg: '登录失败',
+        data: {
+          success: false,
+          msg: '密码错误'
+        }
+      })
+    } else if (!userInfo.enable) {
+      // 用户未授权
+      // ctx.app.emit('app error', ErrorCode.UserNotReviewed, ctx)
+      // 改用正常回复返回失败原因
+      body = Object.assign(body, {
+        code: 200,
+        msg: '登录失败',
+        data: {
+          success: false,
+          msg: '用户未授权'
+        }
+      })
+    } else {
+      const access_token = generateToken({ ...userInfo });
+      body = Object.assign(body, {
+        code: 200,
+        msg: '登录成功',
+        data: {
+          success: true,
+          msg: '登录成功',
+          access_token: access_token
+        }
+      })
+    }
+
+    ctx.body = body
+  }
+
+  logout(ctx: Koa.Context, next: Koa.Next) {
+    const body: ResultData<undefined> = {
+      code: 200,
+      msg: 'Logout successful',
+      data: undefined
+    }
+    ctx.body = body
+  }
+}
+
+export default new UserController()

+ 92 - 0
src/controller/userInfo.controller.ts

@@ -0,0 +1,92 @@
+import Koa from 'koa'
+import { JwtInfo } from '@/core/type'
+import { addResources } from '@/utils/i18next'
+import userInfoService from '@/service/userInfo.service'
+import { removeLoginCache } from '@/external/fwh/login'
+import { ErrorCode } from '@/constant/err.type'
+import { checkReqSaveUserInfo } from '@/api/userInfo'
+import { ResultData } from '@/api/public'
+import { UserInfo } from '@/api/userInfo/interface'
+import logger from '@/logger'
+
+// 动态添加翻译
+addResources('en', 'AccountManage', {
+  'pro_server': 'Production Server',
+  'tst_server': 'Test Server'
+})
+addResources('zh', 'AccountManage', {
+  'pro_server': '生产服务器',
+  'tst_server': '测试服务器'
+})
+
+// 添加服务器选项,需求同时修改数据库
+const ServerOption = (ctx: Koa.Context) => {
+  return [
+    { label: ctx.t('AccountManage:pro_server'), value: 'production'},
+    { label: ctx.t('AccountManage:tst_server'), value: 'testing'}
+  ]
+}
+
+// 发起请求 -》 controller -》 service --》 model -》 操作数据 -》 
+class UserInfoController {
+
+  async getUserInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 1. 获取数据
+    const { username } = ctx.state.jwtData as JwtInfo
+
+    // 2. 操作数据库
+    const res = await userInfoService.getUserInfo(username)
+    
+    if (!res) {
+      ctx.app.emit('app error', ErrorCode.UserNotExited, ctx)
+      logger.error("UserInfoController.getUserInfo - Invalid user name.")
+      return
+    }
+
+    // 3. 返回结果
+    const body: ResultData<UserInfo.ResUserInfo> = {
+      code: 200,
+      msg: '用户注册成功',
+      data: {
+        username: res.user_name,
+        fwhAccount: {
+          server: res.fwh_server,
+          server_opt: ServerOption(ctx),
+          fwh_pro_username: res.fwh_pro_username,
+          fwh_pro_password_length: String(res.fwh_pro_password).length,
+          fwh_tst_username: res.fwh_tst_username,
+          fwh_tst_password_length: String(res.fwh_tst_password).length,
+        }
+      },
+    }
+    ctx.body = body
+  }
+
+  async postUserInfo(ctx: Koa.Context, next: Koa.Next) {
+    // 获取jwt数据
+    const { id, username } = ctx.state.jwtData as JwtInfo
+    // 获取要保存的数据
+    const { fwhAccount } = checkReqSaveUserInfo(ctx)
+    const { server, fwh_username, fwh_password } = fwhAccount
+
+    const res = await userInfoService.saveUserInfo(id, username, server, fwh_username, fwh_password)
+    if (!res) {
+      // 账户信息保存失败
+      ctx.app.emit('app error', ErrorCode.FwhAccountSaveFailed, ctx)
+      return
+    }
+
+    // 保存fwh账号信息后需要清除登录缓存
+    removeLoginCache(username)
+
+    // 返回结果
+    const body: ResultData<undefined> = {
+      code: 200,
+      msg: "成功",
+      data: undefined
+    }
+    ctx.body = body
+  }
+}
+
+export default new UserInfoController()

+ 2 - 0
src/core/index.ts

@@ -0,0 +1,2 @@
+
+export const sleep = (second: number) => new Promise((resolve) => setTimeout(resolve, second * 1000));

+ 11 - 0
src/core/type/index.ts

@@ -0,0 +1,11 @@
+export interface JwtInfo {
+  id: number;
+  username: string;
+  admin: boolean;
+  enable:boolean;
+  server: string;
+}
+
+export interface UserInfo extends JwtInfo {
+  password: string;
+}

+ 67 - 0
src/external/fwh/api/getEmsLog/dataType.ts

@@ -0,0 +1,67 @@
+export type EmsLogType = typeof EmsLogTypeCfg
+export type EmsLogKeyType = keyof EmsLogType
+
+export const EmsLogTypeCfg = {
+  'runLog': { // 4-IBG运行日志
+    index: 4,
+    url: '/emsLog/runLog'
+  },
+  'mqttLog': { // 6-通信日志
+    index: 6,
+    url: '/emsLog/mqttLog'
+  },
+  'battLog': { // 7-电池日志
+    index: 7,
+    url: '/emsLog/battLog'
+  },
+  'awsLog': { // 11-AWS日志
+    index: 11,
+    url: '/emsLog/awsLog'
+  }
+}
+
+export interface PostDownloadEmsLog {
+  alarmId: number;
+  companyId: string;
+  gatewayId: string; // SN
+  logEqSn: string; // SN
+  logId: string; // SN
+  logTime: string; // YYYY-MM-DD
+  logType: number; // 4-IBG运行日志
+  lang: string;
+}
+
+export interface GetEmsLogList {
+  linkId?: string; // SN
+  status?: number; // -1 - 失败; 0 - 召唤中; 1 - 成功
+  pageIndex: number;
+  pageSize: number;
+  pageNum: number;
+  lang: string;
+}
+
+export interface DownloadInfo {
+  callTime: string; // YYYY-MM-DD HH:mm:SS
+  codeType: number;
+  completeTime: string; // YYYY-MM-DD HH:mm:SS
+  endTime?: string;  // YYYY-MM-DD HH:mm:SS
+  fileName?: string;
+  fileUrl?: string;
+  id: number;
+  linkId: string; // SN
+  logTime: string;  // YYYY-MM-DD
+  startTime?: string;  // YYYY-MM-DD HH:mm:SS
+  status: number; // -1 - 失败; 0 - 召唤中; 1 - 成功
+}
+
+// 不需要包括RequestResult已经定义的部分
+export interface PostDownloadEmsLogRes {
+  result: boolean;
+  total: number;
+}
+
+// 不需要包括RequestResult已经定义的部分
+export interface GetEmsLogListRes {
+  result: DownloadInfo[];
+  total: number;
+}

+ 110 - 0
src/external/fwh/api/getEmsLog/index.ts

@@ -0,0 +1,110 @@
+import { format } from "date-fns"
+import { get, post } from '../../request'
+import { EmsLogKeyType, EmsLogTypeCfg, PostDownloadEmsLog, GetEmsLogList, PostDownloadEmsLogRes, GetEmsLogListRes } from './dataType';
+import { ErrorCode } from '@/constant/err.type';
+import logger from "@/logger";
+
+export async function requestDownloadEmsLog(username: string, serialNumber: string, type: string, date: Date) {
+  // 判断日志类型是否支持
+  if (!(type in EmsLogTypeCfg)) {
+    logger.error(`Ems log type is not supported: ${type}`);
+    return ErrorCode.FwhServerRequestFailed
+  }
+  const logType = type as EmsLogKeyType
+
+  const payload : PostDownloadEmsLog = {
+    alarmId: 0,
+    companyId: "",
+    gatewayId: serialNumber, // SN
+    logEqSn: serialNumber, // SN
+    logId: serialNumber, // SN
+    logTime: format(date, 'YYYY-MM-DD'), // YYYY-MM-DD
+    logType: EmsLogTypeCfg[logType].index,
+    lang: "ZH_CN"
+  }
+  const postRes = await post<PostDownloadEmsLogRes>(
+    username,
+    '/hes-gateway/manage/logFileCall',
+    payload
+  )
+    .then(response => {
+      if (typeof response !== 'object') return response
+      return response.data
+    })
+    .catch(error => {
+      console.error('Request failed:', error);
+      return ErrorCode.FwhServerRequestFailed
+    });
+
+  if (typeof postRes !== 'object') {
+    return postRes
+  }
+
+  return postRes.result
+}
+
+export interface QueryEmsLogListParams {
+  username: string;
+  serialNumber?: string;
+  status?: number; // -1 - 失败; 0 - 召唤中; 1 - 成功
+  pageNum: number;
+  pageSize: number;
+}
+
+export async function queryEmsLogList(param: QueryEmsLogListParams) {
+  const params: GetEmsLogList = {
+    linkId: param.serialNumber, // SN
+    status: param.status,
+    pageIndex: 1,
+    pageSize: param.pageSize,
+    pageNum: param.pageNum,
+    lang: "ZH_CN"
+  }
+  const getRes = await get<GetEmsLogListRes>(
+    param.username,
+    '/hes-gateway/manage/iotDeviceFileRecords',
+    {
+      params
+    }
+  )
+    .then(response => {
+      if (typeof response !== 'object') return response
+      return response.data
+    })
+    .catch(error => {
+      logger.error('Request failed:', error);
+      return ErrorCode.FwhServerRequestFailed
+    });
+
+  return getRes
+}
+
+export async function queryDownloadStatus(username: string, serialNumber: string, type: string, date: Date) {
+  const params: QueryEmsLogListParams = {
+    username,
+    serialNumber, // SN
+    pageSize: 50,
+    pageNum: 1
+  }
+  const getRes = await queryEmsLogList(params)
+
+  if (typeof getRes !== 'object') {
+    return getRes
+  }
+
+  if (!(type in EmsLogTypeCfg)) {
+    logger.error(`Ems log type is not supported: ${type}`);
+    return ErrorCode.FwhServerRequestFailed
+  }
+  const logType = type as EmsLogKeyType
+
+  const statusInfo = getRes.result
+  for (const data of statusInfo) {
+    if ((data.linkId === serialNumber)
+      && (data.codeType === EmsLogTypeCfg[logType].index)
+      && (data.logTime === format(date, 'YYYY-MM-DD'))
+    ) {
+      return data
+    }
+  }
+}

+ 30 - 0
src/external/fwh/api/getMqttLog/dataType.ts

@@ -0,0 +1,30 @@
+export interface GetSiteMqttLog {
+  startTime: string; // format yyyy-MM-dd HH:mm:ss
+  endTime: string; // format yyyy-MM-dd HH:mm:ss
+  cmdTypes?: string;
+  timeType: number; // 0-通讯时间 1-设备时间
+  searchName: string;
+  userId: number;
+  pageSize: number;
+  pageNum: number;
+  lang: string;
+}
+
+export interface MqttLog {
+  cmdType: string;
+  cmdName: string;
+  gatewayId: string;
+  gatewayName: string | null;
+  gatewayType: string | null;
+  gatewayStatus: string;
+  activeStatus: string;
+  logData: string;
+  logTime: string; // 设备时间
+  time: string; // 通讯时间 - 8h
+}
+
+// 不需要包括RequestResult已经定义的部分
+export interface GetSiteMqttLogRes {
+  result: MqttLog[];
+  total: number;
+}

+ 145 - 0
src/external/fwh/api/getMqttLog/index.ts

@@ -0,0 +1,145 @@
+import { get } from '../../request'
+import { format, addHours } from 'date-fns'
+import { GetSiteMqttLog, GetSiteMqttLogRes, MqttLog } from './dataType';
+import { ErrorCode } from '@/constant/err.type';
+import logger from '@/logger';
+
+interface QuerySiteMqttLogParam {
+  username: string, 
+  serialNumber?: string, 
+  cmdType?: string, 
+  timeBegin: Date, 
+  timeEnd: Date
+}
+
+export async function queryAllSiteMqttLogBaseServerTime(
+  username: string, 
+  cmdType: string, 
+  timeBegin: Date, 
+  timeEnd: Date
+) {
+  // 计算和转换时间
+  // 通讯时间 = 需求时间 - 8h
+  const startTime = addHours(timeBegin, -8)
+  const endTime = addHours(timeEnd, -8)
+
+  return await querySiteMqttLog({
+    username,
+    cmdType,
+    timeBegin: startTime,
+    timeEnd: endTime
+  })
+}
+
+export async function querySiteMqttLogBaseServerTime(
+  username: string, 
+  serialNumber: string, 
+  cmdType: string, 
+  timeBegin: Date, 
+  timeEnd: Date
+) {
+  // 计算和转换时间
+  // 通讯时间 = 需求时间 - 8h
+  const startTime = addHours(timeBegin, -8)
+  const endTime = addHours(timeEnd, -8)
+
+  return await querySiteMqttLog({
+    username,
+    serialNumber,
+    cmdType,
+    timeBegin: startTime,
+    timeEnd: endTime
+  })
+}
+
+export async function querySiteMqttLogBaseSiteTime(
+  username: string, 
+  serialNumber: string, 
+  cmdType: string | undefined, 
+  timeBegin: Date, 
+  timeEnd: Date,
+  timeZone: number
+) {
+  // 计算和转换时间
+  // 通讯时间 = 需求时间 - 8h
+  // 设备时间 = 通讯时间 + timeZone
+  // 设备时间 = 需求时间 - 8h + timeZone
+  const startTime = addHours(timeBegin, timeZone - 8)
+  const endTime = addHours(timeEnd, timeZone - 8)
+
+  return await querySiteMqttLog({
+    username,
+    serialNumber,
+    cmdType,
+    timeBegin: startTime,
+    timeEnd: endTime
+  })
+}
+
+async function querySiteMqttLog(query: QuerySiteMqttLogParam) {
+  const pageSize = 50 // 每页50个数据
+
+  const startTime = format(query.timeBegin, 'yyyy-MM-dd HH:mm:ss')
+  const endTime = format(query.timeEnd, 'yyyy-MM-dd HH:mm:ss')
+  
+  // 格式化时间
+
+  let params: GetSiteMqttLog = {
+    userId: 0, // 接口中统一添加
+    searchName: query.serialNumber? query.serialNumber : '',
+    cmdTypes: query.cmdType,
+    startTime,
+    endTime,
+    timeType: 0, // 0-通讯时间 1-设备时间
+    pageSize,
+    pageNum: 0, // 从第一页开始,开始时会加1
+    lang: 'ZH_CN'
+  }
+
+  let mqttLogs: MqttLog[] = []
+
+  let total = 0;
+  do {
+    params.pageNum = params.pageNum + 1
+
+    // 设置请求间隔 1000ms
+    await new Promise(resolve => setTimeout(resolve, 1000));
+
+    const mqttLogRes = await get<GetSiteMqttLogRes>(
+      query.username,
+      '/hes-gateway/manage/iotMqttCommlogs',
+      {
+        params
+      }
+    )
+      .then(response => {
+        if (typeof response !== 'object') return response
+        return response.data
+      })
+      .catch(error => {
+        console.error('Request failed:', error);
+        return ErrorCode.FwhServerRequestFailed
+      });
+
+    if (typeof mqttLogRes !== 'object') {
+      return mqttLogRes
+    }
+
+    total = mqttLogRes.total
+
+    mqttLogs.push(...mqttLogRes.result)
+
+    // 打印进度
+    logger.info(`SN:${query.serialNumber}, type:${query.cmdType} total: ${total}, page: ${params.pageNum}, pageSize: ${pageSize}, startTime:${params.startTime}, endTime:${params.endTime}`)
+
+    // 如果数据量>=1000,则需要继续缩小时间段请求数据
+    if (total === 1000 && (params.pageNum * pageSize) >= total) {
+      // 取最后一天数据的最后通讯时间
+      params.endTime = mqttLogRes.result[mqttLogRes.result.length - 1].time
+      params.pageNum = 0
+    }
+
+  } while((params.pageNum * pageSize) < total)
+
+  return mqttLogs
+}

+ 119 - 0
src/external/fwh/api/getSiteList/dataType.ts

@@ -0,0 +1,119 @@
+export interface GetSiteListReq {
+  countryId?: number,
+  provinceId?: number,
+  cityId?: number,
+  alarmStatus?: number, // 0
+  activeStatus?: number,
+  search?: string, // 10130005A01F24180013
+  searchFlag?: number, // 0
+  status?: number,
+  connType?: number,
+  runStatus?: number,
+  emsModel?: number,
+  userId?: number, // 3974
+  electricityType?: number,
+  financierId?: number,
+  relationStatus?: number,
+  gridPhaseConSet?: number,
+  pageSize?: number, // 10
+  pageNum?: number, // 1
+  lang?: string, // ZH_CN
+}
+
+export interface SiteInfo {
+  id: string,
+  name: string | null,
+  distributorId: number | null,
+  distributorName: string | null,
+  installerId: number | null,
+  installerAccountId: number | null,
+  installerAccount: string | null,
+  installerName: string | null,
+  maintId: number | null,
+  maintName: string | null,
+  account: string | null,
+  accountName: string | null,
+  longitude: string | null,
+  latitude: string | null,
+  address: string | null,
+  installTime: string | null,
+  activeStatus: number, // 0-未激活,1-已激活
+  activeTime: string | null, // "2024-10-23 14:10:33"
+  localActiveTime: string | null, // "2024-10-23 09:10:33"
+  status: string, // 0-离线,1-在线
+  onLineTime: string | null, // 最后上线时间 "2024-11-08 01:10:17"
+  offLineTime: string | null, // 最后离线时间 "2024-11-07 23:48:59"
+  model: any,
+  deviceIp: string | null, // "10.178.166.109"
+  subnet: string | null, // "255.255.255.252"
+  mac: string | null, // "76:B9:69:67:8F:90"
+  gatewayIp: string | null, // "10.178.166.110"
+  signal: string | null, // "0"
+  iccId: string | null, // "89445008042182902796"
+  imei: string | null, // "868105046088538"
+  firmUpgradeTime: string | null,
+  turnoverTime: string | null, // "2024-11-07 23:49:03"
+  createTime: string | null, // "2024-10-23 13:46:08"
+  powerSun: number | null, // 0.0
+  powerGen: number | null, // 0.0
+  powerUti: number | null, // 4.79
+  powerFhp: number | null, // -0.09
+  powerLoad: number | null, // 4.7
+  batterySoc: number | null, // 99.9
+  runStatus: number | null, // 0
+  emsModel: number | null, // 52244
+  electricityType: number | null, // 1
+  workMode: number | null, // 
+  fhpSn: string | null, // "10050003A02J22390159,10050003A02J22390166,10050003A02J22390208"
+  timeZone: number | null, // -6.0
+  version: string | null, // "V10R06B10D00"
+  slVersion: string | null, // "V10R06B06D00"
+  slVersionJson: string | null,// "[{\"subType\": \"SL_VER\", \"version\": \"V10R06B06D00\"}, {\"subType\": \"SL_VER_APP\"}, {\"subType\": \"SL_VER_BOOT\"}, {\"subType\": \"SL_VER_APP_BK\"}, {\"subType\": \"SL_VER_BOOT_BK\"}, {\"subType\": \"SL_STATE_APP\"}, {\"subType\": \"SL_STATE_BOOT\"}]"
+  meterVersion: string | null,
+  mbVersion: string | null, // "V10R01B04D00_240621"
+  awsVersion: string | null, // "V10R06B02D00_240715"
+  appVersion: string | null, // "V10R06B04D00_240812"
+  elecnetStatus: number | null, // 0
+  slaverStat: number | null, // 4
+  dst: number | null, // 1
+  zoneInfo: string | null, // "America/Chicago"
+  postCode: string | null, // "75124"
+  deviceTime: string | null, // "2024-11-07 19:14:40"
+  protocolVer: string | null, // "V1.11.01"
+  sysHdVersion: number | null, // 100
+  realSysHdVersion: string | null, // "FHP1.0"
+  wifiSignal: string | null, // "0"
+  connType: number, // 4
+  userName: string | null, // "Robert Bennett"
+  contactNumber: string | null, // "817-975-****"
+  totalData: any,
+  usedData: any,
+  resourceList: string | null, // "31,32,33"
+  financierName: string | null, // ""
+  financierContractNumber: string | null, // ""
+  countryId: number, // 2
+  provinceId: number, // 77
+  financierId: number, // 0
+  firstName: string | null, // "****"
+  lastName: string | null, // "****"
+  region: string | null,
+  remark: string | null, // ""
+  genEn: any,
+  userId: number, // 12690
+  country: string | null, // "United States"
+  province: string | null, // "Texas"
+  city: string | null, // "Eustace"
+  addressSecondLast: string | null, // "9230 FM1861"
+  deviceAddress: string | null, // "9230 FM1861, Eustace, Texas, United States"
+  whetherEncrypt: number | null, // 1
+  emsDeviceVer: any,
+  site: string | null, // "USTX2143789566"
+  newHomeFlag: any,
+  sleepStatus: any
+}
+
+// 不需要包括RequestResult已经定义的部分
+export interface GetSiteListRes {
+  result: SiteInfo[],
+  total: number
+}

+ 117 - 0
src/external/fwh/api/getSiteList/index.ts

@@ -0,0 +1,117 @@
+import { get } from '../../request'
+import { GetSiteListReq, GetSiteListRes, SiteInfo } from './dataType'
+import { ErrorCode } from '@/constant/err.type'
+
+async function query(username: string, params: GetSiteListReq) : Promise<GetSiteListRes | ErrorCode> {
+  // GetSiteListReq必填项在这里添加
+  let payload: GetSiteListReq = {
+    userId: 0,
+    pageSize: 10,
+    pageNum: 1,
+    lang: 'ZH_CN',
+  }
+
+  Object.assign(payload, params)
+  console.log("querySiteList payload : ", payload)
+
+  return await get<GetSiteListRes>(
+    username,
+    '/hes-gateway/manage/selectIotGatewayList',
+    {
+      params: payload,
+    }
+  )
+    .then(response => {
+      if (typeof response !== 'object') return response
+      return response.data
+    })
+    .catch(error => {
+      console.error('Request failed:', error);
+      return ErrorCode.FwhServerRequestFailed
+    });
+}
+
+export async function querySiteListAll(username: string) : Promise<SiteInfo[] | ErrorCode> {
+  const pageSize = 50 // 每页50个数据
+  let params: GetSiteListReq = {
+    searchFlag: 0,
+    pageNum: 0,
+    pageSize,
+  }
+
+  let total = 0;
+  let siteList : SiteInfo[] = []
+  do {
+    params.pageNum = (params.pageNum? params.pageNum : 0) + 1
+
+    // 设置请求间隔 10000ms
+    await new Promise(resolve => setTimeout(resolve, 10000));
+
+    const listPage = await query(username, params)
+    if (typeof listPage !== 'object') {
+      return listPage
+    }
+
+    total = listPage.total
+
+    siteList.push(...listPage.result)
+
+    // 打印进度
+    console.log(`total: ${total}, page: ${params.pageNum}, pageSize: ${pageSize}`)
+
+  } while ((params.pageNum * pageSize) < total)
+
+  return siteList
+}
+
+export async function queryOfflineSiteListAll(username: string) : Promise<SiteInfo[] | ErrorCode> {
+  const pageSize = 50 // 每页50个数据
+  let params: GetSiteListReq = {
+    activeStatus: 1, // 已激活站点
+    status: 0, // 离线站点
+    searchFlag: 0,
+    pageNum: 0,
+    pageSize,
+  }
+
+  let total = 0;
+  let siteList : SiteInfo[] = []
+  do {
+    params.pageNum = (params.pageNum? params.pageNum : 0) + 1
+
+    // 设置请求间隔 10000ms
+    await new Promise(resolve => setTimeout(resolve, 10000));
+
+    const listPage = await query(username, params)
+    if (typeof listPage !== 'object') {
+      return listPage
+    }
+
+    total = listPage.total
+
+    siteList.push(...listPage.result)
+
+    // 打印进度
+    console.log(`total: ${total}, page: ${params.pageNum}, pageSize: ${pageSize}`)
+
+  } while ((params.pageNum * pageSize) < total)
+
+  return siteList
+}
+
+export async function querySiteListOnePage(username: string, pageNum: number = 1, pageSize: number = 10) : Promise<GetSiteListRes | ErrorCode> {
+  return await query(username, { 
+    searchFlag: 0,
+    pageNum,
+    pageSize
+  })
+}
+
+export async function querySiteListInSerialNumber(username: string, serialNumber: string, pageNum: number = 1, pageSize: number = 10) : Promise<GetSiteListRes | ErrorCode> {
+  return await query(username, { 
+    searchFlag: 11, // 11-aGate SN
+    search: serialNumber, 
+    pageNum,
+    pageSize
+  })
+}

+ 46 - 0
src/external/fwh/api/getWarehouseList/dataType.ts

@@ -0,0 +1,46 @@
+export interface GetWarehouseList {
+  apowerSn: string;
+  endTime: string; // format yyyy-MM-dd HH:mm:ss
+  socRegin: number[];
+  sortFlag: string;
+  sortType: string;
+  timeType: string;
+  warehouseName: string[];
+  pageSize: number;
+  pageNo: number;
+  lang: string; // ZH_CN
+}
+
+export interface WarehouseData {
+  id: number;
+  apowerSn: string;
+  address: string;
+  afterRechargeSoc?: number; // 下次补电SOC
+  beforeRechargeSoc?: number; // 上次补电SOC
+  createTime: string; // 条目创建时间
+  currentSoc?: number;
+  delFlag: number;
+  deliveryTime: string;
+  distributor:string;
+  needRecharge: number;
+  nextRechargeTime: string;
+  orderNo: string;
+  outboundTime: string;
+  productionTime: string;
+  rechargeTime?: string;
+  socDecreaseTime: string;
+  updateTime: string;
+  version?: string;
+  warehouseName: string;
+}
+
+// 不需要包括RequestResult已经定义的部分
+export interface GetWarehouseListRes {
+  data: {
+    pageNo: number;
+    pageSize: number;
+    pageTotal: number;
+    records: WarehouseData[];
+    total: number;
+  };
+}

+ 60 - 0
src/external/fwh/api/getWarehouseList/index.ts

@@ -0,0 +1,60 @@
+import { post } from '../../request'
+import { GetWarehouseList, GetWarehouseListRes, WarehouseData } from './dataType';
+import { ErrorCode } from '@/constant/err.type';
+
+export async function queryWarehouseList(username: string) {
+  const pageSize = 50 // 每页50个数据
+
+  let payload: GetWarehouseList = {
+    apowerSn: '',
+    endTime: '',
+    socRegin: [],
+    sortFlag: '',
+    sortType: '',
+    timeType: '',
+    warehouseName: ['US-CA'],
+    pageSize,
+    pageNo: 0, // 从第一页开始,开始时会加1
+    lang: 'ZH_CN'
+  }
+
+  let warehouseData: WarehouseData[] = []
+
+  let total = 0;
+  do {
+    payload.pageNo = payload.pageNo + 1
+
+    // 设置请求间隔 1000ms
+    await new Promise(resolve => setTimeout(resolve, 1000));
+
+    const warehouseListRes = await post<GetWarehouseListRes>(
+      username,
+      '/api-inspect/warehouse/listApowers',
+      payload
+    )
+      .then(response => {
+        if (typeof response !== 'object') return response
+        return response.data
+      })
+      .catch(error => {
+        console.error('Request failed:', error);
+        return ErrorCode.FwhServerRequestFailed
+      });
+
+    if (typeof warehouseListRes !== 'object') {
+      return warehouseListRes
+    }
+
+    total = warehouseListRes.data.total
+
+    warehouseListRes.data.records.map(log => {
+      warehouseData.push(log)
+    })
+
+    // 打印进度
+    console.log(`total: ${total}, page: ${payload.pageNo}, pageSize: ${pageSize}`)
+
+  } while((payload.pageNo * pageSize) < total)
+
+  return warehouseData
+}

+ 24 - 0
src/external/fwh/axiosRetry.ts

@@ -0,0 +1,24 @@
+import axios, { AxiosInstance } from 'axios';
+import axiosRetry from 'axios-retry';
+
+// 创建 Axios 实例
+const axiosInstance = axios.create({
+  baseURL: 'https://energy.franklinwh.com/hes-gateway/manage/',
+  timeout: 180 * 1000,  // 请求超时设置
+});
+
+// 启用自动重试
+export function setRetry(axiosInstance: AxiosInstance) {
+  axiosRetry(axiosInstance, {
+    retries: 3, // 设置最大重试次数
+    retryDelay: axiosRetry.exponentialDelay, // 设置重试的延迟策略(例如指数退避)
+    retryCondition: (error: any) => {
+      // 根据条件判断是否重试
+      return (
+        (error.response?.status >= 500 && error.response?.status < 600 ) // 5xx的错误
+        // || error.code === 'ECONNABORTED' // 超时重连
+        || axiosRetry.isNetworkOrIdempotentRequestError(error) // 网络错误
+      );
+    },
+  });
+}

+ 172 - 0
src/external/fwh/login/index.ts

@@ -0,0 +1,172 @@
+import axios from 'axios'
+import AccountManageService from '@/service/accountManage.service'
+import { getLoginUrl } from '@/constant/fwh/serverConfig';
+import { ErrorCode } from '@/constant/err.type';
+
+interface AuthInfo {
+  userId: number;
+  token: string;
+}
+
+export interface LoginResult extends AuthInfo {
+  server: string;
+}
+
+// 保存用户登录信息的map
+const loginMap = new Map<string, LoginResult>();
+
+interface LoginInfo {
+  server: string;
+  username: string;
+  password: string;
+}
+
+interface LoginReq {
+  loginName: string;
+  password: string;
+  type: number; // 1
+  lang: string; // EN_US or ZH_CN
+}
+
+interface LoginRes {
+  code: number;
+  msg: string;
+  data: {
+    userInfo: {
+      loginName: string;
+    };
+    token: string;
+    userId: number;
+    groupName: string;
+    enGroupName: string;
+    innerGroupList: [];
+    outsideGroupList: [];
+    roleName: string;
+    resourceList: [];
+    serviceVoltageFlag: number;
+    ninetyDaysPwdUpdate: number;
+    countries: string;
+  };
+  success: boolean;
+}
+
+async function login(params: LoginInfo) : Promise<AuthInfo | ErrorCode> {
+  if (params.username === '') {
+    console.log('error : fwh username is null.')
+    return ErrorCode.FwhAccountNotExist
+  }
+  if (params.password === '') {
+    console.log('error : fwh password is null.')
+    return ErrorCode.FwhAccountNotExist
+  }
+
+  const baseURL = getLoginUrl(params.server)
+  if (typeof baseURL !== 'string') return baseURL
+
+  const payload: LoginReq = {
+    loginName: params.username,
+    password: params.password,
+    type: 1,
+    lang: 'ZH_CN',
+  }
+
+  // 创建一个 axios 实例,并设置 baseURL
+  const api = axios.create({
+    baseURL: baseURL,  // 设置基础 URL
+    timeout: 5000,  // 可选:设置请求超时时间
+    headers: {
+      'Content-Type': 'application/json',  // 设置默认请求头
+    },
+  });
+
+  return await api.post<LoginRes>(
+    '/api-user/user/login',
+    payload,
+  )
+    .then(response => {
+      if (response.data.code === 1000005) {
+        // TODO 账户密码错误
+        return ErrorCode.FwhLoginFailed
+      }
+      const data = response.data.data
+      console.log("login response : ", response.data)
+      const res: AuthInfo = {
+        userId: data.userId,
+        token: data.token
+      }
+      return res
+    })
+    .catch(error => {
+      console.error('Request failed:', error);
+      return ErrorCode.FwhServerRequestFailed
+    });
+}
+
+export async function getLoginResult(username: string) : Promise<LoginResult | ErrorCode> {
+  const resMap = loginMap.get(username)
+  if (resMap === undefined) {
+    const data = await AccountManageService.getAccountInfo(username)
+    if (data === undefined) {
+      console.warn("用户数据库没有FWH账户信息。")
+      return ErrorCode.FwhAccountNotExist
+    }
+    const authInfo = await login({
+      server: data.server,
+      username: data.fwh_username, 
+      password: data.fwh_password
+    })
+    if (typeof authInfo !== 'object') {
+      console.warn("FWH登录失败,请重试。")
+      return authInfo
+    }
+
+    const loginRes = {
+      server: data.server,
+      ...authInfo
+    }
+
+    // 保存Map
+    loginMap.set(
+      username, 
+      loginRes
+    )
+
+    return loginRes
+  }
+
+  return resMap
+}
+
+// 如果登录信息有变化,需要移除登录缓存
+// TODO token过期未处理
+export function removeLoginCache(username: string) {
+  loginMap.delete(username);
+}
+
+// --- 获取单个登录信息 ---
+export async function getServer(username: string) {
+  const res = await getLoginResult(username)
+  if (typeof res !== 'object') {
+    return res
+  } else {
+    return res.server
+  }
+}
+
+export async function getToken(username: string) {
+  const res = await getLoginResult(username)
+  if (typeof res !== 'object') {
+    return res
+  } else {
+    return res.token
+  }
+}
+
+export async function getUserId(username: string) {
+  const res = await getLoginResult(username)
+  if (typeof res !== 'object') {
+    return res
+  } else {
+    return res.userId
+  }
+}

+ 116 - 0
src/external/fwh/request/index.ts

@@ -0,0 +1,116 @@
+import axios, { AxiosRequestConfig } from 'axios'
+import qs from 'query-string'
+import lodash from 'lodash'
+import { removeLoginCache, getLoginResult } from '../login';
+import { setRetry } from '../axiosRetry'
+import { getAccessUrl } from '@/constant/fwh/serverConfig';
+
+export interface RequestResult {
+  code: number;
+  message: string;
+  success?: boolean;
+}
+
+// 创建一个 axios 实例,并设置 baseURL
+const request = axios.create({
+  timeout: 120 * 1000,  // 可选:设置请求超时时间 120s
+  headers: {
+    'Content-Type': 'application/json',  // 设置默认请求头
+  },
+});
+// 设置重试
+setRetry(request)
+
+export async function get<T>(username: string, url: string, config?: AxiosRequestConfig<any>) {
+  const resLogin = await getLoginResult(username)
+  if (typeof resLogin !== 'object') return resLogin
+
+  const baseURL = getAccessUrl(resLogin.server)
+  if (typeof baseURL !== 'string') return baseURL
+
+  const baseConfig : AxiosRequestConfig<any> = {
+    baseURL,
+    headers: {
+      LoginToken: resLogin.token,
+    },
+    params: {
+      userId: resLogin.userId
+    },
+    paramsSerializer: (obj) => {
+      return qs.stringify(obj)
+    }
+  }
+  let reqConfig : AxiosRequestConfig<any> = lodash.merge({}, config? config : {}, baseConfig)
+  let res = await request.get<RequestResult & T>(url, reqConfig)
+
+  const resData = res.data
+  if (isAuthInvalid(resData.code, resData.success, resData.message)) {
+    // 授权过期,重新登录
+    removeLoginCache(username)
+    const resLogin = await getLoginResult(username)
+    if (typeof resLogin !== 'object') {
+      return resLogin
+    }
+    // 重新get
+    lodash.merge(reqConfig, { 
+      headers: {
+        LoginToken: resLogin.token,
+      }, 
+      params: {
+        userId: resLogin.userId
+      },
+    })
+    res = await request.get<RequestResult & T>(url, reqConfig)
+  }
+  return res
+}
+
+export async function post<T>(username: string, url: string, data?: any, config?: AxiosRequestConfig<any>) {
+  const resLogin = await getLoginResult(username)
+  if (typeof resLogin !== 'object') return resLogin
+
+  const baseURL = getAccessUrl(resLogin.server)
+  if (typeof baseURL !== 'string') return baseURL
+
+  let reqData = lodash.merge({}, data? data : {}, { userId: resLogin.userId })
+
+  const baseConfig : AxiosRequestConfig<any> = {
+    baseURL,
+    headers: {
+      LoginToken: resLogin.token,
+    }
+  }
+  let reqConfig = lodash.merge({}, config? config : {}, baseConfig)
+  let res = await request.post<RequestResult & T>(url, reqData, reqConfig)
+
+  const resData = res.data
+  if (isAuthInvalid(resData.code, resData.success, resData.message)) {
+    // 授权过期,重新登录
+    removeLoginCache(username)
+    const resLogin = await getLoginResult(username)
+    if (typeof resLogin !== 'object') {
+      return resLogin
+    }
+    // 重新post
+    lodash.merge(reqData, { userId: resLogin.userId })
+    lodash.merge(reqConfig, {
+      headers: {
+        LoginToken: resLogin.token,
+      }
+    })
+    res = await request.post<RequestResult & T>(url, reqData, reqConfig)
+  }
+  return res
+}
+
+// 判断token是否过期
+function isAuthInvalid(code: number, success: boolean | undefined, message: string) : boolean {
+  if ((code === 401)
+    // && (success === false)
+    && (message === "请输入有效的用户授权令牌!")
+  ) {
+    return true
+  }
+
+  return false
+}

+ 10 - 0
src/index.ts

@@ -0,0 +1,10 @@
+import app from '@/app'
+import { hostConfig } from '@/config/config.default'
+import logger from './logger'
+
+
+const { APP_HOST, APP_PORT } = hostConfig
+
+app.listen(APP_PORT, APP_HOST, () => {
+	logger.info(`server is running on http://${APP_HOST}:${APP_PORT}`)
+})

+ 188 - 0
src/locales/cloudLogType/en.json

@@ -0,0 +1,188 @@
+{
+    "cloudLog": {
+        "logType": {
+            "cmd_0": "Device connection interrupted - 0",
+            "cmd_101": "Login upstream command - 101",
+            "cmd_102": "Log in and answer the downstream command - 102",
+            "cmd_103": "Device binding and unbinding uplink command - 103",
+            "cmd_104": "Device binding and unbinding response downlink command - 104",
+            "cmd_105": "Device heartbeat uplink data - 105",
+            "cmd_106": "Responding to cloud platform heartbeat downlink - 106",
+            "cmd_107": "Device parameter update uplink data - 107",
+            "cmd_108": "Respond to device parameter update downlink data - 108",
+            "cmd_109": "Cloud platform updates device parameter downlink data - 109",
+            "cmd_110": "Reply to the cloud platform to update the device parameter uplink data - 110",
+            "cmd_201": "User real-time data - 201",
+            "cmd_202": "Manufacturer real-time data upload notification - 202",
+            "cmd_203": "Real-time data call - 203",
+            "cmd_204": "Real-time data call response - 204",
+            "cmd_205": "Current alarm upload - 205",
+            "cmd_206": "Current alarm upload response - 206",
+            "cmd_207": "Alarm log upload - 207",
+            "cmd_208": "Alarm log upload response - 208",
+            "cmd_209": "Supplementary transmission command after user real-time data is offline - 209",
+            "cmd_210": "User real-time data offline after supplementary transmission response - 210",
+            "cmd_211": "Device real-time data calls downlink data - 211",
+            "cmd_212": "Device real-time data calls uplink data - 212",
+            "cmd_213": "Alarm log file upload notification - 213",
+            "cmd_214": "Current alarm set uplink data - 214",
+            "cmd_215": "Respond to current alarm set downlink data - 215",
+            "cmd_216": "Current alarm collection query downlink data - 216",
+            "cmd_217": "Respond to current alarm collection query uplink data - 217",
+            "cmd_218": "Alarm monitoring snapshot file upload notification - 218",
+            "cmd_219": "Respond to alarms monitoring snapshot file uploads - 219",
+            "cmd_220": "System resource monitoring data uplink data - 220",
+            "cmd_221": "Active upload of device data - 221",
+            "cmd_223": "Device data supplementary transmission notification uplink data - 223",
+            "cmd_224": "Response device data to supplement downlink data - 224",
+            "cmd_227": "The device uploads data in real time - 227",
+            "cmd_229": "Real-time data supplementary transmission - 229",
+            "cmd_230": "Real-time data supplementary transmission response - 230",
+            "cmd_301": "Parameter settings - 301",
+            "cmd_302": "Parameter setting response - 302",
+            "cmd_303": "Parameter query - 303",
+            "cmd_304": "Parameter query response - 304",
+            "cmd_305": "Early warning mode - 305",
+            "cmd_306": "Early warning mode response - 306",
+            "cmd_307": "Disable the early warning mode on the device side - 307",
+            "cmd_308": "The device end releases the early warning mode response - 308",
+            "cmd_309": "Device event push - 309",
+            "cmd_310": "Device event push response - 310",
+            "cmd_311": "Device control instruction setting query - 311",
+            "cmd_312": "Device control command setting query response - 312",
+            "cmd_313": "TOU parameter configuration query command - 313",
+            "cmd_314": "TOU parameter configuration query command response - 314",
+            "cmd_315": "System setting downlink data - 315",
+            "cmd_316": "System Settings Upstream Data - 316",
+            "cmd_317": "Global parameter configuration instructions - 317",
+            "cmd_318": "Global parameter configuration command response - 318",
+            "cmd_319": "TOU policy configuration query downlink data - 319",
+            "cmd_320": "TOU policy configuration query uplink data response - 320",
+            "cmd_321": "Wi-Fi settings query downlink data - 321",
+            "cmd_322": "Wi-Fi settings query uplink data response - 322",
+            "cmd_323": "Generator parameter setting query downlink data - 323",
+            "cmd_324": "Response to generator parameter setting query uplink data - 324",
+            "cmd_325": "Solar parameter setting query downlink data - 325",
+            "cmd_326": "Response to solar parameter settings to query uplink data - 326",
+            "cmd_327": "Control the aPower light switch downlink data - 327",
+            "cmd_328": "Respond to control the aPower light switch uplink data - 328",
+            "cmd_329": "Automatic addressing downlink data - 329",
+            "cmd_330": "Respond to automatic addressing uplink data - 330",
+            "cmd_331": "Query automatic addressing results downlink data - 331",
+            "cmd_332": "Respond to query automatic addressing results uplink data - 332",
+            "cmd_333": "Debug mode downlink data - 333",
+            "cmd_334": "Respond to debug mode uplink data - 334",
+            "cmd_335": "Scan Wi-Fi hotspot downlink data - 335",
+            "cmd_336": "Respond to scan Wi-Fi hotspot uplink data - 336",
+            "cmd_337": "Wi-Fi hotspot account query to set downlink data - 337",
+            "cmd_338": "Respond to query to set Wi-Fi hotspot account uplink data - 338",
+            "cmd_339": "Query Connection status downlink data - 339",
+            "cmd_340": "Respond to query Connection status uplink data - 340",
+            "cmd_341": "Network termination switch query to set downlink data - 341",
+            "cmd_342": "Respond to network termination switch query to set uplink data - 342",
+            "cmd_343": "Battery-controlled downlink data - 343",
+            "cmd_344": "Respond to battery control uplink data - 344",
+            "cmd_345": "Grid settings downlink data - 345",
+            "cmd_346": "Respond to grid settings for uplink data - 346",
+            "cmd_347": "Grid event result notification uplink data - 347",
+            "cmd_348": "Respond to grid event results downlink data - 348",
+            "cmd_349": "Active off-grid data downlink - 349",
+            "cmd_350": "Response to active off-grid data uplink - 350",
+            "cmd_351": "Active off-grid result notification to uplink data - 351",
+            "cmd_352": "Response to active off-grid result notification downlink data - 352",
+            "cmd_353": "System parameter query sets downlink data - 353",
+            "cmd_354": "System parameter query sets uplink data - 354",
+            "cmd_355": "Energy scheduling policy query - 355",
+            "cmd_356": "Energy scheduling policy query response - 356",
+            "cmd_357": "Energy scheduling policy addition and deletion settings - 357",
+            "cmd_358": "Reply to adding and deleting energy scheduling policy settings - 358",
+            "cmd_359": "System parameter query or setting - 359",
+            "cmd_360": "System parameter query or setting response - 360",
+            "cmd_361": "Network Channel Detection Settings - 361",
+            "cmd_362": "Network Channel Detection Setting Answer - 362",
+            "cmd_363": "Network channel detection result query - 363",
+            "cmd_364": "Network channel detection result query response - 364",
+            "cmd_365": "Swell registration/de-registration settings - 365",
+            "cmd_366": "Answer to Swell registration/de-registration settings - 366",
+            "cmd_367": "Swell application status query - 367",
+            "cmd_368": "Answer to Swell application status query - 368",
+            "cmd_369": "Modbus parameter privilege query and setting - 369",
+            "cmd_370": "Answer to modbus parameter privilege query - 370",
+            "cmd_371": "Synchronization point table configuration request - 371",
+            "cmd_372": "Answer to synchronized point table configuration request - 372",
+            "cmd_373": "Point table configuration table add/delete command - 373",
+            "cmd_374": "Point table configuration table add/delete command response - 374",
+            "cmd_375": "Point table response setting command - 375",
+            "cmd_376": "Answer to point meter response setting command - 376",
+            "cmd_377": "Fault Recording Real-time Data Acquisition - 377",
+            "cmd_378": "Fault Recording Information Answer - 378",
+            "cmd_379": "Fault recording information upload - 379",
+            "cmd_380": "Fault Recording Information Upload Answer - 380",
+            "cmd_381": "Device sleep configuration command - 381",
+            "cmd_382": "Device sleep configuration command response - 382",
+            "cmd_383": "Device sleep/exit sleep reporting - 383",
+            "cmd_384": "Device sleep/exit sleep reporting response - 384",
+            "cmd_385": "Device exits hibernation/cancels charging hibernation configuration command - 385",
+            "cmd_386": "Device exits hibernation/cancels charging hibernation configuration command response - 386",
+            "cmd_387": "Smart load switch query settings - 387",
+            "cmd_388": "Smart load switch query settings response - 388",
+            "cmd_389": "Smart circuit switch query command  - 389",
+            "cmd_390": "Smart circuit switch query answer - 390",
+            "cmd_401": "Daily power curve power call - 401",
+            "cmd_402": "Daily power curve power call response - 402",
+            "cmd_403": "Electricity history report call (month, year) - 403",
+            "cmd_404": "Electricity history report call response - 404",
+            "cmd_405": "Backup power log upload - 405",
+            "cmd_406": "Backup power log upload response - 406",
+            "cmd_407": "Notification of uploading historical daily power curve electricity report - 407",
+            "cmd_408": "Historical daily power curve report upload response - 408",
+            "cmd_409": "Notification of uploading power history report (month, year) - 409",
+            "cmd_410": "Upload response of power historical report - 410",
+            "cmd_411": "Daily power statistics upload uplink data - 411",
+            "cmd_412": "Response to daily electricity statistics upload and downlink data - 412",
+            "cmd_413": "Upload on device mode change - 413",
+            "cmd_414": "Answer to device mode change downstream data - 414",
+            "cmd_501": "IBG equipment installation information is actively uploaded - 501",
+            "cmd_502": "IBG equipment installation information is actively uploaded and responded - 502",
+            "cmd_503": "IBG equipment installation information call - 503",
+            "cmd_504": "IBG equipment installation information report - 504",
+            "cmd_505": "Equipment upgrade instructions - 505",
+            "cmd_506": "Device upgrade command Device upgrade response - 506",
+            "cmd_507": "Equipment upgrade result - 507",
+            "cmd_508": "Device upgrade result response - 508",
+            "cmd_509": "Device installation information actively uploads uplink data - 509",
+            "cmd_510": "Device installation information actively uploads downlink data - 510",
+            "cmd_511": "Device upgrade file download information delivery - 511",
+            "cmd_512": "Device upgrade file download information response uplink data - 512",
+            "cmd_513": "Device upgrade file download completes uplink data - 513",
+            "cmd_514": "Answering device upgrade file download completes downlink data - 514",
+            "cmd_515": "Special upgrade startup information reports uplink data - 515",
+            "cmd_516": "Special upgrade startup information reporting response to downlink data - 516",
+            "cmd_517": "Parameter update after device addressing uplink data - 517",
+            "cmd_518": "Responding to the parameter after addressing the device downlink data - 518",
+            "cmd_601": "Debug parameter issuance - 601",
+            "cmd_602": "Debug parameter delivery result - 602",
+            "cmd_603": "Device log call - 603",
+            "cmd_604": "Device log call response - 604",
+            "cmd_605": "Device log upload result - 605",
+            "cmd_606": "Device log upload result response - 606",
+            "cmd_607": "Fault Recording Summons Downlink Data - 607",
+            "cmd_608": "Fault Recording Summons Answer Uplink Data - 608",
+            "cmd_609": "Fault Recording File Upload Result - 609",
+            "cmd_610": "Fault Recording File Upload Result Answer - 610",
+            "cmd_611": "Module log call - 611",
+            "cmd_612": "Module log call response - 612",
+            "cmd_613": "Module log upload result - 613",
+            "cmd_614": "Module log upload result response - 614",
+            "cmd_701": "Device data active upload notification uplink data - 701",
+            "cmd_702": "Responding to device data supplementary downlink data transmission - 702",
+            "cmd_703": "Notify uplink data of active device data upload - 703",
+            "cmd_704": "Responding to device data upload notification for downlink data - 704",
+            "cmd_705": "Responding to device data upload notification for downlink data - 705",
+            "cmd_706": "Responding device data upload notification for downlink data - 706",
+            "cmd_801": "sunnova data cycle upload - 801",
+            "cmd_802": "sunnova data upload enable button - 802",
+            "cmd_803": "Respond to sunnova data setting query - 803"
+        }
+    }
+}

+ 188 - 0
src/locales/cloudLogType/zh.json

@@ -0,0 +1,188 @@
+{
+    "cloudLog": {
+        "logType": {
+            "cmd_0": "设备连接中断 - 0",
+            "cmd_101": "登录上行命令 - 101",
+            "cmd_102": "登录应答下行命令 - 102",
+            "cmd_103": "设备绑定解绑上行命令 - 103",
+            "cmd_104": "设备绑定解绑应答下行命令 - 104",
+            "cmd_105": "设备心跳上行数据 - 105",
+            "cmd_106": "应答云平台心跳下行数据 - 106",
+            "cmd_107": "设备参数更新上行数据 - 107",
+            "cmd_108": "应答设备参数更新下行数据 - 108",
+            "cmd_109": "云平台更新设备参数下行数据 - 109",
+            "cmd_110": "应答云平台更新设备参数上行数据 - 110",
+            "cmd_201": "用户实时数据 - 201",
+            "cmd_202": "厂商实时数据上传通知 - 202",
+            "cmd_203": "实时数据召唤 - 203",
+            "cmd_204": "实时数据召唤应答 - 204",
+            "cmd_205": "当前告警上传 - 205",
+            "cmd_206": "当前告警上传应答 - 206",
+            "cmd_207": "告警日志上传 - 207",
+            "cmd_208": "告警日志上传应答 - 208",
+            "cmd_209": "用户实时数据离线后补传命令 - 209",
+            "cmd_210": "用户实时数据离线后补传应答 - 210",
+            "cmd_211": "设备实时数据召唤下行数据 - 211",
+            "cmd_212": "设备实时数据召唤上行数据 - 212",
+            "cmd_213": "告警日志文件上传通知 - 213",
+            "cmd_214": "当前告警集合上行数据 - 214",
+            "cmd_215": "应答当前告警集合下行数据 - 215",
+            "cmd_216": "当前告警集合查询下行数据 - 216",
+            "cmd_217": "应答当前告警集合查询上行数据 - 217",
+            "cmd_218": "告警监控快照文件上传通知 - 218",
+            "cmd_219": "应答告警监控快照文件上传 - 219",
+            "cmd_220": "系统资源监控数据上行数据 - 220",
+            "cmd_221": "设备数据主动上传 - 221",
+            "cmd_223": "设备数据补传通知上行数据 - 223",
+            "cmd_224": "应答设备数据补传下行数据 - 224",
+            "cmd_227": "设备实时上传数据 - 227",
+            "cmd_229": "实时数据补传上行 - 229",
+            "cmd_230": "实时数据补传下行 - 230",
+            "cmd_301": "参数设置 - 301",
+            "cmd_302": "参数设置应答 - 302",
+            "cmd_303": "参数查询 - 303",
+            "cmd_304": "参数查询应答 - 304",
+            "cmd_305": "预警模式 - 305",
+            "cmd_306": "预警模式应答 - 306",
+            "cmd_307": "设备端解除预警模式 - 307",
+            "cmd_308": "设备端解除预警模式应答 - 308",
+            "cmd_309": "设备事件推送 - 309",
+            "cmd_310": "设备事件推送应答 - 310",
+            "cmd_311": "设备控制指令设置查询 - 311",
+            "cmd_312": "设备控制指令设置查询应答 - 312",
+            "cmd_313": "TOU参数配置查询指令 - 313",
+            "cmd_314": "TOU参数配置查询指令应答 - 314",
+            "cmd_315": "系统设置下行数据 - 315",
+            "cmd_316": "系统设置上行数据 - 316",
+            "cmd_317": "全局参数配置指令 - 317",
+            "cmd_318": "全局参数配置指令应答 - 318",
+            "cmd_319": "TOU策略配置查询下行数据 - 319",
+            "cmd_320": "应答TOU策略配置查询上行数据 - 320",
+            "cmd_321": "Wi-Fi设置查询下行数据 - 321",
+            "cmd_322": "应答Wi-Fi设置查询上行数据 - 322",
+            "cmd_323": "发电机参数设置查询下行数据 - 323",
+            "cmd_324": "应答发电机参数设置查询上行数据 - 324",
+            "cmd_325": "光伏参数设置查询下行数据 - 325",
+            "cmd_326": "应答光伏参数设置查询上行数据 - 326",
+            "cmd_327": "aPower灯开关控制下行数据 - 327",
+            "cmd_328": "应答aPower灯开关控制上行数据 - 328",
+            "cmd_329": "自动编址下行数据 - 329",
+            "cmd_330": "应答自动编址上行数据 - 330",
+            "cmd_331": "自动编址结果查询下行数据 - 331",
+            "cmd_332": "应答自动编址结果查询上行数据 - 332",
+            "cmd_333": "调试模式下行数据 - 333",
+            "cmd_334": "应答调试模式上行数据 - 334",
+            "cmd_335": "Wi-Fi热点扫描下行数据 - 335",
+            "cmd_336": "应答Wi-Fi热点扫描上行数据 - 336",
+            "cmd_337": "Wi-Fi热点账号查询设置下行数据 - 337",
+            "cmd_338": "应答Wi-Fi热点账号查询设置上行数据 - 338",
+            "cmd_339": "连接状态查询下行数据 - 339",
+            "cmd_340": "应答连接状态查询上行数据 - 340",
+            "cmd_341": "网终开关查询设置下行数据 - 341",
+            "cmd_342": "应答网终开关查询设置上行数据 - 342",
+            "cmd_343": "电池控制下行数据 - 343",
+            "cmd_344": "应答电池控制上行数据 - 344",
+            "cmd_345": "电网设置下行数据 - 345",
+            "cmd_346": "应答电网设置上行数据 - 346",
+            "cmd_347": "电网事件结果通知上行数据 - 347",
+            "cmd_348": "应答电网事件结果下行数据 - 348",
+            "cmd_349": "主动离网数据下行 - 349",
+            "cmd_350": "应答主动离网数据上行 - 350",
+            "cmd_351": "主动离网结果通知上行数据 - 351",
+            "cmd_352": "应答主动离网结果通知下行数据 - 352",
+            "cmd_353": "系统参数查询设置下行数据 - 353",
+            "cmd_354": "系统参数查询设置上行数据 - 354",
+            "cmd_355": "能量调度策略查询 - 355",
+            "cmd_356": "能量调度策略查询应答 - 356",
+            "cmd_357": "能量调度策略增加和删除设置 - 357",
+            "cmd_358": "能量调度策略增加和删除设置应答 - 358",
+            "cmd_359": "系统参数查询设置 - 359",
+            "cmd_360": "系统参数查询设置应答 - 360",
+            "cmd_361": "网络通道检测设置 - 361",
+            "cmd_362": "网络通道检测设置应答 - 362",
+            "cmd_363": "网络通道检测结果查询 - 363",
+            "cmd_364": "网络通道检测结果查询应答 - 364",
+            "cmd_365": "Swell注册/注销设置 - 365",
+            "cmd_366": "Swell注册/注销设置应答 - 366",
+            "cmd_367": "Swell应用程序状态查询 - 367",
+            "cmd_368": "Swell应用程序状态查询应答 - 368",
+            "cmd_369": "Modbus参数权限的查询设置 - 369",
+            "cmd_370": "Modbus参数权限的查询设置应答 - 370",
+            "cmd_371": "同步点表配置请求 - 371",
+            "cmd_372": "同步点表配置请求应答 - 372",
+            "cmd_373": "点表配置表增删查命令 - 373",
+            "cmd_374": "点表配置表增删查命令应答 - 374",
+            "cmd_375": "点表响应设置命令 - 375",
+            "cmd_376": "点表响应设置应答 - 376",
+            "cmd_377": "故障录波实时数据获取 - 377",
+            "cmd_378": "故障录波信息应答 - 378",
+            "cmd_379": "故障录波信息上传 - 379",
+            "cmd_380": "故障录波信息上传应答 - 380",
+            "cmd_381": "设备休眠配置命令 - 381",
+            "cmd_382": "设备休眠命令应答 - 382",
+            "cmd_383": "休眠/退出休眠结果上报 - 383",
+            "cmd_384": "休眠/退出休眠结果上报应答 - 384",
+            "cmd_385": "设备退出休眠/取消充电休眠配置命令 - 385",
+            "cmd_386": "设备退出休眠/取消充电休眠配置命令应答 - 386",
+            "cmd_387": "智能负载开关查询设置 - 387",
+            "cmd_388": "智能负载开关查询设置应答 - 388",
+            "cmd_389": "智能负载开关实时数据查询命令 - 389",
+            "cmd_390": "智能负载开关实时数据查询应答 - 390",
+            "cmd_401": "日功率曲线电量召唤 - 401",
+            "cmd_402": "日功率曲线电量召唤应答 - 402",
+            "cmd_403": "电量历史报表召唤(月,年) - 403",
+            "cmd_404": "电量历史报表召唤应答 - 404",
+            "cmd_405": "备电日志上传 - 405",
+            "cmd_406": "备电日志上传应答 - 406",
+            "cmd_407": "历史日功率曲线电量报表上传通知 - 407",
+            "cmd_408": "历史日功率曲线电量报表上传应答 - 408",
+            "cmd_409": "电量历史报表上传通知(月,年) - 409",
+            "cmd_410": "电量历史报表上传应答 - 410",
+            "cmd_411": "日电量统计上传上行数据 - 411",
+            "cmd_412": "应答日电量统计上传下行数据 - 412",
+            "cmd_413": "设备模式变化时上传 - 413",
+            "cmd_414": "应答设备模式变化下行数据 - 414",
+            "cmd_501": "IBG设备安装信息主动上传 - 501",
+            "cmd_502": "IBG设备安装信息主动上传应答 - 502",
+            "cmd_503": "IBG设备安装信息召唤 - 503",
+            "cmd_504": "IBG设备安装信息上报 - 504",
+            "cmd_505": "设备升级指令 - 505",
+            "cmd_506": "设备升级应答 - 506",
+            "cmd_507": "设备升级结果 - 507",
+            "cmd_508": "设备升级结果应答 - 508",
+            "cmd_509": "设备安装信息主动上传上行数据 - 509",
+            "cmd_510": "设备安装信息主动上传下行数据 - 510",
+            "cmd_511": "设备升级文件下载信息下发 - 511",
+            "cmd_512": "设备升级文件下载信息响应上行数据 - 512",
+            "cmd_513": "设备升级文件下载完成上行数据 - 513",
+            "cmd_514": "应答设备升级文件下载完成下行数据 - 514",
+            "cmd_515": "特殊升级启动信息上报上行数据 - 515",
+            "cmd_516": "特殊升级启动信息上报应答下行数据 - 516",
+            "cmd_517": "设备编址后参数更新上行数据 - 517",
+            "cmd_518": "应答设备编址后参数下行数据 - 518",
+            "cmd_601": "调试参数下发 - 601",
+            "cmd_602": "调试参数下发结果 - 602",
+            "cmd_603": "设备日志召唤 - 603",
+            "cmd_604": "设备日志召唤应答 - 604",
+            "cmd_605": "设备日志上传结果 - 605",
+            "cmd_606": "设备日志上传结果应答 - 606",
+            "cmd_607": "故障录波召唤下行数据 - 607",
+            "cmd_608": "故障录波召唤应答上行数据 - 608",
+            "cmd_609": "故障录波文件上传结果 - 609",
+            "cmd_610": "故障录波文件上传结果应答 - 610",
+            "cmd_611": "模块日志召唤 - 611",
+            "cmd_612": "模块日志召唤应答 - 612",
+            "cmd_613": "模块日志文件上传结果 - 613",
+            "cmd_614": "模块日志文件上传结果应答 - 614",
+            "cmd_701": "设备数据主动上传通知上行数据 - 701",
+            "cmd_702": "应答设备数据补传下行数据 - 702",
+            "cmd_703": "设备数据主动上传通知上行数据 - 703",
+            "cmd_704": "应答设备数据补传下行数据 - 704",
+            "cmd_705": "设备数据主动上传通知下行数据 - 705",
+            "cmd_706": "应答设备数据补传下行数据 - 706",
+            "cmd_801": "sunnova数据周期上传 - 801",
+            "cmd_802": "sunnova数据主动上传使能按钮 - 802",
+            "cmd_803": "应答sunnova数据设置查询 - 803"
+        }
+    }
+}

+ 28 - 0
src/logger/index.ts

@@ -0,0 +1,28 @@
+import koa_pino from 'koa-pino-logger'
+import { logConfig } from '@/config/config.default'
+import { destination } from 'pino'
+
+export const koaLoggerMiddleware = koa_pino(
+  // options
+  {
+    level: logConfig.LOG_LEVEL,
+    formatters: {
+      level (label: string, number: number): object {
+        return { level: label }
+      }
+    },
+    msgPrefix: '[BEE]',
+    base: undefined,
+    nestedKey: 'payload',
+    timestamp: () => `,"time":"${new Date(Date.now()).toLocaleString()}"`
+  },
+  // destination
+  destination('/var/log/bee-server/dev/run.log')
+)
+
+export function getModuleLogger(module: string) {
+  return koaLoggerMiddleware.logger.child({module})
+}
+
+const logger = koaLoggerMiddleware.logger.child({module: 'app'})
+export default logger

+ 53 - 0
src/middleware/cloudLog.middleware.ts

@@ -0,0 +1,53 @@
+import Koa from 'koa'
+import cloudLogService from '@/service/cloudLog.service'
+import { ErrorCode } from '@/constant/err.type'
+import { JwtInfo } from '@/core/type'
+import { checkReqCreateLogParams } from '@/api/cloudLog'
+
+export const verifySerialNumber = async (ctx: Koa.Context, next: Koa.Next) => {
+  // 获取jwt数据
+  const { username } = ctx.state.jwtData as JwtInfo
+  // 获取数据
+  const { serialNumberList } = checkReqCreateLogParams(ctx)
+  const res = await cloudLogService.existSite(ctx.state, username, serialNumberList)
+  if (typeof res !== 'boolean') {
+    ctx.log.error('Serial number query failed', ctx.request.body)
+    // 抛出给下方报错的地方
+    ctx.app.emit('app error', res, ctx)
+    return
+  } else if (res === false) {
+    ctx.log.warn('Serial number does not exist', ctx.request.body)
+    ctx.app.emit('app error', ErrorCode.CloudSerialNumberNotExist, ctx)
+    return
+  }
+
+  await next()
+}
+
+export const verifyCmdType = async (ctx: Koa.Context, next: Koa.Next) => {
+  // 获取数据
+  const { logTypes } = checkReqCreateLogParams(ctx)
+  for (const type of logTypes) {
+    const cmd = Number(type)
+    if (cmd != 0 && cmd < 100 && cmd > 999)
+    {
+      ctx.log.warn('CmdType does not exist', ctx.request.body)
+      ctx.app.emit('app error', ErrorCode.CloudCmdTypeNotExist, ctx)
+      return
+    }
+  }
+
+  await next()
+}
+
+export const verifyTime = async (ctx: Koa.Context, next: Koa.Next) => {
+  const { startTime, endTime } = checkReqCreateLogParams(ctx)
+  if (startTime.getTime() >= endTime.getTime())
+  {
+    ctx.log.warn('Wrong time selection', ctx.request.body)
+    ctx.app.emit('app error', ErrorCode.CloudTimeRangeError, ctx)
+    return
+  }
+
+  await next()
+}

+ 22 - 0
src/middleware/jwt.middleware.ts

@@ -0,0 +1,22 @@
+import { Context, Next } from 'koa'
+import jwt from 'jsonwebtoken'
+import { SECRET_KEY } from '@/utils/jwt'
+import { ErrorCode } from '@/constant/err.type';
+
+// JWT 中间件
+export async function jwtMiddleware(ctx: Context, next: Next) {
+  const token = ctx.headers['x-access-token'] || ctx.headers['authorization']?.split(' ')[1];
+
+  if (!token) {
+    ctx.app.emit('app error', ErrorCode.JwtTokenNotExited, ctx)
+    return;
+  }
+
+  try {
+    const decoded = jwt.verify(token as string, SECRET_KEY);
+    ctx.state.user = decoded;  // 将用户信息存储在 ctx.state.user 中
+    await next();  // 继续执行后续中间件
+  } catch (err) {
+    ctx.app.emit('app error', ErrorCode.JwtTokenInvalid, ctx)
+  }
+}

+ 89 - 0
src/middleware/user.middleware.ts

@@ -0,0 +1,89 @@
+import userService from '@/service/user.service'
+import { ErrorCode } from '@/constant/err.type'
+import bcrypt from 'bcryptjs'
+import Koa from 'koa'
+import { checkReqLogin } from '@/api/user'
+import { ResultData } from '@/api/public'
+import { Login } from '@/api/user/interface'
+
+const userValidator = async (ctx: Koa.Context, next: Koa.Next) => {
+    const { username, password } = checkReqLogin(ctx)
+    // 合法性
+    if (!username || !password) {
+        ctx.log.error('用户名或密码为空', ctx.request.body)
+        // 抛出给下方报错的地方
+        // ctx.app.emit('app error', ErrorCode.UserFormateError, ctx)
+        // 改为正常回复,并包含错误信息
+        const body: ResultData<Login.ResLogin> = {
+            code: 200,
+            msg: '用户名或密码为空',
+            data: {
+                success: false,
+                msg: '用户名或密码为空'
+            }
+        }
+        ctx.body = body
+        return
+    }
+
+    await next()
+}
+
+const verifyUser = async (ctx: Koa.Context, next: Koa.Next) => {
+    const { username } = checkReqLogin(ctx)
+
+    try {
+        const res = await userService.getUserInfo({ username: username })
+
+        if (res) {
+            console.error('用户名已经存在', { username })
+            // ctx.app.emit('app error', ErrorCode.UserAlreadyExited, ctx)
+            // 改为正常回复,并包含错误信息
+            const body: ResultData<Login.ResLogin> = {
+                code: 200,
+                msg: '用户名已经存在',
+                data: {
+                    success: false,
+                    msg: '用户名已经存在'
+                }
+            }
+            ctx.body = body
+            return
+        }
+    } catch (err) {
+        console.error('获取用户信息错误', err)
+        ctx.app.emit('app error', ErrorCode.UserRegisterError, ctx)
+        // 改为正常回复,并包含错误信息
+        const body: ResultData<Login.ResLogin> = {
+            code: 200,
+            msg: '获取用户信息错误',
+            data: {
+                success: false,
+                msg: '获取用户信息错误'
+            }
+        }
+        ctx.body = body
+        return
+    }
+
+    await next()
+}
+
+const cryptPassword = async (ctx: Koa.Context, next: Koa.Next) => {
+    const { password } = checkReqLogin(ctx)
+
+    // const salt = bcrypt.genSaltSync(10)
+    // hash保存的是 密文
+    // const hash = bcrypt.hashSync(password, salt)
+    const hash = bcrypt.hashSync(password) // 默认加盐
+
+    ctx.request.body.password = hash
+
+    await next()
+}
+
+export default {
+  userValidator,
+  verifyUser,
+  cryptPassword,
+}

+ 144 - 0
src/model/cloudLog.model.ts

@@ -0,0 +1,144 @@
+import { Model, InferAttributes, InferCreationAttributes, CreationOptional, DataTypes } from 'sequelize'
+import seq from '@/utils/db/sequelize'
+
+export class CloudLog extends Model<InferAttributes<CloudLog>, InferCreationAttributes<CloudLog>> {
+  declare id: CreationOptional<number>;
+  declare serial_number: string;
+  declare count: CreationOptional<number>;
+  declare update_time: Date;
+  declare status: CreationOptional<number>;
+}
+
+// 创建云日志数据库连接
+// 参数 server:"production" or "testing"
+export async function connectCloudLogList(server: string) {
+  CloudLog.init({
+    id: {
+      type: DataTypes.INTEGER,
+      autoIncrement: true,
+      primaryKey: true
+    },
+    serial_number: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: true // 数据唯一索引
+    },
+    count: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0,
+    },
+    update_time: {
+      type: DataTypes.DATE,
+      allowNull: false,
+    },
+    status: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0,
+    }
+  }, {
+    sequelize: seq,
+    schema: server,
+    tableName: 'cloud_log',
+    timestamps: false,
+  })
+
+  try {
+    // 同步数据库
+    // force: true 会删除现有的表并重新创建
+    // alter: true 会同步修改数据表(注意)
+    // await model.sync({ force: false, alter: true });  
+    await CloudLog.sync(); 
+    console.log('数据库同步成功 - ', CloudLog.name);
+  } catch (error) {
+    console.error('数据库同步失败 - ', CloudLog.name, ': ', error);
+  }
+
+  return CloudLog
+}
+
+// 动态表
+export class SiteLogTable extends Model<InferAttributes<SiteLogTable>, InferCreationAttributes<SiteLogTable>> {
+  declare id: CreationOptional<number>;
+  declare serial_number: string;
+  declare cmd_type: number;
+  declare cmd_name: string;
+  declare network_status: number; // 网络状态 0-离线,1-在线
+  declare active_status: number; // 激活状态 0-未激活,1-已激活
+  declare time: Date; // 设备时间
+  declare server_time: Date; // 服务器通讯时间
+  declare data: object;
+}
+
+// 设备表创建(表名:SN)
+// 参数 server:"production" or "testing"
+export async function connectSiteTable(server: string, tableName: string) {
+  SiteLogTable.init({
+    id: {
+      type: DataTypes.INTEGER,
+      autoIncrement: true,
+      primaryKey: true
+    },
+    serial_number: {
+      type: DataTypes.STRING,
+      allowNull: false,
+    },
+    cmd_type: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+    },
+    cmd_name: {
+      type: DataTypes.STRING,
+      allowNull: false,
+    },
+    network_status: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+    },
+    active_status: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+    },
+    time: {
+      type: DataTypes.DATE,
+      allowNull: false,
+    },
+    server_time: {
+      type: DataTypes.DATE,
+      allowNull: false,
+    },
+    data: {
+      type: DataTypes.JSONB,
+      allowNull: false,
+    }
+  }, {
+    sequelize: seq,
+    schema: server + '_sitelog',
+    tableName: tableName,
+    timestamps: false,
+    indexes: [
+      // 定义单字段索引
+      {
+        name: tableName + '_cmd_type_index',  // 索引的名称
+        fields: ['cmd_type'],    // 索引字段
+      },
+      // 创建复合唯一约束
+      {
+        unique: true,
+        fields: ['cmd_type', 'time']
+      }
+    ]
+  })
+
+  try {
+    // 同步数据库
+    // force: true 会删除现有的表并重新创建
+    // alter: true 会同步修改数据表(注意)
+    // await model.sync({ force: false, alter: true });  
+    await SiteLogTable.sync(); 
+    console.log('数据库同步成功 - ', SiteLogTable.getTableName());
+  } catch (error) {
+    console.error('数据库同步失败 - ', SiteLogTable.getTableName(), ': ', error);
+  }
+
+  return SiteLogTable
+}

+ 4 - 0
src/model/emsLogManage.model.ts

@@ -0,0 +1,4 @@
+import { Schema } from 'mongoose'
+import mongodb from '@/utils/db/mongodb'
+
+

+ 15 - 0
src/model/model.ts

@@ -0,0 +1,15 @@
+
+/**
+ * Returns the list of attributes that are part of the model's primary key.
+ */
+export function getPrimaryKeyAttributes(model: ModelStatic<any>): ModelAttributeColumnOptions[] {
+  const attributes: ModelAttributeColumnOptions[] = [];
+
+  for (const attribute of Object.values(model.rawAttributes)) {
+    if (attribute.primaryKey) {
+      attributes.push(attribute);
+    }
+  }
+
+  return attributes;
+}

+ 266 - 0
src/model/siteManage.model.ts

@@ -0,0 +1,266 @@
+import { Model, InferAttributes, InferCreationAttributes, CreationOptional, DataTypes } from 'sequelize'
+import seq from '@/utils/db/sequelize'
+import { Schema } from 'mongoose'
+import mongodb from '@/utils/db/mongodb'
+import { SiteManage } from '@/api/siteManage/interface'
+
+interface SiteManageInfoSchema extends SiteManageInfo {
+  offlineInfo: Partial<SiteManage.ResSiteManageInfo>
+}
+
+interface SiteManageInfo extends Partial<SiteManage.ResSiteManageInfo> {
+  server: string;
+}
+
+interface ResSiteManageInfo extends SiteManage.ResSiteManageInfo {}
+
+const siteManageInfoSchema = new Schema<SiteManageInfoSchema>({
+  server: { 
+    type: String,
+    enum: ['production', 'testing'],
+    required: true,
+    unique: true
+  },
+  updateTime: { type: Date },
+  siteCount: { type: Number },
+  updateStatus: { 
+    type: String,
+    enum: ['RefreshFailed', 'NotInProgress', 'Refreshing', 'RefreshCompleted'],
+    required: true,
+    default: 'NotInProgress'
+  },
+  offlineInfo: {
+    updateTime: { type: Date },
+    siteCount: { type: Number },
+    updateStatus: { 
+      type: String,
+      enum: ['RefreshFailed', 'NotInProgress', 'Refreshing', 'RefreshCompleted'],
+      required: true,
+      default: 'NotInProgress'
+    },
+  }
+});
+
+const SiteManageInfoModel = mongodb?.model<SiteManageInfoSchema>('SiteManageInfo', siteManageInfoSchema);
+
+export const querySiteManageInfo = async (server: string) : Promise<ResSiteManageInfo | undefined> =>  {
+  // 数据库未连接
+  if (!SiteManageInfoModel) {
+    console.error('[siteManage.model] Error not connect to MongoDB.');
+    return undefined
+  }
+
+  const doc = await SiteManageInfoModel.findOne({ server }).exec()
+  // 未查到数据,返回默认值
+  if (!doc) return {
+    updateTime: new Date('invalid-date'),
+    siteCount: 0,
+    updateStatus: 'NotInProgress'
+  }
+
+  return {
+    updateTime: doc.updateTime? doc.updateTime : new Date('invalid-date'),
+    siteCount: doc.siteCount? doc.siteCount : 0,
+    updateStatus: doc.updateStatus? doc.updateStatus : 'NotInProgress'
+  }
+}
+
+export const querySiteOfflineInfo = async (server: string) : Promise<ResSiteManageInfo | undefined> =>  {
+  // 数据库未连接
+  if (!SiteManageInfoModel) {
+    console.error('[siteManage.model] Error not connect to MongoDB.');
+    return undefined
+  }
+
+  const doc = await SiteManageInfoModel.findOne({ server }).exec()
+  // 未查到数据,返回默认值
+  if (!doc) return {
+    updateTime: new Date('invalid-date'),
+    siteCount: 0,
+    updateStatus: 'NotInProgress'
+  }
+
+  return {
+    updateTime: doc.offlineInfo.updateTime? doc.offlineInfo.updateTime : new Date('invalid-date'),
+    siteCount: doc.offlineInfo.siteCount? doc.offlineInfo.siteCount : 0,
+    updateStatus: doc.offlineInfo.updateStatus? doc.offlineInfo.updateStatus : 'NotInProgress'
+  }
+}
+
+export const updateSiteManageInfo = async (info: SiteManageInfo) : Promise<boolean> => {
+  // 数据库未连接
+  if (!SiteManageInfoModel) {
+    console.error('[siteManage.model] Error not connect to MongoDB.');
+    return false
+  }
+
+  let siteManageInfo = await SiteManageInfoModel.findOne({ server: info.server }).exec()
+  if (!siteManageInfo) {
+    siteManageInfo = new SiteManageInfoModel({
+      server: info.server,
+      updateTime: info.updateTime? info.updateTime : new Date('invalid-date'),
+      siteCount: info.siteCount? info.siteCount : 0,
+      updateStatus: info.updateStatus? info.updateStatus : 'NotInProgress',
+      offlineInfo: {
+        updateTime: new Date('invalid-date'),
+        siteCount: 0,
+        updateStatus: 'NotInProgress',
+      }
+    })
+  } else {
+    if (info.updateTime) siteManageInfo.updateTime = info.updateTime;
+    if (info.siteCount) siteManageInfo.siteCount = info.siteCount;
+    if (info.updateStatus) siteManageInfo.updateStatus = info.updateStatus;
+  }
+  return await siteManageInfo.save()
+    .then(saveDoc => { return true })
+    .catch(err => {
+      console.error('[siteManage.model] Error Site Manage Info save to MongoDB failed:', err);
+      return false
+    })
+}
+
+export const updateSiteOfflineInfo = async (info: SiteManageInfo) : Promise<boolean> => {
+  // 数据库未连接
+  if (!SiteManageInfoModel) {
+    console.error('[siteManage.model] Error not connect to MongoDB.');
+    return false
+  }
+
+  let siteManageInfo = await SiteManageInfoModel.findOne({ server: info.server }).exec()
+  if (!siteManageInfo) {
+    siteManageInfo = new SiteManageInfoModel({
+      server: info.server,
+      updateTime: new Date('invalid-date'),
+      siteCount: 0,
+      updateStatus: 'NotInProgress',
+      offlineInfo: {
+        updateTime: info.updateTime? info.updateTime : new Date('invalid-date'),
+        siteCount: info.siteCount? info.siteCount : 0,
+        updateStatus: info.updateStatus? info.updateStatus : 'NotInProgress',
+      }
+    })
+  } else {
+    if (info.updateTime) siteManageInfo.offlineInfo.updateTime = info.updateTime;
+    if (info.siteCount) siteManageInfo.offlineInfo.siteCount = info.siteCount;
+    if (info.updateStatus) siteManageInfo.offlineInfo.updateStatus = info.updateStatus;
+  }
+  return await siteManageInfo.save()
+    .then(saveDoc => { return true })
+    .catch(err => {
+      console.error('[siteManage.model] Error Site Manage Info save to MongoDB failed:', err);
+      return false
+    })
+}
+
+export class SiteInfoTable extends Model<InferAttributes<SiteInfoTable>, InferCreationAttributes<SiteInfoTable>> {
+  declare id: CreationOptional<number>;
+  declare serialNumber: string;
+  declare activeStatus: number; // 激活状态:0-未激活,1-已激活
+  declare networkStatus: number; // 网络状态:0-离线,1-在线
+  declare iccId: CreationOptional<string>;
+  declare imei: CreationOptional<string>;
+  declare apowerSn: string[];
+  declare apowerNum: number;
+  declare timeZone: CreationOptional<number>;
+  declare zoneInfo: CreationOptional<string>;
+  declare country: CreationOptional<string>;
+  declare province: CreationOptional<string>;
+  declare city: CreationOptional<string>;
+  declare deviceAddress: CreationOptional<string>;
+  declare tags: CreationOptional<string[]>;
+
+  declare rawData: object;
+}
+
+// 创建云日志数据库连接
+// 参数 server:"production" or "testing"
+export async function connectSiteInfoList(server: string) {
+  SiteInfoTable.init({
+    id: {
+      type: DataTypes.INTEGER,
+      autoIncrement: true,
+      primaryKey: true
+    },
+    serialNumber: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: true // 数据唯一索引
+    },
+    activeStatus: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+    },
+    networkStatus: {
+      type: DataTypes.INTEGER,
+      allowNull: false,
+    },
+    iccId: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    imei: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    apowerSn: {
+      type: DataTypes.ARRAY(DataTypes.STRING),
+      allowNull: false,
+    },
+    apowerNum: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0,
+    },
+    timeZone: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0,
+    },
+    zoneInfo: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    country: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    province: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    city: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    deviceAddress: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    tags: {
+      type: DataTypes.ARRAY(DataTypes.STRING),
+      allowNull: true,
+      defaultValue: [],
+    },
+    rawData: {
+      type: DataTypes.JSONB,
+      allowNull: false,
+    }
+  }, {
+    sequelize: seq,
+    schema: server,
+    tableName: 'site_info',
+    timestamps: false,
+  })
+
+  try {
+    // 同步数据库
+    // force: true 会删除现有的表并重新创建
+    // alter: true 会同步修改数据表(注意)
+    // await model.sync({ force: false, alter: true });  
+    await SiteInfoTable.sync(); 
+    console.log('数据库同步成功 - ', SiteInfoTable.name);
+  } catch (error) {
+    console.error('数据库同步失败 - ', SiteInfoTable.name, ': ', error);
+  }
+
+  return SiteInfoTable
+}

+ 112 - 0
src/model/user.model.ts

@@ -0,0 +1,112 @@
+import { DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, Model } from 'sequelize'
+import seq from '@/utils/db/sequelize'
+
+// export interface User {
+//   id: number;
+//   user_name: string;
+//   password: string;
+//   admin: boolean;
+//   enable: boolean;
+  
+//   fwh_server: string;
+//   fwh_pro_username: string;
+//   fwh_pro_password: string;
+//   fwh_tst_username: string;
+//   fwh_tst_password: string;
+// }
+
+class UserModel extends Model<InferAttributes<UserModel>, InferCreationAttributes<UserModel>> {
+  declare id: CreationOptional<number>;
+  declare user_name: string;
+  declare password: string;
+  declare admin: CreationOptional<boolean>;
+  declare enable: CreationOptional<boolean>;
+
+  declare fwh_server: CreationOptional<string>;
+  declare fwh_pro_username: CreationOptional<string>;
+  declare fwh_pro_password: CreationOptional<string>;
+  declare fwh_tst_username: CreationOptional<string>;
+  declare fwh_tst_password: CreationOptional<string>;
+}
+
+export interface UserData extends InferAttributes<UserModel> {}
+
+const user = await (async () => {
+  UserModel.init(
+    {
+      id: {
+        type: DataTypes.INTEGER,
+        autoIncrement: true,
+        primaryKey: true
+      },
+      // 用户名
+      user_name: {
+        type: DataTypes.STRING,
+        allowNull: false,
+      },
+      // 密码
+      password: {
+        type: DataTypes.STRING,
+        allowNull: false,
+      },
+      // 是否是管理员账户
+      admin: {
+        type: DataTypes.BOOLEAN,
+        defaultValue: false,
+      },
+      // 账户是否验证通过
+      enable: {
+        type: DataTypes.BOOLEAN,
+        defaultValue: false,
+      },
+  
+      // FWH相关
+      fwh_server: {
+        type: DataTypes.STRING,
+        defaultValue: 'production',
+        allowNull: false,
+      },
+      fwh_pro_username: {
+        type: DataTypes.STRING,
+        defaultValue: '',
+        allowNull: false,
+      },
+      fwh_pro_password: {
+        type: DataTypes.STRING,
+        defaultValue: '',
+        allowNull: false,
+      },
+      fwh_tst_username: {
+        type: DataTypes.STRING,
+        defaultValue: '',
+        allowNull: false,
+      },
+      fwh_tst_password: {
+        type: DataTypes.STRING,
+        defaultValue: '',
+        allowNull: false,
+      },
+      
+    }, {
+      sequelize: seq,
+      schema: 'public',
+      tableName: 'test_user',
+      timestamps: false,
+    }
+  )
+
+  try {
+    // 同步数据库
+    // force: true 会删除现有的表并重新创建
+    // alter: true 会同步修改数据表(注意)
+    // await UserModel.sync({ force: false, alter: true });  
+    await UserModel.sync({ force: false, alter: true }); 
+    console.log('数据库同步成功 - ', UserModel.getTableName());
+  } catch (error) {
+    console.error('数据库同步失败 - ', UserModel.getTableName(), ': ', error);
+  }
+
+  return UserModel
+})()
+
+export default user

+ 138 - 0
src/model/warehouse.model.ts

@@ -0,0 +1,138 @@
+import { Model, InferAttributes, InferCreationAttributes, CreationOptional, DataTypes } from 'sequelize'
+import seq from '@/utils/db/sequelize'
+
+export interface Warehouse {
+  id: number;
+  apowerSn: string;
+  address: string;
+}
+
+class WarehouseModel extends Model<InferAttributes<WarehouseModel>, InferCreationAttributes<WarehouseModel>> {
+  declare id: CreationOptional<number>;
+  declare apowerSn: string;
+  declare address: string;
+  declare afterRechargeSoc: CreationOptional<number>; // 下次补电SOC
+  declare beforeRechargeSoc: CreationOptional<number>; // 上次补电SOC
+  declare createTime: string; // 条目创建时间
+  declare currentSoc: CreationOptional<number>;
+  declare delFlag: number;
+  declare deliveryTime: string;
+  declare distributor:string;
+  declare needRecharge: number;
+  declare nextRechargeTime: string;
+  declare orderNo: string;
+  declare outboundTime: string;
+  declare productionTime: string;
+  declare rechargeTime: CreationOptional<string>;
+  declare socDecreaseTime: string;
+  declare updateTime: string;
+  declare version: CreationOptional<string>;
+  declare warehouseName: string;
+}
+
+const warehouse = await (async () => {
+  WarehouseModel.init({
+    id: {
+      type: DataTypes.INTEGER,
+      autoIncrement: true,
+      primaryKey: true
+    },
+    apowerSn: {
+      type: DataTypes.STRING,
+      allowNull: false,
+      unique: true // 数据唯一索引
+    },
+    address: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    afterRechargeSoc: {
+      type: DataTypes.INTEGER,
+      allowNull: true,
+    },
+    beforeRechargeSoc: {
+      type: DataTypes.INTEGER,
+      allowNull: true,
+    },
+    createTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    currentSoc: {
+      type: DataTypes.INTEGER,
+      allowNull: true,
+    },
+    delFlag: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    deliveryTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    distributor: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    needRecharge: {
+      type: DataTypes.INTEGER,
+      defaultValue: 0
+    },
+    nextRechargeTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    orderNo: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    outboundTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    productionTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    rechargeTime: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    socDecreaseTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    updateTime: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    },
+    version: {
+      type: DataTypes.STRING,
+      allowNull: true,
+    },
+    warehouseName: {
+      type: DataTypes.STRING,
+      defaultValue: '',
+    }
+  }, {
+    sequelize: seq,
+    schema: 'public',
+    tableName: 'warehouse_list',
+    timestamps: false,
+  })
+
+  try {
+    // 同步数据库
+    // force: true 会删除现有的表并重新创建
+    // alter: true 会同步修改数据表(注意)
+    // await model.sync({ force: false, alter: true });  
+    await WarehouseModel.sync(); 
+    console.log('数据库同步成功 - ', WarehouseModel.name);
+  } catch (error) {
+    console.error('数据库同步失败 - ', WarehouseModel.name, ': ', error);
+  }
+
+  return WarehouseModel
+})()
+
+export default warehouse

+ 17 - 0
src/queue/board.queue.ts

@@ -0,0 +1,17 @@
+import { createBullBoard } from '@bull-board/api'
+import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'
+import { KoaAdapter } from '@bull-board/koa'
+
+import { queue as FwhServerTaskQueue } from './fwhServerTask'
+
+const serverAdapter = new KoaAdapter();
+
+createBullBoard({
+  queues: [new BullMQAdapter(FwhServerTaskQueue)],
+  serverAdapter,
+});
+
+serverAdapter.setBasePath('/bull-board');
+// await app.use(serverAdapter.registerPlugin());
+
+export default serverAdapter

+ 380 - 0
src/queue/fwhServerTask/getCloudLog.task.ts

@@ -0,0 +1,380 @@
+import { isBefore, differenceInDays, subDays } from 'date-fns'
+import { querySiteMqttLogBaseSiteTime } from '@/external/fwh/api/getMqttLog'
+import { querySiteListInSerialNumber } from '@/external/fwh/api/getSiteList'
+import { MqttLog } from "@/external/fwh/api/getMqttLog/dataType"
+import { connectCloudLogList, SiteLogTable, connectSiteTable } from '@/model/cloudLog.model'
+import { insertCloudLog } from '@/cache/cloudLogList.cache'
+import { addGetCloudLogTask } from '.'
+import { ErrorCode } from '@/constant/err.type'
+import logger from '@/logger'
+import { string } from 'zod'
+
+// 配置每次请求的最大天数,天数越小,对FWH服务器的查询压力越小
+// 请求每段不能超过30天
+const QueryDataMaxDays = 1
+
+export interface GetCloudLogTaskParams {
+  userName: string;
+  server: string;
+  siteInfoList: {
+    serialNumber: string;
+    timeZone?: number;
+  }[];
+  cmdTypes: string[];
+  timeBegin: Date;
+  timeEnd: Date;
+}
+
+async function importDatabase(server: string, serialNumber: string, dataList: MqttLog[]) {
+  const table = await connectSiteTable(server, serialNumber)
+  try {
+    await table.bulkCreate<SiteLogTable>(dataList.map(obj => {
+      return {
+        serial_number: obj.gatewayId,
+        cmd_type: Number(obj.cmdType),
+        cmd_name: obj.cmdName,
+        network_status: Number(obj.gatewayStatus),
+        active_status: Number(obj.activeStatus),
+        time: new Date(obj.logTime),
+        server_time: new Date(obj.time),
+        data: JSON.parse(obj.logData)
+      }
+    }), 
+    {
+      ignoreDuplicates: true,
+      conflictAttributes: [
+        'cmd_type',
+        'time'
+      ]
+    })
+  } catch (error) {
+    console.error(`Error insert ${serialNumber} table:`, error);
+  }
+
+  const count = await table.count()
+  const cloudLogModel = await connectCloudLogList(server)
+  cloudLogModel.update(
+    { 
+      count
+    },
+    {
+      where: {
+        serial_number: serialNumber
+      }
+    }
+  )
+}
+
+async function taskCompleted(server: string, serialNumber: string) {
+  const cloudLogModel = await connectCloudLogList(server)
+  cloudLogModel.update(
+    { 
+      status: 1, // 读取完成
+    },
+    {
+      where: {
+        serial_number: serialNumber
+      }
+    }
+  )
+}
+
+async function taskFailed(server: string, serialNumber: string) {
+  const cloudLogModel = await connectCloudLogList(server)
+  cloudLogModel.update(
+    { 
+      status: -1, // 失败
+    },
+    {
+      where: {
+        serial_number: serialNumber
+      }
+    }
+  )
+}
+
+async function checkSerialNumber(userName: string, serialNumber: string) : Promise<{result: boolean, code: ErrorCode, timeZone: number}> {
+  // 检查SN是否存在
+  const res = await querySiteListInSerialNumber(userName, serialNumber)
+  console.log("querySiteListInSerialNumber: ", res)
+  if (typeof res !== 'object') {
+    return {
+      result: false,
+      code: res,
+      timeZone: 0
+    }
+  } else {
+    for(const result of res.result) {
+      if (result.id === serialNumber) {
+        return {
+          result: true,
+          code: ErrorCode.NoError,
+          timeZone: result.timeZone ? result.timeZone : 0
+        }
+      }
+    }
+
+    return {
+      result: false,
+      code: ErrorCode.NoError,
+      timeZone: 0
+    }
+  }
+}
+
+interface TimePeriodItem {
+  begin: Date;
+  end: Date;
+}
+
+function getTimeList(startTime: Date, endTime: Date, intervalDays: number): TimePeriodItem[] {
+  let timeList: {begin: Date, end: Date}[] = []
+  let itemBegin = startTime
+  let itemEnd = endTime
+  while (isBefore(startTime, itemEnd)) {
+    if (Math.abs(differenceInDays(startTime, itemEnd)) > intervalDays) {
+      itemBegin = subDays(itemEnd, intervalDays)
+      timeList.push({
+        begin: itemBegin,
+        end: itemEnd
+      })
+      console.log("push : ", {
+        begin: itemBegin,
+        end: itemEnd
+      })
+      itemEnd = itemBegin
+    } else {
+      timeList.push({
+        begin: startTime,
+        end: itemEnd
+      })
+      console.log("push : ", {
+        begin: startTime,
+        end: itemEnd
+      })
+      itemEnd = startTime
+    }
+  }
+  return timeList
+}
+
+async function getSiteMqttLog(
+  userName: string,
+  server: string,
+  serialNumber: string, 
+  cmdType: string | undefined, 
+  timeBegin: Date, 
+  timeEnd: Date,
+  timeZone: number
+) {
+  // 请求云数据
+  const logs = await querySiteMqttLogBaseSiteTime(
+    userName,
+    serialNumber,
+    cmdType,
+    timeBegin,
+    timeEnd,
+    timeZone
+  )
+
+  if (typeof logs !== 'object') {
+    await taskFailed(server, serialNumber)
+    // 返回错误结果
+    throw new Error(`error 云日志请求失败, error code ${logs}`);
+
+    // TODO 任务失败,需要将失败信息记录到数据库
+  }
+
+  // 数据插入
+  await importDatabase(server, serialNumber, logs)
+}
+
+// async function getSiteMqttLog(
+//   userName: string,
+//   server: string,
+//   serialNumber: string, 
+//   cmdTypes: string[], 
+//   timeBegin: Date, 
+//   timeEnd: Date,
+//   timeZone: number
+// ) {
+//   // 请求每段不能超过30天
+//   // 拆分请求时间段,每段最多1天
+//   const queryTimeList = getTimeList(timeBegin, timeEnd, QueryDataMaxDays)
+//   console.log("queryTime: ", queryTimeList)
+//   logger.info("queryTime", queryTimeList)
+
+//   for (const timeItem of queryTimeList) {
+//     // const cmdList = cmdTypes.split(',').map(item => item.trim())
+//     for (const cmdType of cmdTypes) {
+//       // 请求云数据
+//       const logs = await querySiteMqttLogBaseSiteTime(
+//         userName,
+//         serialNumber,
+//         cmdType,
+//         timeItem.begin,
+//         timeItem.end,
+//         timeZone
+//       )
+
+//       if (typeof logs !== 'object') {
+//         await taskFailed(server, serialNumber)
+//         // 返回错误结果
+//         throw new Error(`error 云日志请求失败, error code ${logs}`);
+//       }
+    
+//       // 数据插入
+//       await importDatabase(server, serialNumber, logs)
+
+//       // 设置请求间隔 5000ms
+//       await new Promise(resolve => setTimeout(resolve, 5000));
+//     }
+
+//     // 设置请求间隔 10000ms
+//     await new Promise(resolve => setTimeout(resolve, 10000));
+//   }
+// }
+
+async function taskSplit(
+  userName: string, 
+  server: string, 
+  siteInfoList: {
+    serialNumber: string,
+    timeZone?: number
+  }[], 
+  cmdTypes: string[], 
+  timeList: TimePeriodItem[]
+) {
+  for (const siteInfo of siteInfoList) {
+    if (siteInfo.timeZone === undefined) {
+      // 检查SN是否存在, 顺带获取时区
+      const res = await checkSerialNumber(userName, siteInfo.serialNumber)
+      if (!res.result) {
+        if (res.code === ErrorCode.NoError) {
+          // SN不存在,跳过SN
+          console.log("Invalid serial number: ", siteInfo.serialNumber)
+          // TODO 跳过SN,需要记录到数据库
+
+          continue
+        } else {
+          // 返回错误结果,任务失败
+          throw new Error(`error 云日志请求失败, error code ${res.code}`);
+        }
+      } else {
+        siteInfo.timeZone = res.timeZone
+      }
+    }
+
+    let taskParamList = cmdTypes.map(type => {
+      return timeList.map(timeItem => {
+        return {
+          userName,
+          server,
+          siteInfoList: [{...siteInfo}],
+          cmdTypes: [type],
+          timeBegin: timeItem.begin,
+          timeEnd: timeItem.end
+        }
+      })
+    }).flat()
+
+    // cmdTypes数据数量为0时处理获取所有类型日志
+    if (taskParamList.length === 0) {
+      taskParamList = timeList.map(timeItem => {
+        return {
+          userName,
+          server,
+          siteInfoList: [{...siteInfo}],
+          cmdTypes: [] as string[],
+          timeBegin: timeItem.begin,
+          timeEnd: timeItem.end
+        }
+      })
+    }
+
+    for (const param of taskParamList) {
+      await addGetCloudLogTask({...param})
+    }
+  }
+}
+
+async function handleSiteTask(
+  userName: string, 
+  server: string, 
+  serialNumber: string, 
+  cmdType: string | undefined, 
+  timeBegin: Date, 
+  timeEnd: Date,
+  timeZone: number
+) {
+  // 将创建数据更新到数据库
+  await insertCloudLog(server, serialNumber)
+
+  await getSiteMqttLog(
+    userName,
+    server,
+    serialNumber,
+    cmdType,
+    timeBegin,
+    timeEnd,
+    timeZone
+  )
+
+  await taskCompleted(server, serialNumber)
+}
+
+export async function getCloudLogJob (data: GetCloudLogTaskParams): Promise<void> {
+  console.log('执行getCloudLogJob任务')
+
+  // 获取任务数据
+	const {
+    userName,
+    server,
+    siteInfoList,
+		cmdTypes,
+		timeBegin,
+		timeEnd
+	} = data
+
+  if ((siteInfoList.length > 1)
+    || (cmdTypes.length > 1)
+    || (Math.abs(differenceInDays(timeBegin, timeEnd)) > QueryDataMaxDays)
+  ) {
+    // 生成请求时间段
+    const queryTimeList = getTimeList(timeBegin, timeEnd, QueryDataMaxDays)
+    console.log("queryTime: ", queryTimeList)
+    logger.info("queryTime", queryTimeList)
+    
+    taskSplit(
+      userName,
+      server,
+      siteInfoList,
+      cmdTypes,
+      queryTimeList
+    )
+
+    console.log('执行getCloudLogJob任务分配完成')
+    return
+  } else if (siteInfoList.length < 1) {
+    // 返回错误结果,任务失败
+    throw new Error("error 任务参数错误");
+  }
+
+  let cmdType : string | undefined = undefined
+  if (cmdTypes.length >= 1) {
+    cmdType = cmdTypes[0]
+  }
+
+  await handleSiteTask(
+    userName, 
+    server, 
+    siteInfoList[0].serialNumber,
+    cmdType,
+    timeBegin,
+    timeEnd,
+    siteInfoList[0].timeZone ? siteInfoList[0].timeZone : 0
+  )
+
+  console.log('执行getCloudLogJob任务完成')
+  logger.info(data, '执行getCloudLogJob任务完成')
+}

+ 131 - 0
src/queue/fwhServerTask/getEmsLog.task.ts

@@ -0,0 +1,131 @@
+import { format } from 'date-fns'
+import { requestDownloadEmsLog, queryDownloadStatus } from '@/external/fwh/api/getEmsLog'
+import { EmsLogKeyType } from '@/external/fwh/api/getEmsLog/dataType'
+import { querySiteListInSerialNumber } from '@/external/fwh/api/getSiteList'
+import { downloadEmsLog } from "@/utils/download"
+import logger from '@/logger';
+
+export interface GetEmsLogTaskParams {
+  userName: string;
+  server: string;
+  serialNumber: string[];
+  logType: EmsLogKeyType;
+  startDate: Date;
+  endDate: Date;
+}
+
+async function taskFailed(server: string) {
+  // TODO 
+}
+
+export async function getEmsLogJob(data: GetEmsLogTaskParams): Promise<void> {
+  console.log('执行getEmsLogJob任务')
+
+  // 获取任务数据
+	const {
+    userName,
+    server,
+    serialNumber,
+    logType,
+    startDate,
+    endDate
+	} = data
+
+  // 时间范围是否有效
+  if (endDate.isBefore(startDate)) {
+    await taskFailed(server)
+    // 返回错误结果
+    throw new Error(`[getEmsLog.task] error 时间范围无效, error date ${format(startDate, 'YYYY-MM-DD')}-${format(endDate, 'YYYY-MM-DD')}`);
+  }
+
+  for (const siteSn of serialNumber) {
+    let downloadDate = startDate
+    do {
+      // 判断设备是否在线
+      const isOnline = await (async () => {
+        for (const _ of Array(3)) { // 最多尝试3次
+          const siteRes = await querySiteListInSerialNumber(userName, siteSn)
+          if (typeof siteRes !== 'object') {
+            await taskFailed(server)
+            // 返回错误结果
+            throw new Error(`[getEmsLog.task] error 站点信息获取失败, error code ${siteRes}`);
+          }
+
+          // 延时 1000ms
+          await new Promise(resolve => setTimeout(resolve, 1000));
+
+          for (const siteData of siteRes.result) {
+            if (siteData.id === siteSn) {
+              if (siteData.status === '1') return true
+              else break
+            }
+          }
+        }
+        return false
+      })()
+      if (!isOnline) {
+        logger.warn(`EMS日志捞取失败,${siteSn}设备不在线。。。`)
+        break
+      }
+
+      const downUrl = await (async () => {
+        for (const _ of Array(3)) { // 最多尝试3次
+          // 请求捞取EMS日志
+          const reqResult = await requestDownloadEmsLog(userName, siteSn, logType, downloadDate)
+          if (typeof reqResult !== 'object') {
+            await taskFailed(server)
+            // 返回错误结果
+            throw new Error(`[getEmsLog.task] error 请求捞取EMS日志失败, error code ${reqResult}`);
+          }
+          if (!reqResult) {
+            logger.warn(`EMS日志捞取请求失败-${siteSn}。。。`)
+            break
+          }
+        
+          let status: number = 0
+          do {
+            // 延时 10 * 1000ms
+            await new Promise(resolve => setTimeout(resolve, 10 * 1000));
+
+            // 查询捞取状态
+            const dowloadStatus = await queryDownloadStatus(userName, siteSn, logType, downloadDate)
+            if (typeof dowloadStatus !== 'object') {
+              await taskFailed(server)
+              // 返回错误结果
+              throw new Error(`[getEmsLog.task] error 查询EMS日志捞取状态失败, error code ${dowloadStatus}`);
+            }
+            if (dowloadStatus === undefined) {
+              await taskFailed(server)
+              // 返回错误结果
+              throw new Error(`[getEmsLog.task] error 无EMS日志捞取记录`);
+            }
+
+            status = dowloadStatus.status
+            if (status === 1) {
+              return dowloadStatus.fileUrl
+            }
+          } while(status === 0)
+        }
+      })()
+
+      if (downUrl === undefined) {
+        logger.warn(`EMS日志捞取请求失败-${siteSn}-${downloadDate.format('YYYY-MM-DD')}。。。`)
+        break
+      }
+
+      // 下载文件
+      const downloadResult = await downloadEmsLog(
+        logType, 
+        'https://energy.franklinwh.com/hes-upload/v1/file/download/' + downUrl,
+        siteSn,
+        downloadDate
+      )
+
+      if (downloadResult) {
+        // TODO 处理下载结果
+      }
+
+      downloadDate = downloadDate.add(1, 'day')
+    } while (!downloadDate.isAfter(endDate))
+  }
+}

+ 288 - 0
src/queue/fwhServerTask/getSiteInfo.task.ts

@@ -0,0 +1,288 @@
+import { querySiteListAll, querySiteListInSerialNumber, queryOfflineSiteListAll } from '@/external/fwh/api/getSiteList'
+import { queryAllSiteMqttLogBaseServerTime } from '@/external/fwh/api/getMqttLog'
+import { SiteInfo } from "@/external/fwh/api/getSiteList/dataType"
+import { querySiteManageInfo, updateSiteManageInfo, updateSiteOfflineInfo, SiteInfoTable, connectSiteInfoList } from '@/model/siteManage.model'
+import { getLoginResult } from '@/external/fwh/login'
+import { sendEvent } from '@/router/siteManage.route'
+import { Op } from 'sequelize'
+
+export interface GetSiteInfoTaskParams {
+  userName: string;
+  server: string;
+}
+
+export interface GetPartSiteInfoTaskParams extends GetSiteInfoTaskParams {
+  startTime: Date;
+}
+
+async function queryDataBaseOfflineSite(server: string) {
+  const table = await connectSiteInfoList(server)
+  const where = {
+    activeStatus: { [Op.eq]: 1 },
+    networkStatus: { [Op.eq]: 0 }
+  }
+  const findData = await table.findAll({
+    where,
+    order: [['id', 'DESC']]
+  })
+    .then(value => {
+      console.log(value)
+      return value
+    })
+    .catch(err => {
+      console.error(`数据库查询失败 - ${table.name} : , ${err}`)
+      return [] as SiteInfoTable[]
+    })
+
+  return findData
+}
+
+async function importDatabase(server: string, dataList: SiteInfo[]) {
+  const table = await connectSiteInfoList(server)
+  try {
+    const siteList = dataList.map(item => {
+      return {
+        serialNumber: item.id,
+        activeStatus: Number(item.activeStatus),
+        networkStatus: Number(item.status),
+        iccId: item.iccId? item.iccId : '',
+        imei: item.imei? item.imei : '',
+        apowerSn: [...(item.fhpSn? item.fhpSn.split(',') : [])],
+        apowerNum: item.fhpSn? item.fhpSn.split(',').length : 0,
+        timeZone: item.timeZone? item.timeZone : 0,
+        zoneInfo: item.zoneInfo? item.zoneInfo : '',
+        country: item.country? item.country : '',
+        province: item.province? item.province : '',
+        city: item.city? item.city : '',
+        deviceAddress: item.deviceAddress? item.deviceAddress : '',
+
+        rawData: item
+      }
+    })
+
+    await table.bulkCreate<SiteInfoTable>(
+      siteList, 
+      {
+        updateOnDuplicate: [
+          'activeStatus',
+          'networkStatus',
+          'iccId',
+          'imei',
+          'apowerSn',
+          'apowerNum',
+          'timeZone',
+          'zoneInfo',
+          'country',
+          'province',
+          'city',
+          'deviceAddress',
+          'rawData'
+        ]
+      }
+    )
+  } catch (error) {
+    console.error('Error insert site info table:', error);
+  }
+
+  const count = await table.count()
+  await updateSiteManageInfo({
+    server,
+    siteCount: count,
+    updateStatus: 'RefreshCompleted'
+  })
+
+  const offlineCount = await table.count({
+    where: { 
+      activeStatus: { [Op.eq]: 1 },
+      networkStatus: { [Op.eq]: 0 } 
+    }
+  })
+  await updateSiteOfflineInfo({
+    server,
+    siteCount: offlineCount,
+    updateStatus: 'RefreshCompleted'
+  })
+}
+
+async function taskFailed(server: string) {
+  await updateSiteManageInfo({
+    server,
+    updateStatus: 'RefreshFailed'
+  })
+}
+
+async function taskOfflineFailed(server: string) {
+  await updateSiteOfflineInfo({
+    server,
+    updateStatus: 'RefreshFailed'
+  })
+}
+
+export async function getSiteInfoJob (data: GetSiteInfoTaskParams): Promise<void> {
+  console.log('执行getSiteInfoJob任务')
+
+  // 获取任务数据
+	const {
+    userName,
+    server
+	} = data
+
+  // 获取登录信息
+  const login = await getLoginResult(userName)
+  if (typeof login !== 'object') {
+    await taskFailed(server)
+    // 返回错误结果
+    throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${login}`);
+  }
+
+  // 请求云数据
+  const siteList = await querySiteListAll(userName)
+
+  if (typeof siteList !== 'object') {
+    await taskFailed(server)
+    // 返回错误结果
+    throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${siteList}`);
+  }
+
+  // 数据插入
+  await importDatabase(server, siteList)
+}
+
+export async function getPartSiteInfoJob (data: GetPartSiteInfoTaskParams): Promise<void> {
+  console.log('执行getPartSiteInfoJob任务')
+
+  // 获取任务数据
+	const {
+    userName,
+    server,
+    startTime
+	} = data
+
+  // 获取登录信息
+  const login = await getLoginResult(userName)
+  if (typeof login !== 'object') {
+    await taskFailed(server)
+    // 返回错误结果
+    throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${login}`);
+  }
+
+  // 请求云数据
+  // const siteList = await querySiteListAll(userName)
+
+  // 请求绑定/解绑数据
+  const logs = await queryAllSiteMqttLogBaseServerTime(userName, '104', startTime, new Date())
+  if (typeof logs !== 'object') {
+    await taskFailed(server)
+    // 返回错误结果
+    throw new Error(`error 云日志请求失败, error code ${logs}`);
+  }
+  let bindingList: string[] = []
+  logs.map(item => {
+    bindingList.push(item.gatewayId)
+  })
+
+  // 设置请求间隔 5000ms
+  await new Promise(resolve => setTimeout(resolve, 5000));
+
+  // 请求单个数据
+  let siteList: SiteInfo[] = []
+  for (const [index, sn] of bindingList.entries()) {
+    const siteData = await querySiteListInSerialNumber(userName, sn)
+    if (typeof siteData !== 'object') {
+      await taskFailed(server)
+      // 返回错误结果
+      throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${siteList}`);
+    }
+    siteList.push(...siteData.result)
+
+    // 设置请求间隔 3000ms
+    await new Promise(resolve => setTimeout(resolve, 3000));
+
+    // 打印进度
+    console.log(`total: ${siteList.length}, index: ${index}, SN: ${sn}`)
+  }
+
+  // 数据插入
+  await importDatabase(server, siteList)
+}
+
+export async function getOfflineSiteInfoJob (data: GetSiteInfoTaskParams): Promise<void> {
+  console.log('执行getOfflineSiteInfoJob任务')
+
+  // 获取任务数据
+	const {
+    userName,
+    server
+	} = data
+
+  // 获取登录信息
+  const login = await getLoginResult(userName)
+  if (typeof login !== 'object') {
+    await taskOfflineFailed(server)
+    // 返回错误结果
+    throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${login}`);
+  }
+
+  // 请求云离线站点(激活)数据
+  console.log('请求云离线站点(激活)数据')
+  let offlineSiteList = await queryOfflineSiteListAll(userName)
+
+  if (typeof offlineSiteList !== 'object') {
+    await taskOfflineFailed(server)
+    // 返回错误结果
+    throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${offlineSiteList}`);
+  }
+
+  // 提取数据库离线站(激活)点表
+  console.log('提取数据库离线站(激活)点表')
+  const dateBaseOfflineSite = await queryDataBaseOfflineSite(server)
+
+  // 对比是否有离线站点上线
+  let siteList: string[] = []
+  dateBaseOfflineSite.map(item => {
+    const siteSn = item.serialNumber
+    let notInclude = true
+    for (const site of offlineSiteList) {
+      if (site.id === siteSn) {
+        notInclude = false
+        break
+      }
+    }
+    if (notInclude) siteList.push(siteSn)
+  })
+
+  // 获取离线站点表未涉及的部分数据
+  console.log('获取离线站点表未涉及的部分数据')
+  console.log("siteList : ", siteList)
+  for (const [index, siteSn] of siteList.entries()) {
+    const siteData = await querySiteListInSerialNumber(userName, siteSn)
+    if (typeof siteData !== 'object') {
+      await taskOfflineFailed(server)
+      // 返回错误结果
+      throw new Error(`[getSiteInfo.task] error 站点信息获取失败, error code ${siteList}`);
+    }
+    offlineSiteList.push(...siteData.result)
+
+    // 设置请求间隔 3000ms
+    await new Promise(resolve => setTimeout(resolve, 3000));
+
+    // 打印进度
+    console.log(`total: ${siteList.length}, index: ${index}, SN: ${siteSn}`)
+  }
+
+  // 数据插入
+  await importDatabase(server, offlineSiteList)
+}
+
+export async function completedSiteInfoJob(data: GetSiteInfoTaskParams) {
+  console.log('执行完成SiteInfoJob任务')
+
+  // 获取任务数据
+	const {
+    server
+	} = data
+
+  const info = await querySiteManageInfo(server)
+  if (!info) return
+  sendEvent(info)
+}

+ 93 - 0
src/queue/fwhServerTask/getWarehouseList.task.ts

@@ -0,0 +1,93 @@
+import { Job } from "bullmq"
+import { queryWarehouseList } from '@/external/fwh/api/getWarehouseList'
+import { WarehouseData } from "@/external/fwh/api/getWarehouseList/dataType"
+import warehouseModel from '@/model/warehouse.model'
+
+export interface GetCloudLogTaskParams {
+  userName: string;
+  server: string;
+  serialNumber: string;
+  cmdTypes: string[];
+  timeBegin: Date;
+  timeEnd: Date;
+  timeZone: number
+}
+
+async function importDatabase(serialNumber: string, dataList: WarehouseData[]) {
+  try {
+    await warehouseModel.bulkCreate(dataList.map(obj => {
+      return {
+        apowerSn: obj.apowerSn,
+        address: obj.address,
+        afterRechargeSoc: obj.afterRechargeSoc,
+        beforeRechargeSoc: obj.beforeRechargeSoc,
+        createTime: obj.createTime,
+        currentSoc: obj.currentSoc,
+        delFlag: obj.delFlag,
+        deliveryTime: obj.deliveryTime,
+        distributor: obj.distributor,
+        needRecharge: obj.needRecharge,
+        nextRechargeTime: obj.nextRechargeTime,
+        orderNo: obj.orderNo,
+        outboundTime: obj.outboundTime,
+        productionTime: obj.productionTime,
+        rechargeTime: obj.rechargeTime,
+        socDecreaseTime: obj.socDecreaseTime,
+        updateTime: obj.updateTime,
+        version: obj.version,
+        warehouseName: obj.warehouseName,
+      }
+      }),
+      {
+        ignoreDuplicates: true // 忽略重复记录
+        // updateOnDuplicate: [
+        //   'address', 
+        //   'afterRechargeSoc', 
+        //   'beforeRechargeSoc',
+        //   'createTime',
+        //   'currentSoc',
+        //   'delFlag',
+        //   'deliveryTime',
+        //   'distributor',
+        //   'needRecharge',
+        //   'nextRechargeTime',
+        //   'orderNo',
+        //   'outboundTime',
+        //   'productionTime',
+        //   'rechargeTime',
+        //   'socDecreaseTime',
+        //   'updateTime',
+        //   'version',
+        //   'warehouseName'
+        // ]  // 如果数据重复,更新这些字段
+      }
+    )
+  } catch (error) {
+    console.error(`Error insert ${serialNumber} table:`, error);
+  }
+}
+
+async function getCloudLogJob (job: Job<GetCloudLogTaskParams, void>): Promise<void> {
+  console.log('执行queue任务的数据:', job.data)
+  console.log('执行queue任务的参数:', job.opts)
+  console.log('执行queue任务的整体信息:', job.toJSON())
+
+  // 获取任务数据
+	const {
+    userName,
+		serialNumber
+	} = job.data
+
+  // 请求云数据
+  const logs = await queryWarehouseList(userName)
+
+  if (typeof logs !== 'object') {
+    // 返回错误结果
+    throw new Error(`error 云日志请求失败, error code ${logs}`);
+  }
+
+  // 数据插入
+  await importDatabase(serialNumber, logs)
+}
+
+export default getCloudLogJob

+ 121 - 0
src/queue/fwhServerTask/index.ts

@@ -0,0 +1,121 @@
+import { Queue, Worker, Job } from 'bullmq'
+import { queueOptions } from '../task.queue'
+import { getCloudLogJob, GetCloudLogTaskParams } from './getCloudLog.task'
+import { getSiteInfoJob, getPartSiteInfoJob, getOfflineSiteInfoJob, completedSiteInfoJob, GetSiteInfoTaskParams, GetPartSiteInfoTaskParams } from './getSiteInfo.task'
+import logger from '@/logger';
+
+interface TaskParams<T> {
+	taskName: string;
+	data: T;
+}
+
+type TaskParamData = GetCloudLogTaskParams | GetSiteInfoTaskParams | GetPartSiteInfoTaskParams
+
+const fwhServerTaskScheduler = async (job: Job<TaskParams<TaskParamData>, void, string>) : Promise<void> => {
+	console.log('执行queue任务的数据:', job.data)
+  console.log('执行queue任务的参数:', job.opts)
+  console.log('执行queue任务的整体信息:', job.toJSON())
+	logger.info(job.data, '执行queue任务的数据')
+
+	const data = job.data
+	switch (data.taskName) {
+	case 'GetCloudLog':
+		await getCloudLogJob(data.data as GetCloudLogTaskParams)
+		break
+	case 'GetSiteInfo':
+		await getSiteInfoJob(data.data as GetSiteInfoTaskParams)
+		break
+	case 'GetPartSiteInfo':
+		await getPartSiteInfoJob(data.data as GetPartSiteInfoTaskParams)
+		break
+	case 'GetOfflineSiteInfo':
+		await getOfflineSiteInfoJob(data.data as GetSiteInfoTaskParams)
+		break
+	default:
+		console.error('[fwhServerTask.queue] Unable to perform this task : ', data.taskName)
+		break
+	}
+}
+
+export const queue = await (async function initTaskQueue(): Promise<Queue> {
+	// 给队列任务一个名字,后面参数进行队列执行配置
+	const queueTask: Queue = new Queue('FwhServerTask', queueOptions)
+
+	const worker: Worker = new Worker<TaskParams<TaskParamData>, void>('FwhServerTask', fwhServerTaskScheduler, queueOptions)
+
+	// 监听处理事件
+	worker.on('progress', function (job, progress) {
+		console.log(`Job ${job.id} is ${progress.toString}% ready!`);
+	})
+	// 监听完成事件
+	worker.on('completed', async function (job, result) {
+		const data = job.data
+		switch (data.tableName) {
+		case 'GetCloudLog':
+			break
+		case 'GetSiteInfo':
+			await completedSiteInfoJob(data.data as GetSiteInfoTaskParams)
+			break
+		}
+
+		console.log(`Job ${job.id} completed! Result: ${JSON.stringify(result)}`);
+		job.remove(); // 移除任务记录,防止下次添加同名jobId失败
+	})
+	// 监听失败事件
+	worker.on('failed', function (job, err) {
+		console.log(`Job ${job?.id} failed! ${err}`);
+		job?.remove(); // 移除任务记录,防止下次添加同名jobId失败
+	})
+
+	return queueTask
+})()
+
+// 添加任务
+async function addTaskQueue<T>(params: TaskParams<T>) {
+	const job = await queue.add('FwhServerTask', params);
+	return job
+}
+
+export async function addGetCloudLogTask(params: GetCloudLogTaskParams) {
+	const taskParams : TaskParams<GetCloudLogTaskParams> = {
+		taskName: 'GetCloudLog',
+		data: {
+			...params
+		}
+	}
+	const job = await addTaskQueue<GetCloudLogTaskParams>(taskParams)
+	return job
+}
+
+export async function addGetSiteInfoTask(params: GetSiteInfoTaskParams) {
+	const taskParams : TaskParams<GetSiteInfoTaskParams> = {
+		taskName: 'GetSiteInfo',
+		data: {
+			...params
+		}
+	}
+	const job = await addTaskQueue<GetSiteInfoTaskParams>(taskParams)
+	return job
+}
+
+export async function addGetPartSiteInfoTask(params: GetPartSiteInfoTaskParams) {
+	const taskParams : TaskParams<GetPartSiteInfoTaskParams> = {
+		taskName: 'GetPartSiteInfo',
+		data: {
+			...params
+		}
+	}
+	const job = await addTaskQueue<GetPartSiteInfoTaskParams>(taskParams)
+	return job
+}
+
+export async function addGetOfflineSiteInfoTask(params: GetSiteInfoTaskParams) {
+	const taskParams : TaskParams<GetSiteInfoTaskParams> = {
+		taskName: 'GetOfflineSiteInfo',
+		data: {
+			...params
+		}
+	}
+	const job = await addTaskQueue<GetSiteInfoTaskParams>(taskParams)
+	return job
+}

+ 20 - 0
src/queue/task.queue.ts

@@ -0,0 +1,20 @@
+import { QueueOptions } from 'bullmq'
+import { redisConfig } from '@/config/config.default'
+// import initCloudLogQueue from './cloudLogTask.queue'
+
+// 队列执行配置参数可以看 
+// https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md
+export const queueOptions: QueueOptions = {
+  connection: {
+    port: redisConfig.RDS_PORT,
+    host: redisConfig.RDS_HOST, // 连接IP
+    password: redisConfig.RDS_PWD, // 没有密码就填null
+    db: redisConfig.RDS_DB_QUEUE, // 范围0-15
+  },
+  defaultJobOptions: {
+    attempts: 1,
+    removeOnComplete: true,
+    backoff: 0,
+    delay: 1000
+  },
+}

+ 17 - 0
src/router/accountManage.route.ts

@@ -0,0 +1,17 @@
+import Router from 'koa-router'
+import accountManageController from '@/controller/accountManage.controller'
+import { URL } from '@/api/accountManage'
+
+const { getAccountInfo, saveAccountInfo } = accountManageController
+
+const addAccountManageRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  // 获取账户信息
+  router.get(URL.getAccountInfo, getAccountInfo)
+
+  // 保存账户信息
+  router.post(URL.postAccountInfo, saveAccountInfo)
+
+  return router
+}
+
+export default addAccountManageRoute

+ 17 - 0
src/router/auth.route.ts

@@ -0,0 +1,17 @@
+import Router from 'koa-router'
+import authController from '@/controller/auth.controller'
+import { URL } from '@/api/auth'
+
+const { getMenuAuth, getButtonAuth } = authController
+
+const addAuthRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  // 获取 menu list
+  router.get(URL.getMenuAuth, getMenuAuth)
+
+  // 获取 button auth
+  router.get(URL.getButtonAuth, getButtonAuth)
+
+  return router
+}
+
+export default addAuthRoute

+ 26 - 0
src/router/cloudLog.route.ts

@@ -0,0 +1,26 @@
+import Router from 'koa-router'
+import { verifySerialNumber, verifyCmdType, verifyTime } from '@/middleware/cloudLog.middleware'
+import cloudLogController from '@/controller/cloudLog.controller'
+import { URL } from '@/api/cloudLog'
+
+const addCloudLogRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  router.get(URL.getCloudLogList, cloudLogController.getList)
+
+  router.post(
+    URL.postCloudLog,
+    // verifySerialNumber, // 改为任务中验证
+    verifyCmdType,
+    verifyTime,
+    cloudLogController.create
+  )
+
+  router.get(URL.getLogType, cloudLogController.getCmdTypeList)
+
+  router.get(URL.getCloudLogData, cloudLogController.getCloudLogData)
+
+  router.get(URL.getCloudLogDataPage, cloudLogController.getCloudLogDataPage)
+
+  return router
+}
+
+export default addCloudLogRoute

+ 11 - 0
src/router/emsLog.route.ts

@@ -0,0 +1,11 @@
+import Router from 'koa-router'
+import emsLogController from '@/controller/emsLog.controller'
+import { URL } from '@/api/emsLog'
+
+const addEmsLogRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  router.get(URL.getEmsLogList, emsLogController.getList)
+
+  return router
+}
+
+export default addEmsLogRoute

+ 20 - 0
src/router/index.ts

@@ -0,0 +1,20 @@
+import Router from 'koa-router'
+import addAuthRoute from './auth.route'
+import addCloudLogRoute from './cloudLog.route'
+import addEmsLogRoute from './emsLog.route'
+import addSiteManageRoute from './siteManage.route'
+import addUserRoute from './user.route'
+import addAccountManageRoute from './accountManage.route'
+import addUserInfoRoute from './userInfo.route'
+
+const router = new Router({ prefix: '/bee' })
+
+addAuthRoute(router)
+addCloudLogRoute(router)
+addEmsLogRoute(router)
+addSiteManageRoute(router)
+addUserRoute(router)
+addAccountManageRoute(router)
+addUserInfoRoute(router)
+
+export default router

+ 94 - 0
src/router/siteManage.route.ts

@@ -0,0 +1,94 @@
+import Router from 'koa-router'
+import siteManageController from '@/controller/siteManage.controller'
+import { URL } from '@/api/siteManage'
+import { SiteManage } from '@/api/siteManage/interface'
+import { EventEmitter } from 'events'
+import { PassThrough } from 'stream'
+
+const emitter = new EventEmitter();  // 创建一个事件发射器
+
+const addSiteManageRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  router.get(
+    URL.getSiteManageInfo, 
+    siteManageController.getInfo
+  )
+
+  router.post(
+    URL.postRefreshSiteInfo,
+    siteManageController.refresh
+  )
+
+  router.get(
+    URL.getSiteOfflineInfo, 
+    siteManageController.getOfflineInfo
+  )
+
+  router.post(
+    URL.postRefreshOfflineSiteInfo,
+    siteManageController.refreshOffline
+  )
+
+  router.get(URL.eventsRefreshSiteInfo, async (ctx, next) => {
+    ctx.request.socket.setTimeout(0);
+    ctx.req.socket.setNoDelay(true);
+    ctx.req.socket.setKeepAlive(true);
+
+    // 设置响应头
+    ctx.set('Content-Type', 'text/event-stream');
+    ctx.set('Cache-Control', 'no-cache');
+    ctx.set('Connection', 'keep-alive');
+    ctx.set('Access-Control-Allow-Origin', '*');
+
+    const stream = new PassThrough()
+
+    ctx.status = 200;
+    ctx.body = stream;
+
+    console.log(`${URL.eventsRefreshSiteInfo} events connected`);
+    const sendEventHandle = (data : SiteManage.ResSiteManageInfo) => {
+      stream.write(`type: SiteManageInfo, data: ${JSON.stringify(data)}\n\n`)
+      console.log(`${URL.eventsRefreshSiteInfo} events send ${JSON.stringify(data)}`);
+    }
+    emitter.on('SiteInfo-event', sendEventHandle)
+
+    // 发送周期数据(心跳),避免断开连接
+    let heartbeat = 0
+    const intervalId = setInterval(() => {
+      stream.write(`type: connect, heartbeat: ${heartbeat}\n\n`)
+      heartbeat++
+    }, 30 * 1000) // 间隔30s
+  
+    // 客户端断开连接时清理定时器
+    ctx.req.on('close', () => {
+      emitter.off('SiteInfo-event', sendEventHandle)
+      clearInterval(intervalId); // 清理定时器
+      console.log(`${URL.eventsRefreshSiteInfo} events disconnected`);
+    });
+
+    await next()
+  });
+
+  router.get(
+    URL.getSiteInfo, 
+    siteManageController.getSiteInfo
+  )
+
+  router.post(
+    URL.postSiteTags,
+    siteManageController.postSiteTags
+  )
+
+  router.get(
+    URL.getSiteMap, 
+    siteManageController.getSiteMap
+  )
+
+  return router
+}
+
+export default addSiteManageRoute
+
+// 事件接口
+export function sendEvent(data : SiteManage.ResSiteManageInfo) {
+  emitter.emit('SiteInfo-event', data)
+}

+ 36 - 0
src/router/user.route.ts

@@ -0,0 +1,36 @@
+import Router from 'koa-router'
+import userController from '@/controller/user.controller'
+import userMiddleware from '@/middleware/user.middleware'
+import { jwtMiddleware } from '@/middleware/jwt.middleware'
+import { URL } from '@/api/user'
+
+const { register, login, logout } = userController
+const {
+  userValidator,
+  verifyUser,
+  cryptPassword,
+} = userMiddleware
+
+const addUserRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  // 获取秘钥对
+  router.post(URL.getKeyPair, )
+
+  // 注册接口
+  router.post(URL.postRegister, userValidator, verifyUser, cryptPassword, register)
+
+  // 登录接口
+  router.post(URL.postLogin, login)
+  router.post(URL.postLogout, logout)
+
+  // 示例:受保护的路由
+  router.get('/protected', jwtMiddleware, async (ctx) => {
+    ctx.body = {
+      message: `Hello, ${ctx.state.user.username}. This is a protected route.`,
+      data: { secretInfo: 'Sensitive data here' },
+    }
+  })
+
+  return router
+}
+
+export default addUserRoute

+ 17 - 0
src/router/userInfo.route.ts

@@ -0,0 +1,17 @@
+import Router from 'koa-router'
+import userInfoController from '@/controller/userInfo.controller'
+import { URL } from '@/api/userInfo'
+
+const { getUserInfo, postUserInfo } = userInfoController
+
+const addUserInfoRoute = (router : Router<any, {}>) : Router<any, {}> => {
+  // 获取用户信息
+  router.get(URL.getUserInfo, getUserInfo)
+
+  // 保存用户信息
+  router.post(URL.postUserInfo, postUserInfo)
+
+  return router
+}
+
+export default addUserInfoRoute

+ 101 - 0
src/service/accountManage.service.ts

@@ -0,0 +1,101 @@
+import User from '@/model/user.model'
+
+export interface FwhAccount {
+  server: string;
+  fwh_username: string;
+  fwh_password: string;
+}
+
+class AccountManageService {
+  // 使用本站登录用户名获取Fwh的登录信息
+  async getAccountInfo(
+    username: string, 
+  ) : Promise<FwhAccount | undefined> {
+    const whereOpt: any = {user_name: username} // 存储查询条件
+
+    let res = undefined
+    try {
+      // User.findOne 查询 用户名是否存在
+      res = await User.findOne({
+        attributes: [
+          'id',
+          'user_name',
+          'admin',
+          'enable',
+          'fwh_server',
+          'fwh_pro_username',
+          'fwh_pro_password',
+          'fwh_tst_username',
+          'fwh_tst_password'
+        ],
+        where: whereOpt,
+      })
+    } catch (err) {
+      console.log(`Account Manage find database failed.(${err})`)
+    }
+
+    if (!res) {
+      return undefined;
+    }
+
+    const data = res.dataValues
+    
+    if (data.fwh_server === 'production') {
+      return {
+        server: data.fwh_server,
+        fwh_username: data.fwh_pro_username,
+        fwh_password: data.fwh_pro_password
+      };
+    } else if (data.fwh_server === 'testing') {
+      return {
+        server: data.fwh_server,
+        fwh_username: data.fwh_tst_username,
+        fwh_password: data.fwh_tst_password
+      };
+    } else {
+      return undefined;
+    }
+  }
+
+  async saveAccountInfo(
+    id: number,
+    username: string,
+    server: string, 
+    fwh_username: string, 
+    fwh_password: string
+  ) : Promise<boolean> {
+    let fwhInfo: object = {}
+    if (server === 'production') {
+      fwhInfo = {
+        fwh_server: server,
+        fwh_pro_username: fwh_username,
+        fwh_pro_password: fwh_password
+      };
+    } else if (server === 'testing') {
+      fwhInfo = {
+        fwh_server: server,
+        fwh_tst_username: fwh_username,
+        fwh_tst_password: fwh_password
+      };
+    }
+
+    try {
+      const [affectedCount] = await User.update(
+        { ...fwhInfo },
+        { where: { id, user_name: username } }
+      )
+
+      if (affectedCount > 0) {
+        return true
+      } else {
+        console.log('Account Manage update failed because no record matching the conditions was found.')
+      }
+    } catch (err) {
+      console.log(`Account Manage update database failed.(${err})`)
+    }
+
+    return false
+  }
+}
+
+export default new AccountManageService()

+ 36 - 0
src/service/auth.service.ts

@@ -0,0 +1,36 @@
+import menu_about from '@/constant/auth/about/menu.json' assert { type: "json" }
+import menu_home from '@/constant/auth/home/menu.json' assert { type: "json" }
+import menu_cloudData from '@/constant/auth/cloudData/menu.json' assert { type: "json" }
+import menu_emsData from '@/constant/auth/emsData/menu.json' assert { type: "json" }
+import menu_operations from '@/constant/auth/operations/menu.json' assert { type: "json" }
+import menu_system from '@/constant/auth/system/menu.json' assert { type: "json" }
+import menu_toolbox from '@/constant/auth/toolbox/menu.json' assert { type: "json" }
+
+import button_cloudData from '@/constant/auth/cloudData/button_admin.json' assert { type: "json" }
+
+import { Menu } from '@/api/auth/interface'
+import { Login } from '@/api/user/interface'
+
+class AuthService {
+  // 获取站点列表
+  getMenuAuth(admin: boolean): Menu.MenuOptions[] {
+    let data : Array<Menu.MenuOptions>
+    if (admin) {
+      data = [menu_home, menu_cloudData, menu_emsData, menu_operations, menu_system, menu_toolbox, menu_about]
+    } else {
+      data = [menu_home, menu_cloudData, menu_emsData, menu_about]
+    }
+    return data
+  }
+
+  // 获取按钮权限
+  getButtonAuth(admin: boolean): Login.ResAuthButtons {
+    let data : Login.ResAuthButtons = {}
+    if (admin) {
+      data = {...button_cloudData}
+    }
+    return data
+  }
+}
+
+export default new AuthService()

+ 183 - 0
src/service/cloudLog.service.ts

@@ -0,0 +1,183 @@
+import { Op } from 'sequelize';
+import { addGetCloudLogTask } from '@/queue/fwhServerTask'
+import { querySiteListInSerialNumber } from '@/external/fwh/api/getSiteList'
+import { getCloudLogList, insertCloudLog } from '@/cache/cloudLogList.cache'
+import { SiteLogTable, connectSiteTable } from '@/model/cloudLog.model'
+import { ErrorCode } from '@/constant/err.type'
+
+class CloudLogService {
+  // 获取站点列表
+  async getSiteList(server: string, serialNumber: string | undefined = undefined, status: number | undefined = undefined, page: number = 1, pageSize: number = 10) {
+    // 标签转换
+    const list = await getCloudLogList(server, serialNumber, status, page, pageSize)
+    return list.map(obj => {
+      return {
+        id: obj.id,
+        serialNumber: obj.serial_number,
+        count: obj.count,
+        updateTime: obj.update_time,
+        status: obj.status
+      }
+    })
+  }
+
+  // 创建云日志
+  async createCloudLog(
+    state: any,
+    userName: string,
+    server: string,
+    serialNumberList: string[], 
+    cmdTypes: string[], 
+    timeBegin: Date, 
+    timeEnd: Date
+  ) {
+    // 处理批量创建
+    // for (const serialNumber of serialNumberList) {
+    //   // 提取timeZone数据
+    //   const timeZone: number = state.fwhSiteInfo[serialNumber].timeZone ? state.fwhSiteInfo[serialNumber].timeZone : 0
+
+    //   // 添加日志捞取任务
+    //   await addGetCloudLogTask({
+    //     userName, 
+    //     server,
+    //     serialNumber, 
+    //     cmdTypes, 
+    //     timeBegin, 
+    //     timeEnd, 
+    //     timeZone
+    //   })
+
+    //   // 将创建数据更新到数据库
+    //   await insertCloudLog(server, serialNumber)
+    // }
+
+    // 添加日志捞取任务
+    await addGetCloudLogTask({
+      userName, 
+      server,
+      siteInfoList: serialNumberList.map(item => {
+        return { serialNumber: item }
+      }), 
+      cmdTypes, 
+      timeBegin, 
+      timeEnd
+    })
+  }
+
+  // 验证是否存在这个设备
+  async existSite(state: any, username: string, serialNumberList: string[]): Promise<boolean | ErrorCode> {
+    // 如果批量导入时,在创建时验证
+    if (serialNumberList.length > 1) {
+      return true
+    }
+    
+    for (const serialNumber of serialNumberList) {
+      let oneResult : boolean = false
+      const res = await querySiteListInSerialNumber(username, serialNumber)
+      console.log("querySiteListInSerialNumber: ", res)
+      if (typeof res !== 'object') {
+        return res
+      } else {
+        for(const result of res.result) {
+          if (result.id === serialNumber) {
+            // 保存用户信息,后续使用
+            if (!state.fwhSiteInfo) state.fwhSiteInfo = {}
+            Object.assign(state.fwhSiteInfo, {
+              [serialNumber]: {...result}
+            })
+            oneResult = true
+            break
+          }
+        }
+
+        if (!oneResult) return false
+      }
+    }
+    return true
+  }
+
+  // 获取日志数据
+  async getCloudLogData(server: string, serialNumber: string, logType: string[], startTime: Date, endTime: Date) {
+    const table = await connectSiteTable(server, serialNumber)
+    let data : SiteLogTable[] = []
+    console.log(logType.map(item => { return Number(item) }))
+    const findData = await table.findAll({
+      where: {
+        time: {
+          [Op.gte]: startTime,
+          [Op.lt]: endTime,
+        },
+        cmd_type: {
+          [Op.in]: logType.map(item => { return Number(item) }),
+        }
+      },
+      attributes: ['cmd_type', 'time', 'data'],
+      order: [['time', 'ASC']]
+    })
+      .catch(err => {
+        console.error(`数据库查询失败 - ${table.name} : , ${err}`)
+        return [] as SiteLogTable[]
+      })
+    data.push(...findData)
+
+    if (data) {
+      return data.map(item => {
+        return {
+          type: item.cmd_type,
+          time: item.time,
+          data: item.data
+        }
+      })
+    }
+
+    return undefined
+  }
+
+  async getCloudLogDataPage(server: string, serialNumber: string, logType?: string[], startTime?: Date, endTime?: Date, page: number = 1, pageSize: number = 10) {
+    const start = (page - 1) * pageSize
+    const limit = pageSize
+    let offset = start
+    const where = {
+      serial_number: { [Op.eq]: `${serialNumber}` },
+      ...logType === undefined ? {} : { cmd_type: { [Op.or]: logType }},
+      ...((startTime === undefined) || (endTime === undefined)) ? {} : { time: { [Op.gte]: startTime, [Op.lt]: endTime }},
+    }
+    const table = await connectSiteTable(server, serialNumber)
+    const data = await table.findAll({
+      where,
+      limit,
+      offset,
+      attributes: ['cmd_type', 'cmd_name', 'time', 'data'],
+      order: [['time', 'DESC']]
+    })
+
+    if (data) {
+      return data.map(item => {
+        return {
+          type: item.cmd_type,
+          typeName: item.cmd_name,
+          time: new Date(item.time.toLocaleString()),
+          data: item.data
+        }
+      })
+    }
+
+    return undefined
+  }
+
+  async getCloudLogDataPageTotal(server: string, serialNumber: string, logType?: string[], startTime?: Date, endTime?: Date) {
+    const where = {
+      serial_number: { [Op.eq]: `${serialNumber}` },
+      ...logType === undefined ? {} : { cmd_type: { [Op.or]: logType }},
+      ...((startTime === undefined) || (endTime === undefined)) ? {} : { time: { [Op.gte]: startTime, [Op.lt]: endTime }},
+    }
+    const table = await connectSiteTable(server, serialNumber)
+    const total = await table.count({
+      where
+    })
+
+    return total
+  }
+}
+
+export default new CloudLogService()

+ 242 - 0
src/service/siteManage.service.ts

@@ -0,0 +1,242 @@
+
+import { addGetSiteInfoTask, addGetPartSiteInfoTask, addGetOfflineSiteInfoTask } from '@/queue/fwhServerTask'
+import { querySiteManageInfo, updateSiteManageInfo, querySiteOfflineInfo, updateSiteOfflineInfo, SiteInfoTable, connectSiteInfoList } from '@/model/siteManage.model'
+import { ErrorCode } from '@/constant/err.type'
+import { SiteManage } from '@/api/siteManage/interface'
+import { Op, where } from 'sequelize'
+
+const StateNames: string[] = [
+  "Alabama",
+  "Alaska",
+  "Arizona",
+  "Arkansas",
+  "California",
+  "Colorado",
+  "Connecticut",
+  "Delaware",
+  "District of Columbia",
+  "Florida",
+  "Georgia",
+  "Hawaii",
+  "Idaho",
+  "Illinois",
+  "Indiana",
+  "Iowa",
+  "Kansas",
+  "Kentucky",
+  "Louisiana",
+  "Maine",
+  "Maryland",
+  "Massachusetts",
+  "Michigan",
+  "Minnesota",
+  "Mississippi",
+  "Missouri",
+  "Montana",
+  "Nebraska",
+  "Nevada",
+  "New Hampshire",
+  "New Jersey",
+  "New Mexico",
+  "New York",
+  "North Carolina",
+  "North Dakota",
+  "Ohio",
+  "Oklahoma",
+  "Oregon",
+  "Pennsylvania",
+  "Rhode Island",
+  "South Carolina",
+  "South Dakota",
+  "Tennessee",
+  "Texas",
+  "Utah",
+  "Vermont",
+  "Virginia",
+  "Washington",
+  "West Virginia",
+  "Wisconsin",
+  "Wyoming",
+  "Puerto Rico"
+]
+
+class SiteManageService {
+  // 获取站点管理状态
+  async getInfo(server: string): Promise<SiteManage.ResSiteManageInfo | ErrorCode> {
+    // 从数据库获取站点管理信息
+    const info = await querySiteManageInfo(server)
+    if (!info) return ErrorCode.DatabaseReadFailed
+    return {
+      updateTime: info.updateTime,
+      siteCount: info.siteCount,
+      updateStatus: info.updateStatus
+    }
+  }
+
+  // 刷新站点信息
+  async refresh(userName: string, server: string) : Promise<ErrorCode | undefined> {
+    // 写入刷新时间和状态
+    const res = await updateSiteManageInfo({
+      server,
+      updateTime: new Date(),
+      updateStatus: 'Refreshing'
+    })
+    if (!res) return ErrorCode.DatabaseReadFailed
+
+    const info = await querySiteManageInfo(server)
+    if (!info) return ErrorCode.DatabaseReadFailed
+    // 判断是否存在站点信息
+    if (info.siteCount > 0) {
+      // 站点数据已存在,捞取这段时间绑定解绑日志
+      // 添加部分站点信息刷新任务
+      await addGetPartSiteInfoTask({
+        userName, 
+        server,
+        startTime: info.updateTime
+      })
+    } else {
+      // 站点数据为空
+      // 添加所有站点信息刷新任务
+      await addGetSiteInfoTask({
+        userName, 
+        server
+      })
+    }
+  }
+
+  // 获取离线站点状态
+  async getOfflineInfo(server: string): Promise<SiteManage.ResSiteManageInfo | ErrorCode> {
+    // 从数据库获取站点管理信息
+    const info = await querySiteOfflineInfo(server)
+    if (!info) return ErrorCode.DatabaseReadFailed
+    return {
+      updateTime: info.updateTime,
+      siteCount: info.siteCount,
+      updateStatus: info.updateStatus
+    }
+  }
+
+  // 刷新离线站点信息
+  async refreshOffline(userName: string, server: string) : Promise<ErrorCode | undefined> {
+    // 写入刷新时间和状态
+    const res = await updateSiteOfflineInfo({
+      server,
+      updateTime: new Date(),
+      updateStatus: 'Refreshing'
+    })
+    if (!res) return ErrorCode.DatabaseReadFailed
+
+    // 添加所有站点信息刷新任务
+    await addGetOfflineSiteInfoTask({
+      userName, 
+      server
+    })
+  }
+
+  // 获取站点信息
+  async getSiteInfo(server: string, params: SiteManage.ReqSiteInfo) {
+    // 直接从数据库获取
+    const limit = params.pageSize
+    let offset = (params.pageNum - 1) * params.pageSize
+    console.log("params : ", params)
+    const where = {
+      ...params.serialNumber === undefined ? {} : { serialNumber: { [Op.iLike]: `%${params.serialNumber}%` }},
+      ...params.apowerSn === undefined ? {} : { apowerSn: { [Op.contains]: [`${params.apowerSn}`] }},
+      ...params.apowerNum === undefined ? {} : { apowerNum: { [Op.eq]: `${params.apowerNum}` }},
+      ...params.activeStatus === undefined ? {} : { activeStatus: { [Op.eq]: `${params.activeStatus}` }},
+      ...params.networkStatus === undefined ? {} : { networkStatus: { [Op.eq]: `${params.networkStatus}` }},
+      ...params.country === undefined ? {} : { country: { [Op.eq]: `${params.country}` }},
+      ...params.province === undefined ? {} : { province: { [Op.eq]: `${params.province}` }},
+      ...params.tags === undefined ? {} : { tags: { [Op.contains]: [ ...params.tags ] } },
+    }
+    console.log("where : ", where)
+    const table = await connectSiteInfoList(server)
+    let data : SiteInfoTable[] = []
+    const findData = await table.findAll({
+      where,
+      limit,
+      offset,
+      order: [['id', 'DESC']]
+    })
+      .then(value => {
+        return value
+      })
+      .catch(err => {
+        console.error(`数据库查询失败 - ${table.name} : , ${err}`)
+        return [] as SiteInfoTable[]
+      })
+    data.push(...findData)
+    console.log("data : ", data)
+
+    return data
+  }
+
+  // 获取站点总页数
+  async getSiteInfoTotal(server: string, params: SiteManage.ReqSiteInfo) {
+    const where = {
+      ...params.serialNumber === undefined ? {} : { serialNumber: { [Op.iLike]: `%${params.serialNumber}%` }},
+      ...params.apowerSn === undefined ? {} : { apowerSn: { [Op.contains]: [`${params.apowerSn}`] }},
+      ...params.apowerNum === undefined ? {} : { apowerNum: { [Op.eq]: `${params.apowerNum}` }},
+      ...params.activeStatus === undefined ? {} : { activeStatus: { [Op.eq]: `${params.activeStatus}` }},
+      ...params.networkStatus === undefined ? {} : { networkStatus: { [Op.eq]: `${params.networkStatus}` }},
+      ...params.country === undefined ? {} : { country: { [Op.eq]: `${params.country}` }},
+      ...params.province === undefined ? {} : { province: { [Op.eq]: `${params.province}` }},
+      ...params.tags === undefined ? {} : { tags: { [Op.contains]: [ ...params.tags ] } },
+    }
+    const table = await connectSiteInfoList(server)
+    const total = await table.count({
+      where
+    })
+
+    return total
+  }
+
+  // 设置站点标签
+  async setSiteTags(server: string, serialNumber: string, tags: string[]) {
+    const table = await connectSiteInfoList(server)
+    await table.update(
+      { tags },
+      { where: { serialNumber } }
+    ).catch(err => {
+      console.error(`数据库更新tags失败 - ${table.name} : , ${err}`)
+      return false
+    })
+    return true
+  }
+
+  // 获取站点地图
+  async getSiteMap(server: string): Promise<SiteManage.ResSiteMapData[]> {
+    const table = await connectSiteInfoList(server)
+    const getWhere = (online: number, state: string) => {
+      const where = {
+        activeStatus: { [Op.eq]: 1 },
+        networkStatus: { [Op.eq]: online },
+        province: { [Op.eq]: `${state}` }
+      }
+      return where
+    }
+
+    let siteMapList: SiteManage.ResSiteMapData[] = []
+    for (const name of StateNames) {
+      const offlineWhere = getWhere(0, name)
+      const offlineCount = await table.count({
+        where: {...offlineWhere}
+      })
+
+      const onlineWhere = getWhere(1, name)
+      const onlineCount = await table.count({
+        where: {...onlineWhere}
+      })
+
+      siteMapList.push({
+        state: name,
+        online: onlineCount,
+        offline: offlineCount
+      })
+    }
+    
+    return siteMapList
+  }
+}
+
+export default new SiteManageService()

Некоторые файлы не были показаны из-за большого количества измененных файлов