commit c0fe67ff19c0f17bac8600103ecbd62d3d9bf864 Author: louis <869322496@qq.com> Date: Wed Feb 28 08:32:35 2024 +0800 refactor: 华信OA后端架构搭建 diff --git a/.cz-config.js b/.cz-config.js new file mode 100644 index 0000000..57d4806 --- /dev/null +++ b/.cz-config.js @@ -0,0 +1,94 @@ +// 请使用npm run c提交代码。遵循代码提交规范 +module.exports = { + types: [ + { value: 'feat', name: '功能: ✨ 新增功能', emoji: ':sparkles:' }, + { value: 'fix', name: '修复: 🐛 修复缺陷', emoji: ':bug:' }, + { value: 'docs', name: '文档: 📝 文档变更', emoji: ':memo:' }, + { + value: 'style', + name: '格式: 🌈 代码格式(不影响功能,例如空格、分号等格式修正)', + emoji: ':lipstick:', + }, + { + value: 'refactor', + name: '重构: 🔄 代码重构(不包括 bug 修复、功能新增)', + emoji: ':recycle:', + }, + { value: 'perf', name: '性能: 🚀 性能优化', emoji: ':zap:' }, + { + value: 'test', + name: '测试: 🧪 添加疏漏测试或已有测试改动', + emoji: ':white_check_mark:', + }, + { + value: 'build', + name: '构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)', + emoji: ':package:', + }, + { + value: 'ci', + name: '集成: ⚙️ 修改 CI 配置、脚本', + emoji: ':ferris_wheel:', + }, + { value: 'revert', name: '回退: ↩️ 回滚 commit', emoji: ':rewind:' }, + { + value: 'chore', + name: '其他: 🛠️ 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)', + emoji: ':hammer:', + }, + ], + useEmoji: true, + emojiAlign: 'center', + useAI: false, + aiNumber: 1, + themeColorCode: '', + scopes: [], + allowCustomScopes: true, + allowEmptyScopes: true, + customScopesAlign: 'bottom', + customScopesAlias: 'custom', + emptyScopesAlias: 'empty', + upperCaseSubject: false, + markBreakingChangeMode: false, + breaklineNumber: 100, + breaklineChar: '|', + issuePrefixes: [ + { value: 'closed', name: 'closed: ISSUES has been processed' }, + ], + customIssuePrefixAlign: 'top', + emptyIssuePrefixAlias: 'skip', + customIssuePrefixAlias: 'custom', + allowCustomIssuePrefix: true, + allowEmptyIssuePrefix: true, + confirmColorize: true, + maxHeaderLength: Infinity, + maxSubjectLength: Infinity, + minSubjectLength: 0, + scopeOverrides: undefined, + defaultBody: '', + defaultIssues: '', + defaultScope: '', + defaultSubject: '', + messages: { + type: '选择一种你期望的提交类型(type):', + // scope: '选择一个更改的范围(scope) (可选):', + // used if allowCustomScopes is true + // customScope: 'Denote the SCOPE of this change:', + subject: '输入本次commit记录说明:\n', + // body: '长说明,使用"|"换行(可选):\n', + // breaking: '非兼容性说明 (可选):\n', + // footer: '关联关闭的issue,例如:#31, #34(可选):\n', + confirmCommit: '确定提交说明?', + }, + skipQuestions: ['scope', 'body', 'breaking', 'footer'], + allowBreakingChanges: [ + 'fix', + 'feat', + 'update', + 'refactor', + 'perf', + 'build', + 'revert', + ], + subjectLimit: 500, // 提交长度限制500 +}; diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..84cee7b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# compiled output +/dist +/node_modules +# package-lock.json +# yarn.lock + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# Code +src/config/config.development.* +docs/* +# sql/* +test/* +README.md + +# Dev data +/__data/ \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..6f19ae2 --- /dev/null +++ b/.env @@ -0,0 +1,32 @@ +# app +APP_NAME = Nest Admin +APP_PORT = 7001 +APP_BASE_URL = http://localhost:${APP_PORT} +APP_LOCALE = zh-CN + +# logger +LOGGER_LEVEL = verbose +LOGGER_MAX_FILES = 31 + +TZ = Asia/Shanghai + +# OSS(qiniu) +OSS_ACCESSKEY=xxx +OSS_SECRETKEY=xxx +OSS_DOMAIN=https://cdn.buqiyuan.site +OSS_BUCKET=nest-admin +OSS_ZONE=Zone_z2 # Zone_as0 | Zone_na0 | Zone_z0 | Zone_z1 | Zone_z2 +OSS_ACCESS_TYPE=public # or private + +DB_HOST = host.docker.internal +DB_PORT = 13307 +DB_DATABASE = nest_admin +DB_USERNAME = root +DB_PASSWORD = root +DB_SYNCHRONIZE = false +DB_LOGGING = ["error"] + +REDIS_PORT = 6379 +REDIS_HOST = host.docker.internal +REDIS_PASSWORD = 123456 +REDIS_DB = 0 \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..6662f21 --- /dev/null +++ b/.env.development @@ -0,0 +1,35 @@ +# logger +LOGGER_LEVEL = debug + +# security +JWT_SECRET = admin!@#123 +JWT_EXPIRE = 86400 # 单位秒 +REFRESH_TOKEN_SECRET = admin!@#123 +REFRESH_TOKEN_EXPIRE = 2592000 + +# swagger +SWAGGER_ENABLE = true +SWAGGER_PATH = api-docs +SWAGGER_VERSION = 1.0 + +# db +DB_HOST = 127.0.0.1 +DB_PORT = 13307 +DB_DATABASE = nest_admin +DB_USERNAME = root +DB_PASSWORD = root +DB_SYNCHRONIZE = true +DB_LOGGING = ["error"] + +# redis +REDIS_PORT = 6379 +REDIS_HOST = 127.0.0.1 +REDIS_PASSWORD = 123456 +REDIS_DB = 0 + +# smtp +SMTP_HOST = smtp.163.com +SMTP_PORT = 465 +SMTP_USER = nest_admin@163.com +SMTP_PASS = VIPLLOIPMETTROYU + \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..c6a2b1d --- /dev/null +++ b/.env.production @@ -0,0 +1,37 @@ +# logger +LOGGER_LEVEL = debug + +# security +JWT_SECRET = admin!@#123 +JWT_EXPIRE = 86400 # 单位秒 +REFRESH_TOKEN_SECRET = admin!@#123 +REFRESH_TOKEN_EXPIRE = 2592000 + +# swagger +SWAGGER_ENABLE = true +SWAGGER_PATH = api-docs +SWAGGER_VERSION = 1.0 + +# db +DB_HOST = host.docker.internal +DB_PORT = 13307 +DB_DATABASE = nest_admin +DB_USERNAME = root +DB_PASSWORD = root +DB_SYNCHRONIZE = false +DB_LOGGING = ["error"] + +# redis +REDIS_PORT = 6379 +REDIS_HOST = host.docker.internal +REDIS_PASSWORD = 123456 +REDIS_DB = 0 + +# smtp +SMTP_HOST = smtp.163.com +SMTP_PORT = 465 +SMTP_USER = nest_admin@163.com +SMTP_PASS = VIPLLOIPMETTROYU + +# 是否为演示模式(在演示模式下,会拦截除 GET 方法以外的所有请求) +IS_DEMO = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d4e5bd3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings + +# Automatically normalize line endings (to LF) for all text-based files. +* text=auto eol=lf + +# Declare files that will always have CRLF line endings on checkout. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fbd170 --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +node_modules +.DS_Store +dist +*-dist +.cache +.history +.vercel/ + +.turbo +.local + +# local env files +#.env.development +#.env.production +.env.local + +.eslintcache + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# Editor directories and files +.idea +# .vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +.nestjs_repl_history +out + +# temp data +__data + +public/upload +types/env.d.ts \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..35ed753 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm run commitlint diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..3bb6316 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +#推送之前运行eslint检查 +npx lint-staged +##推送之前运行单元测试检查 +#npm run test:unit diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..eb950ed --- /dev/null +++ b/.npmrc @@ -0,0 +1,6 @@ +shamefully-hoist=true +strict-peer-dependencies=false + +# 使用淘宝镜像源 +registry = https://registry.npmmirror.com +# registry = https://registry.npmjs.org \ No newline at end of file diff --git a/.versionrc.js b/.versionrc.js new file mode 100644 index 0000000..e4ad6c6 --- /dev/null +++ b/.versionrc.js @@ -0,0 +1,19 @@ +//发布应用 生成commit日志记录。 + +module.exports = { + types: [ + { type: 'feat', section: '✨ Features | 新功能' }, + { type: 'fix', section: '🐛 Bug Fixes | Bug 修复' }, + { type: 'init', section: '📦️ Init | 初始化' }, + { type: 'docs', section: '📝 Documentation | 文档' }, + { type: 'style', section: '🌈 Styles | 风格' }, + { type: 'refactor', section: '🔄 Code Refactoring | 代码重构' }, + { type: 'perf', section: '🚀 Performance Improvements | 性能优化' }, + { type: 'test', section: '🧪 Tests | 测试' }, + { type: 'revert', section: '↩️ Revert | 回退' }, + { type: 'build', section: '📦️ Build System | 打包构建' }, + { type: 'update', section: '⚙️ update | 构建/工程依赖/工具升级' }, + { type: 'tool', section: '🛠️ tool | 工具升级' }, + { type: 'ci', section: '⚙️ Continuous Integration | CI 配置' }, + ], +}; diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d879beb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug Nest Framework", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"], + "autoAttachChildProcesses": true, + "restart": true, + "sourceMaps": true, + "stopOnEntry": false, + "console": "integratedTerminal" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..56ee0fc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,41 @@ +{ + // Enable the ESlint flat config support + "eslint.experimental.useFlatConfig": true, + + // Disable the default formatter, use eslint instead + "prettier.enable": false, + "editor.formatOnSave": false, + + // Auto fix + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + + // Silent the stylistic rules in you IDE, but still auto fix them + "eslint.rules.customizations": [ + { "rule": "style/*", "severity": "off" }, + { "rule": "*-indent", "severity": "off" }, + { "rule": "*-spacing", "severity": "off" }, + { "rule": "*-spaces", "severity": "off" }, + { "rule": "*-order", "severity": "off" }, + { "rule": "*-dangle", "severity": "off" }, + { "rule": "*-newline", "severity": "off" }, + { "rule": "*quotes", "severity": "off" }, + { "rule": "*semi", "severity": "off" } + ], + + // Enable eslint for all supported languages + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "vue", + "html", + "markdown", + "json", + "jsonc", + "yaml" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..31e8e78 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +# 遇到网络问题可以配置镜像加速:https://gist.github.com/y0ngb1n/7e8f16af3242c7815e7ca2f0833d3ea6 +# FROM 表示设置要制作的镜像基于哪个镜像,FROM指令必须是整个Dockerfile的第一个指令,如果指定的镜像不存在默认会自动从Docker Hub上下载。 +# 指定我们的基础镜像是node,latest表示版本是最新, 如果要求空间极致,可以选择lts-alpine +# 使用 as 来为某一阶段命名 +FROM node:20-slim AS base + +ENV PROJECT_DIR=/nest-admin \ + DB_HOST=mysql \ + APP_PORT=7001 \ + PNPM_HOME="/pnpm" \ + PATH="$PNPM_HOME:$PATH" + + +RUN corepack enable \ + && yarn global add pm2 + +# WORKDIR指令用于设置Dockerfile中的RUN、CMD和ENTRYPOINT指令执行命令的工作目录(默认为/目录),该指令在Dockerfile文件中可以出现多次, +# 如果使用相对路径则为相对于WORKDIR上一次的值, +# 例如WORKDIR /data,WORKDIR logs,RUN pwd最终输出的当前目录是/data/logs。 +# cd 到 /nest-admin +WORKDIR $PROJECT_DIR +COPY ./ $PROJECT_DIR +RUN chmod +x ./wait-for-it.sh + +# set timezone +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && echo 'Asia/Shanghai' > /etc/timezone + +# see https://pnpm.io/docker +FROM base AS prod-deps +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile + +FROM base AS build +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile +RUN pnpm run build + + +# mirror acceleration +# RUN npm config set registry https://registry.npmmirror.com +# RUN pnpm config set registry https://registry.npmmirror.com +# RUN npm config rm proxy && npm config rm https-proxy + +FROM base +COPY --from=prod-deps $PROJECT_DIR/node_modules $PROJECT_DIR/node_modules +COPY --from=build $PROJECT_DIR/dist $PROJECT_DIR/dist + +# EXPOSE port +EXPOSE $APP_PORT + +# 容器启动时执行的命令,类似npm run start +# CMD ["pnpm", "start:prod"] +# CMD ["pm2-runtime", "ecosystem.config.js"] +ENTRYPOINT ./wait-for-it.sh $DB_HOST:$DB_PORT -- pm2-runtime ecosystem.config.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5c341e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-present buqiyuan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce302ae --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ + +## 环境要求 + +- `nodejs` `16.20.2`+ +- `docker` `20.x`+ ,其中 `docker compose`版本需要 `2.17.0`+ +- `mysql` `8.x`+ +- 使用 [`pnpm`](https://pnpm.io/zh/) 包管理器安装项目依赖 + +演示环境账号密码: + +| 账号 | 密码 | 权限 | +| :-------: | :----: | :--------: | +| admin | a123456 | 超级管理员 | + +> 所有新建的用户初始密码都为 a123456 + +本地部署账号密码: + +| 账号 | 密码 | 权限 | +| :-------: | :----: | :--------: | +| admin | a123456 | 超级管理员 | + + + +## 本地开发 + +- 【可选】如果你是新手,还不太会搭建`mysql/redis`,你可以使用 `Docker` 启动指定服务供本地开发时使用, 例如: + +```bash +# 启动MySql服务 +docker compose --env-file .env --env-file .env.development run -d --service-ports mysql +# 启动Redis服务 +docker compose --env-file .env --env-file .env.development run -d --service-ports redis +``` + +- 安装依赖 + +```bash + +pnpm install + +``` + +- 运行 + 启动成功后,通过 访问。 + +```bash +pnpm dev +``` + +- 打包 + +```bash +pnpm build +``` + +2.使用docker运行 + +```bash +docker compose up -d +``` + +停止并删除所有容器 + +```bash +pnpm docker:down +# or +docker compose --env-file .env --env-file .env.production down +``` + +删除镜像 + +```bash +pnpm docker:rmi +# or +docker rmi buqiyuan/nest-admin-server:stable +``` + +查看实时日志输出 + +```bash +pnpm docker:logs +# or +docker compose --env-file .env --env-file .env.production logs -f + +``` + +## 数据库迁移 + +1. 更新数据库(或初始化数据) + +```bash +pnpm migration:run +``` + +2. 生成迁移 + +```bash +pnpm migration:generate +``` + +3. 回滚到最后一次更新 + +```bash +pnpm migration:revert +``` + +更多细节,请移步至[官方文档](https://typeorm.io/migrations) + +> [!TIP] +> 如果你的`实体类`或`数据库配置`有更新,请执行`npm run build`后再进行数据库迁移相关操作。 + diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 0000000..7576451 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,24 @@ +// 请使用npm run c/yarn c提交代码。 +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // 新增功能 + 'fix', // 修复缺陷 + 'docs', // 文档变更 + 'style', // 代码格式(不影响功能,例如空格、分号等格式修正) + 'refactor', // 代码重构(不包括 bug 修复、功能新增) + 'perf', // 性能优化 + 'test', // 添加疏漏测试或已有测试改动 + 'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等) + 'ci', // 修改 CI 配置、脚本 + 'revert', // 回滚 commit + 'chore', // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例) + ], + ], + 'subject-case': [0], // subject大小写不做校验 + }, +}; diff --git a/deploy/sql/nest_admin.sql b/deploy/sql/nest_admin.sql new file mode 100644 index 0000000..fc3386e --- /dev/null +++ b/deploy/sql/nest_admin.sql @@ -0,0 +1,744 @@ +/* + Navicat Premium Data Transfer + + Source Server : nest-admin + Source Server Type : MySQL + Source Server Version : 80030 (8.0.30) + Source Host : localhost:13307 + Source Schema : nest_admin + + Target Server Type : MySQL + Target Server Version : 80030 (8.0.30) + File Encoding : 65001 + + Date: 10/02/2024 09:43:40 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_captcha_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_captcha_log`; +CREATE TABLE `sys_captcha_log` ( + `id` int NOT NULL AUTO_INCREMENT, + `user_id` int DEFAULT NULL, + `account` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `code` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `provider` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_captcha_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `id` int NOT NULL AUTO_INCREMENT, + `key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `IDX_2c363c25cf99bcaab3a7f389ba` (`key`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +BEGIN; +INSERT INTO `sys_config` (`id`, `key`, `name`, `value`, `remark`, `created_at`, `updated_at`) VALUES (1, 'sys_user_initPassword', '初始密码', '123456', '创建管理员账号的初始密码', '2023-11-10 00:31:44.154921', '2023-11-10 00:31:44.161263'); +INSERT INTO `sys_config` (`id`, `key`, `name`, `value`, `remark`, `created_at`, `updated_at`) VALUES (2, 'sys_api_token', 'API Token', 'nest-admin', '用于请求 @ApiToken 的控制器', '2023-11-10 00:31:44.154921', '2024-01-29 09:52:27.000000'); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `orderNo` int DEFAULT '0', + `mpath` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '', + `parentId` int DEFAULT NULL, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (`id`) USING BTREE, + KEY `FK_c75280b01c49779f2323536db67` (`parentId`) USING BTREE, + CONSTRAINT `FK_c75280b01c49779f2323536db67` FOREIGN KEY (`parentId`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (1, '华东分部', 1, '1.', NULL, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (2, '研发部', 1, '1.2.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (3, '市场部', 2, '1.3.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (4, '商务部', 3, '1.4.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (5, '财务部', 4, '1.5.', 1, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (6, '华南分部', 2, '6.', NULL, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (7, '西北分部', 3, '7.', NULL, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (8, '研发部', 1, '6.8.', 6, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +INSERT INTO `sys_dept` (`id`, `name`, `orderNo`, `mpath`, `parentId`, `created_at`, `updated_at`) VALUES (9, '市场部', 1, '6.9.', 6, '2023-11-10 00:31:43.996025', '2023-11-10 00:31:44.008709'); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dict +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict`; +CREATE TABLE `sys_dict` ( + `id` int NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `create_by` int NOT NULL COMMENT '创建者', + `update_by` int NOT NULL COMMENT '更新者', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `status` tinyint NOT NULL DEFAULT '1', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_d112365748f740ee260b65ce91` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Records of sys_dict +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dict_item +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_item`; +CREATE TABLE `sys_dict_item` ( + `id` int NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `create_by` int NOT NULL COMMENT '创建者', + `update_by` int NOT NULL COMMENT '更新者', + `label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `order` int DEFAULT NULL COMMENT '字典项排序', + `status` tinyint NOT NULL DEFAULT '1', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `type_id` int DEFAULT NULL, + `orderNo` int DEFAULT NULL COMMENT '字典项排序', + PRIMARY KEY (`id`), + KEY `FK_d68ea74fcb041c8cfd1fd659844` (`type_id`), + CONSTRAINT `FK_d68ea74fcb041c8cfd1fd659844` FOREIGN KEY (`type_id`) REFERENCES `sys_dict_type` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Records of sys_dict_item +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (1, '2024-01-29 01:24:51.846135', '2024-01-29 02:23:19.000000', 1, 1, '男', '1', 0, 1, '性别男', 1, 3); +INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (2, '2024-01-29 01:32:58.458741', '2024-01-29 01:58:20.000000', 1, 1, '女', '0', 1, 1, '性别女', 1, 2); +INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (3, '2024-01-29 01:59:17.805394', '2024-01-29 14:37:18.000000', 1, 1, '人妖王', '3', NULL, 1, '安布里奥·伊万科夫', 1, 0); +INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (5, '2024-01-29 02:13:01.782466', '2024-01-29 02:13:01.782466', 1, 1, '显示', '1', NULL, 1, '显示菜单', 2, 0); +INSERT INTO `sys_dict_item` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `label`, `value`, `order`, `status`, `remark`, `type_id`, `orderNo`) VALUES (6, '2024-01-29 02:13:31.134721', '2024-01-29 02:13:31.134721', 1, 1, '隐藏', '0', NULL, 1, '隐藏菜单', 2, 0); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `id` int NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `create_by` int NOT NULL COMMENT '创建者', + `update_by` int NOT NULL COMMENT '更新者', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `status` tinyint NOT NULL DEFAULT '1', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `IDX_74d0045ff7fab9f67adc0b1bda` (`code`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +BEGIN; +INSERT INTO `sys_dict_type` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `name`, `status`, `remark`, `code`) VALUES (1, '2024-01-28 08:19:12.777447', '2024-02-08 13:05:10.000000', 1, 1, '性别', 1, '性别单选', 'sys_user_gender'); +INSERT INTO `sys_dict_type` (`id`, `created_at`, `updated_at`, `create_by`, `update_by`, `name`, `status`, `remark`, `code`) VALUES (2, '2024-01-28 08:38:41.235185', '2024-01-29 02:11:33.000000', 1, 1, '菜单显示状态', 1, '菜单显示状态', 'sys_show_hide'); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_login_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_login_log`; +CREATE TABLE `sys_login_log` ( + `id` int NOT NULL AUTO_INCREMENT, + `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `ua` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `provider` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `user_id` int DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + KEY `FK_3029712e0df6a28edaee46fd470` (`user_id`), + CONSTRAINT `FK_3029712e0df6a28edaee46fd470` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_login_log +-- ---------------------------- +BEGIN; +COMMIT; + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `id` int NOT NULL AUTO_INCREMENT, + `parent_id` int DEFAULT NULL, + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `type` tinyint NOT NULL DEFAULT '0', + `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '', + `order_no` int DEFAULT '0', + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `keep_alive` tinyint NOT NULL DEFAULT '1', + `show` tinyint NOT NULL DEFAULT '1', + `status` tinyint NOT NULL DEFAULT '1', + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `is_ext` tinyint NOT NULL DEFAULT '0', + `ext_open_mode` tinyint NOT NULL DEFAULT '1', + `active_menu` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +BEGIN; +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (1, NULL, '/system', '系统管理', '', 0, 'ant-design:setting-outlined', 254, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:46.668745', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (2, 1, '/system/user', '用户管理', 'system:user:list', 1, 'ant-design:user-outlined', 0, 'system/user/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:10:30.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (3, 1, '/system/role', '角色管理', 'system:role:list', 1, 'ep:user', 1, 'system/role/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:11:02.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (4, 1, '/system/menu', '菜单管理', 'system:menu:list', 1, 'ep:menu', 2, 'system/menu/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:11:18.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (5, 1, '/system/monitor', '系统监控', '', 0, 'ep:monitor', 5, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:44.567023', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (6, 5, '/system/monitor/online', '在线用户', 'system:online:list', 1, '', 0, 'system/monitor/online/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-15 22:13:59.519267', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (7, 5, '/sys/monitor/login-log', '登录日志', 'system:log:login:list', 1, '', 0, 'system/monitor/log/login/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-15 22:14:02.610719', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (8, 5, '/system/monitor/serve', '服务监控', 'system:serve:stat', 1, '', 4, 'system/monitor/serve/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-15 22:14:05.606355', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (9, 1, '/system/schedule', '任务调度', '', 0, 'ant-design:schedule-filled', 6, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:52.967983', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (10, 9, '/system/task', '任务管理', '', 1, '', 0, 'system/schedule/task/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:14:39.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (11, 9, '/system/task/log', '任务日志', 'system:task:list', 1, '', 0, 'system/schedule/log/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:15:01.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (12, NULL, '/document', '文档', '', 0, 'ion:tv-outline', 2, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:42.514264', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (14, 12, 'https://www.typeorm.org/', 'Typeorm中文文档(外链)', NULL, 1, '', 3, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-30 18:39:53.000000', 1, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (15, 12, 'https://docs.nestjs.cn/', 'Nest.js中文文档(内嵌)', '', 1, '', 4, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-30 18:40:43.000000', 1, 2, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (20, 2, NULL, '新增', 'system:user:create', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (21, 2, '', '删除', 'system:user:delete', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (22, 2, '', '更新', 'system:user:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (23, 2, '', '查询', 'system:user:read', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (24, 3, '', '新增', 'system:role:create', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (25, 3, '', '删除', 'system:role:delete', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (26, 3, '', '修改', 'system:role:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (27, 3, '', '查询', 'system:role:read', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (28, 4, NULL, '新增', 'system:menu:create', 2, NULL, 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (29, 4, NULL, '删除', 'system:menu:delete', 2, NULL, 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (30, 4, '', '修改', 'system:menu:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (31, 4, NULL, '查询', 'system:menu:read', 2, NULL, 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (32, 6, '', '下线', 'system:online:kick', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (34, 10, '', '新增', 'system:task:create', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (35, 10, '', '删除', 'system:task:delete', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (36, 10, '', '执行一次', 'system:task:once', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (37, 10, '', '查询', 'system:task:read', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (38, 10, '', '运行', 'system:task:start', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (39, 10, '', '暂停', 'system:task:stop', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (40, 10, '', '更新', 'system:task:update', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (41, 7, '', '查询登录日志', 'system:log:login:list', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (42, 7, '', '查询任务日志', 'system:log:task:list', 2, '', 0, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (43, NULL, '/about', '关于', '', 1, 'ant-design:info-circle-outlined', 260, 'account/about', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-02-10 09:35:41.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (48, NULL, '/tool', '系统工具', NULL, 0, 'ant-design:tool-outlined', 254, '', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:28.327223', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (49, 48, '/tool/email', '邮件工具', 'system:tools:email', 1, 'ant-design:send-outlined', 1, 'tool/email/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 00:59:07.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (50, 49, NULL, '发送邮件', 'tools:email:send', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (51, 48, '/tool/storage', '存储管理', 'tool:storage:list', 1, 'ant-design:appstore-outlined', 2, 'tool/storage/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 00:59:17.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (52, 51, NULL, '文件上传', 'upload:upload', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 01:04:08.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (53, 51, NULL, '文件删除', 'tool:storage:delete', 2, '', 2, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-25 00:56:01.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (54, 2, NULL, '修改密码', 'system:user:password', 2, '', 5, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (56, 1, '/system/dict-type', '字典管理', 'system:dict-type:list', 1, 'ant-design:book-outlined', 4, 'system/dict-type/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:12.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (57, 56, NULL, '新增', 'system:dict-type:create', 2, '', 1, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:20.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (58, 56, NULL, '更新', 'system:dict-type:update', 2, '', 2, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:26.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (59, 56, NULL, '删除', 'system:dict-type:delete', 2, '', 3, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:42.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (60, 56, NULL, '查询', 'system:dict-type:info', 2, '', 4, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-28 09:07:36.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (61, 1, '/system/dept', '部门管理', 'system:dept:list', 1, 'ant-design:deployment-unit-outlined', 3, 'system/dept/index', 1, 1, 1, '2023-11-10 00:31:44.023393', '2024-01-17 03:11:55.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (62, 61, NULL, '新增', 'system:dept:create', 2, '', 1, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (63, 61, NULL, '更新', 'system:dept:update', 2, '', 2, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (64, 61, NULL, '删除', 'system:dept:delete', 2, '', 3, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (65, 61, NULL, '查询', 'system:dept:read', 2, '', 4, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (68, 5, '/health', '健康检查', '', 1, '', 4, '', 1, 0, 1, '2023-11-10 00:31:44.023393', '2024-01-27 18:53:33.352155', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (69, 68, NULL, '网络', 'app:health:network', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (70, 68, NULL, '数据库', 'app:health: database', 2, '', 0, NULL, 1, 1, 1, '2023-11-10 00:31:44.023393', '2023-11-10 00:31:44.034474', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (86, 1, '/param-config', '参数配置', 'system:param-config:list', 1, 'ep:edit', 255, 'system/param-config/index', 0, 1, 1, '2024-01-10 17:34:52.569663', '2024-01-19 02:11:27.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (87, 86, NULL, '查询', 'system:param-config:read', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:39:20.983241', '2024-01-10 17:39:20.983241', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (88, 86, NULL, '新增', 'system:param-config:create', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:39:57.543510', '2024-01-10 17:39:57.543510', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (89, 86, NULL, '更新', 'system:param-config:update', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:40:27.355944', '2024-01-10 17:40:27.355944', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (92, 86, NULL, '删除', 'system:param-config:delete', 2, '', 255, NULL, 1, 1, 1, '2024-01-10 17:57:32.059887', '2024-01-10 17:57:32.059887', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (107, 1, 'system/dict-item/:id', '字典项管理', 'system:dict-item:list', 1, 'ant-design:facebook-outlined', 255, 'system/dict-item/index', 0, 0, 1, '2024-01-28 09:21:17.409532', '2024-01-30 13:09:47.000000', 0, 1, '字典管理'); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (108, 107, NULL, '新增', 'system:dict-item:create', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:22:39.401758', '2024-01-28 22:38:36.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (109, 107, NULL, '更新', 'system:dict-item:update', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:26:43.911886', '2024-01-28 09:26:43.911886', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (110, 107, NULL, '删除', 'system:dict-item:delete', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:27:28.535225', '2024-01-28 09:27:28.535225', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (111, 107, NULL, '查询', 'system:dict-item:info', 2, '', 255, NULL, 1, 1, 1, '2024-01-28 09:27:43.894820', '2024-01-28 09:27:43.894820', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (112, 12, 'https://antdv.com/components/overview-cn', 'antdv文档(内嵌)', NULL, 1, '', 255, NULL, 1, 1, 1, '2024-01-29 09:23:08.407723', '2024-01-30 18:41:19.000000', 1, 2, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (115, NULL, 'netdisk', '网盘管理', NULL, 0, 'ant-design:cloud-server-outlined', 255, NULL, 1, 1, 1, '2024-02-10 08:00:02.394616', '2024-02-10 09:35:34.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (116, 115, 'manage', '文件管理', 'netdisk:manage:list', 1, '', 252, 'netdisk/manage', 0, 1, 1, '2024-02-10 08:03:49.837348', '2024-02-10 09:34:41.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (117, 116, NULL, '创建文件或文件夹', 'netdisk:manage:create', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:40:22.317257', '2024-02-10 08:40:22.317257', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (118, 116, NULL, '查看文件', 'netdisk:manage:read', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:41:22.008015', '2024-02-10 08:41:22.008015', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (119, 116, NULL, '更新', 'netdisk:manage:update', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:41:50.691643', '2024-02-10 08:41:50.691643', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (120, 116, NULL, '删除', 'netdisk:manage:delete', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:42:09.480601', '2024-02-10 08:42:09.480601', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (121, 116, NULL, '获取文件上传token', 'netdisk:manage:token', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:42:57.688104', '2024-02-10 08:42:57.688104', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (122, 116, NULL, '添加文件备注', 'netdisk:manage:mark', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:43:40.117321', '2024-02-10 08:43:40.117321', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (123, 116, NULL, '下载文件', 'netdisk:manage:download', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:44:01.338984', '2024-02-10 08:44:01.338984', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (124, 116, NULL, '重命名文件或文件夹', 'netdisk:manage:rename', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:44:27.233379', '2024-02-10 08:45:36.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (125, 116, NULL, '复制文件或文件夹', 'netdisk:manage:copy', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:44:44.725391', '2024-02-10 08:45:48.000000', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (126, 116, NULL, '剪切文件或文件夹', 'netdisk:manage:cut', 2, '', 255, NULL, 1, 1, 1, '2024-02-10 08:45:21.660511', '2024-02-10 08:45:21.660511', 0, 1, NULL); +INSERT INTO `sys_menu` (`id`, `parent_id`, `path`, `name`, `permission`, `type`, `icon`, `order_no`, `component`, `keep_alive`, `show`, `status`, `created_at`, `updated_at`, `is_ext`, `ext_open_mode`, `active_menu`) VALUES (127, 115, 'overview', '网盘概览', 'netdisk:overview:desc', 1, '', 254, 'netdisk/overview', 0, 1, 1, '2024-02-10 09:32:56.981190', '2024-02-10 09:34:18.000000', 0, 1, NULL); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `id` int NOT NULL AUTO_INCREMENT, + `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `status` tinyint DEFAULT '1', + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `default` tinyint DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `IDX_223de54d6badbe43a5490450c3` (`name`) USING BTREE, + UNIQUE KEY `IDX_05edc0a51f41bb16b7d8137da9` (`value`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +BEGIN; +INSERT INTO `sys_role` (`id`, `value`, `name`, `remark`, `status`, `created_at`, `updated_at`, `default`) VALUES (1, 'admin', '管理员', '超级管理员', 1, '2023-11-10 00:31:44.058463', '2024-01-28 21:08:39.000000', NULL); +INSERT INTO `sys_role` (`id`, `value`, `name`, `remark`, `status`, `created_at`, `updated_at`, `default`) VALUES (2, 'user', '用户', '', 1, '2023-11-10 00:31:44.058463', '2024-01-30 18:44:45.000000', 1); +INSERT INTO `sys_role` (`id`, `value`, `name`, `remark`, `status`, `created_at`, `updated_at`, `default`) VALUES (9, 'test', '测试', NULL, 1, '2024-01-23 22:46:52.408827', '2024-01-30 01:04:52.000000', NULL); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_role_menus +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menus`; +CREATE TABLE `sys_role_menus` ( + `role_id` int NOT NULL, + `menu_id` int NOT NULL, + PRIMARY KEY (`role_id`,`menu_id`), + KEY `IDX_35ce749b04d57e226d059e0f63` (`role_id`), + KEY `IDX_2b95fdc95b329d66c18f5baed6` (`menu_id`), + CONSTRAINT `FK_2b95fdc95b329d66c18f5baed6d` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE, + CONSTRAINT `FK_35ce749b04d57e226d059e0f633` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of sys_role_menus +-- ---------------------------- +BEGIN; +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 1); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 2); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 3); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 4); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 5); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 6); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 7); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 8); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 9); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 10); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 11); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 12); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 14); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 15); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 20); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 21); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 22); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 23); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 24); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 25); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 26); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 27); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 28); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 29); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 30); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 31); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 32); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 34); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 35); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 36); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 37); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 38); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 39); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 40); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 41); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 42); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 43); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 48); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 49); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 50); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 51); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 52); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 53); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 54); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 56); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 57); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 58); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 59); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 60); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 61); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 62); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 63); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 64); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 65); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 68); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 69); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 70); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 86); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 87); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 88); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 89); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 92); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 107); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 108); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 109); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 110); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (1, 111); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 1); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 5); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 6); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 7); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 8); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 9); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 10); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 11); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 12); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 14); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 15); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 32); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 34); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 35); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 36); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 37); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 38); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 39); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 40); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 41); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 42); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 43); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 48); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 49); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 50); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 51); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 52); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 53); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 56); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 57); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 58); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 59); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 60); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 68); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 69); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 70); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 86); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 87); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 88); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 89); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 92); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 107); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 108); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 109); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 110); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 111); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (2, 112); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 1); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 2); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 3); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 4); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 5); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 6); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 7); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 8); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 9); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 10); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 11); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 20); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 21); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 22); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 23); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 24); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 25); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 26); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 27); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 28); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 29); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 30); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 31); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 32); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 34); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 35); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 36); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 37); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 38); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 39); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 40); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 41); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 42); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 54); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 56); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 57); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 58); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 59); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 60); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 61); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 62); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 63); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 64); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 65); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 68); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 69); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 70); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 86); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 87); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 88); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 89); +INSERT INTO `sys_role_menus` (`role_id`, `menu_id`) VALUES (9, 92); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_task +-- ---------------------------- +DROP TABLE IF EXISTS `sys_task`; +CREATE TABLE `sys_task` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `service` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `type` tinyint NOT NULL DEFAULT '0', + `status` tinyint NOT NULL DEFAULT '1', + `start_time` datetime DEFAULT NULL, + `end_time` datetime DEFAULT NULL, + `limit` int DEFAULT '0', + `cron` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `every` int DEFAULT NULL, + `data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + `job_opts` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `IDX_ef8e5ab5ef2fe0ddb1428439ef` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_task +-- ---------------------------- +BEGIN; +INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (2, '定时清空登录日志', 'LogClearJob.clearLoginLog', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"key\":\"__default__:2:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":2}', '定时清空登录日志', '2023-11-10 00:31:44.197779', '2024-02-10 09:43:14.000000'); +INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (3, '定时清空任务日志', 'LogClearJob.clearTaskLog', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"key\":\"__default__:3:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":3}', '定时清空任务日志', '2023-11-10 00:31:44.197779', '2024-02-10 09:43:14.000000'); +INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (4, '访问百度首页', 'HttpRequestJob.handle', 0, 0, NULL, NULL, 1, '* * * * * ?', NULL, '{\"url\":\"https://www.baidu.com\",\"method\":\"get\"}', NULL, '访问百度首页', '2023-11-10 00:31:44.197779', '2023-11-10 00:31:44.206935'); +INSERT INTO `sys_task` (`id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`, `created_at`, `updated_at`) VALUES (5, '发送邮箱', 'EmailJob.send', 0, 0, NULL, NULL, -1, '0 0 0 1 * ?', NULL, '{\"subject\":\"这是标题\",\"to\":\"zeyu57@163.com\",\"content\":\"这是正文\"}', NULL, '每月发送邮箱', '2023-11-10 00:31:44.197779', '2023-11-10 00:31:44.206935'); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_task_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_task_log`; +CREATE TABLE `sys_task_log` ( + `id` int NOT NULL AUTO_INCREMENT, + `task_id` int DEFAULT NULL, + `status` tinyint NOT NULL DEFAULT '0', + `detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, + `consume_time` int DEFAULT '0', + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (`id`) USING BTREE, + KEY `FK_f4d9c36052fdb188ff5c089454b` (`task_id`), + CONSTRAINT `FK_f4d9c36052fdb188ff5c089454b` FOREIGN KEY (`task_id`) REFERENCES `sys_task` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_task_log +-- ---------------------------- +BEGIN; +INSERT INTO `sys_task_log` (`id`, `task_id`, `status`, `detail`, `consume_time`, `created_at`, `updated_at`) VALUES (1, 3, 1, NULL, 0, '2024-02-05 03:06:22.037448', '2024-02-05 03:06:22.037448'); +INSERT INTO `sys_task_log` (`id`, `task_id`, `status`, `detail`, `consume_time`, `created_at`, `updated_at`) VALUES (2, 2, 1, NULL, 0, '2024-02-10 09:42:21.738712', '2024-02-10 09:42:21.738712'); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `id` int NOT NULL AUTO_INCREMENT, + `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `psalt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + `status` tinyint DEFAULT '1', + `qq` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL, + `dept_id` int DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `IDX_9e7164b2f1ea1348bc0eb0a7da` (`username`) USING BTREE, + KEY `FK_96bde34263e2ae3b46f011124ac` (`dept_id`), + CONSTRAINT `FK_96bde34263e2ae3b46f011124ac` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +BEGIN; +INSERT INTO `sys_user` (`id`, `username`, `password`, `avatar`, `email`, `phone`, `remark`, `psalt`, `status`, `qq`, `created_at`, `updated_at`, `nickname`, `dept_id`) VALUES (1, 'admin', 'a11571e778ee85e82caae2d980952546', 'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777', '1743369777@qq.com', '10086', '管理员', 'xQYCspvFb8cAW6GG1pOoUGTLqsuUSO3d', 1, '1743369777', '2023-11-10 00:31:44.104382', '2024-01-29 09:49:43.000000', 'bqy', 1); +INSERT INTO `sys_user` (`id`, `username`, `password`, `avatar`, `email`, `phone`, `remark`, `psalt`, `status`, `qq`, `created_at`, `updated_at`, `nickname`, `dept_id`) VALUES (2, 'user', 'dbd89546dec743f82bb9073d6ac39361', 'https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=1743369777', 'luffy@qq.com', '10010', '王路飞', 'qlovDV7pL5dPYPI3QgFFo1HH74nP6sJe', 1, '1743369777', '2023-11-10 00:31:44.104382', '2024-01-29 09:49:57.000000', 'luffy', 8); +INSERT INTO `sys_user` (`id`, `username`, `password`, `avatar`, `email`, `phone`, `remark`, `psalt`, `status`, `qq`, `created_at`, `updated_at`, `nickname`, `dept_id`) VALUES (8, 'developer', 'f03fa2a99595127b9a39587421d471f6', '/upload/cfd0d14459bc1a47-202402032141838.jpeg', 'nami@qq.com', '10000', '小贼猫', 'NbGM1z9Vhgo7f4dd2I7JGaGP12RidZdE', 1, '1743369777', '2023-11-10 00:31:44.104382', '2024-02-03 21:41:18.000000', '娜美', 7); +COMMIT; + +-- ---------------------------- +-- Table structure for sys_user_roles +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_roles`; +CREATE TABLE `sys_user_roles` ( + `user_id` int NOT NULL, + `role_id` int NOT NULL, + PRIMARY KEY (`user_id`,`role_id`), + KEY `IDX_96311d970191a044ec048011f4` (`user_id`), + KEY `IDX_6d61c5b3f76a3419d93a421669` (`role_id`), + CONSTRAINT `FK_6d61c5b3f76a3419d93a4216695` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`), + CONSTRAINT `FK_96311d970191a044ec048011f44` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of sys_user_roles +-- ---------------------------- +BEGIN; +INSERT INTO `sys_user_roles` (`user_id`, `role_id`) VALUES (1, 1); +INSERT INTO `sys_user_roles` (`user_id`, `role_id`) VALUES (2, 2); +INSERT INTO `sys_user_roles` (`user_id`, `role_id`) VALUES (8, 2); +COMMIT; + +-- ---------------------------- +-- Table structure for todo +-- ---------------------------- +DROP TABLE IF EXISTS `todo`; +CREATE TABLE `todo` ( + `id` int NOT NULL AUTO_INCREMENT, + `value` varchar(255) NOT NULL, + `user_id` int DEFAULT NULL, + `status` tinyint NOT NULL DEFAULT '0', + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (`id`), + KEY `FK_9cb7989853c4cb7fe427db4b260` (`user_id`), + CONSTRAINT `FK_9cb7989853c4cb7fe427db4b260` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Records of todo +-- ---------------------------- +BEGIN; +INSERT INTO `todo` (`id`, `value`, `user_id`, `status`, `created_at`, `updated_at`) VALUES (1, 'nest.js', NULL, 0, '2023-11-10 00:31:44.139730', '2023-11-10 00:31:44.147629'); +COMMIT; + +-- ---------------------------- +-- Table structure for tool_storage +-- ---------------------------- +DROP TABLE IF EXISTS `tool_storage`; +CREATE TABLE `tool_storage` ( + `id` int NOT NULL AUTO_INCREMENT, + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件名', + `fileName` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '真实文件名', + `ext_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `size` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `user_id` int DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=79 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of tool_storage +-- ---------------------------- +BEGIN; +INSERT INTO `tool_storage` (`id`, `created_at`, `updated_at`, `name`, `fileName`, `ext_name`, `path`, `type`, `size`, `user_id`) VALUES (78, '2024-02-03 21:41:16.851178', '2024-02-03 21:41:16.851178', 'cfd0d14459bc1a47-202402032141838.jpeg', 'cfd0d14459bc1a47.jpeg', 'jpeg', '/upload/cfd0d14459bc1a47-202402032141838.jpeg', '图片', '33.92 KB', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for user_access_tokens +-- ---------------------------- +DROP TABLE IF EXISTS `user_access_tokens`; +CREATE TABLE `user_access_tokens` ( + `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `expired_at` datetime NOT NULL COMMENT '令牌过期时间', + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '令牌创建时间', + `user_id` int DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_e9d9d0c303432e4e5e48c1c3e90` (`user_id`), + CONSTRAINT `FK_e9d9d0c303432e4e5e48c1c3e90` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of user_access_tokens +-- ---------------------------- +BEGIN; +INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('09cf7b0a-62e0-45ee-96b0-e31de32361e0', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDc1MDkxNTd9.0gtKlcxrxQ-TarEai2lsBnfMc852ZDYHeSjjhpo5Fn8', '2024-02-11 04:05:58', '2024-02-10 04:05:57.696509', 1); +INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('3f7dffae-db1f-47dc-9677-5c956c3de39e', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDczMTEzMDJ9.D5Qpht1RquKor8WtgfGAcCp8LwG7z3FZhIwbyQzhDmE', '2024-02-08 21:08:22', '2024-02-07 21:08:22.130066', 1); +INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('40342c3e-194c-42eb-adee-189389839195', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDczNzIxNjF9.tRQOxhB-01Pcut5MXm4L5D1OrbMJfS4LfUys0XB4kWs', '2024-02-09 14:02:41', '2024-02-08 14:02:41.081164', 1); +INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('9d1ba8e9-dffc-4b15-b21f-4a90f196e39c', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDc1Mjc5MDV9.7LeiS3LBBdiAc7YrULWpmnI1oNSvR79K-qjEOlBYOnI', '2024-02-11 09:18:26', '2024-02-10 09:18:25.656695', 1); +INSERT INTO `user_access_tokens` (`id`, `value`, `expired_at`, `created_at`, `user_id`) VALUES ('edbed8fb-bfc7-4fc7-a012-e9fca8ef93fb', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsInB2IjoxLCJyb2xlcyI6WyJhZG1pbiJdLCJpYXQiOjE3MDczNzIxMjd9.VRuJHGca2IPrdfTyW09wfhht4x8JX207pKG-0aZyF60', '2024-02-09 14:02:07', '2024-02-08 14:02:07.390658', 1); +COMMIT; + +-- ---------------------------- +-- Table structure for user_refresh_tokens +-- ---------------------------- +DROP TABLE IF EXISTS `user_refresh_tokens`; +CREATE TABLE `user_refresh_tokens` ( + `id` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `expired_at` datetime NOT NULL COMMENT '令牌过期时间', + `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '令牌创建时间', + `accessTokenId` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `REL_1dfd080c2abf42198691b60ae3` (`accessTokenId`), + CONSTRAINT `FK_1dfd080c2abf42198691b60ae39` FOREIGN KEY (`accessTokenId`) REFERENCES `user_access_tokens` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- ---------------------------- +-- Records of user_refresh_tokens +-- ---------------------------- +BEGIN; +INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('202d0969-6721-4f6f-bf34-f0d1931d4d01', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiRTRpOXVYei1TdldjdWRnclFXVmFXIiwiaWF0IjoxNzA3MzcyMTYxfQ.NOQufR5EAPE2uZoyenmAj9H7S7qo4d6W1aW2ojDxZQc', '2024-03-09 14:02:41', '2024-02-08 14:02:41.091492', '40342c3e-194c-42eb-adee-189389839195'); +INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('461f9b7c-e500-4762-a6d9-f9ea47163064', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoicXJvTWNYMnhNRW5uRmZGWkQtaUx0IiwiaWF0IjoxNzA3MzExMzAyfQ.dFIWCePZnn2z2Qv1D5PKBKXUwVDI0Gp091MIOi9jiIo', '2024-03-08 21:08:22', '2024-02-07 21:08:22.145464', '3f7dffae-db1f-47dc-9677-5c956c3de39e'); +INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('b375e623-2d82-48f0-9b7a-9058e3850cc6', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoicDhUMzdGNFFaUDJHLU5yNGVha21wIiwiaWF0IjoxNzA3MzcyMTI3fQ.fn3It6RKIxXlKmqixg0BMmY_YsQmAxtetueqW-0y1IM', '2024-03-09 14:02:07', '2024-02-08 14:02:07.410008', 'edbed8fb-bfc7-4fc7-a012-e9fca8ef93fb'); +INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('e620ccc1-9e40-4387-9f21-f0722e535a63', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiNE5WdmFIc2hWaU05ZFh0QnVBaHNsIiwiaWF0IjoxNzA3NTI3OTA1fQ.zzyGX0mOJe6KWpTzIi7We9d9c0MRuDeGC86DMB0Vubs', '2024-03-11 09:18:26', '2024-02-10 09:18:25.664251', '9d1ba8e9-dffc-4b15-b21f-4a90f196e39c'); +INSERT INTO `user_refresh_tokens` (`id`, `value`, `expired_at`, `created_at`, `accessTokenId`) VALUES ('f9a003e8-91b7-41ee-979e-e39cca3534ec', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiWGJQdl9SVjFtUl80N0o0TGF0QlV5IiwiaWF0IjoxNzA3NTA5MTU3fQ.oEVdWSigTpAQY7F8MlwBnedldH0sJT1YF1Mt0ZUbIw4', '2024-03-11 04:05:58', '2024-02-10 04:05:57.706763', '09cf7b0a-62e0-45ee-96b0-e31de32361e0'); +COMMIT; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/deploy/web/default.conf b/deploy/web/default.conf new file mode 100644 index 0000000..c4f24e0 --- /dev/null +++ b/deploy/web/default.conf @@ -0,0 +1,54 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 80; + absolute_redirect off; #取消绝对路径的重定向 + sendfile on; + default_type application/octet-stream; + + gzip on; + gzip_http_version 1.1; + gzip_disable "MSIE [1-6]\."; + gzip_min_length 256; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + gzip_comp_level 9; + + root /usr/share/nginx/html; + + location / { + # same docker config + root /usr/share/nginx/html; + index index.html; + # support history mode + try_files $uri $uri/ /index.html; + } + + # 后端服务 + location ^~ /api/ { + proxy_pass http://nest-admin-server:7001/; # 转发规则 + proxy_set_header Host $proxy_host; # 修改转发请求头,让目标应用可以受到真实的请求 + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # websocket服务 + location ^~ /ws-api/ { + proxy_pass http://nest-admin-server:7002/; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + + proxy_set_header Host $host; + proxy_set_header X-real-ip $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + } + +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4139438 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,64 @@ +version: '3' + +services: + mysql: + image: mysql:latest + container_name: nest-admin-mysql + restart: always + env_file: + - .env + - .env.production + environment: + - MYSQL_HOST=${DB_HOST} + - MYSQL_PORT=${DB_PORT} + - MYSQL_DATABASE=${DB_DATABASE} + - MYSQL_USERNAME=${DB_USERNAME} + - MYSQL_PASSWORD=${DB_PASSWORD} + - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} + ports: + - '${DB_PORT}:3306' + command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci #设置utf8字符集 + volumes: + - ./__data/mysql/:/var/lib/mysql/ # ./__data/mysql/ 路径可以替换成自己的路径 + - ./deploy/sql/:/docker-entrypoint-initdb.d/ # 初始化的脚本,若 ./__data/mysql/ 文件夹存在数据,则不会执行初始化脚本 + networks: + - nest_admin_net + + redis: + image: redis:alpine + container_name: nest-admin-redis + restart: always + env_file: + - .env + - .env.production + ports: + - '${REDIS_PORT}:6379' + command: > + --requirepass ${REDIS_PASSWORD} + networks: + - nest_admin_net + + nest-admin-server: + # build: 从当前路径构建镜像 + build: + context: . + dockerfile: Dockerfile + container_name: nest-admin-server + restart: always + env_file: + - .env + - .env.production + extra_hosts: + - 'host.docker.internal:host-gateway' + ports: + - '${APP_PORT}:${APP_PORT}' + # 当前服务启动之前先要把depends_on指定的服务启动起来才行 + depends_on: + - mysql + - redis + networks: + - nest_admin_net + +networks: + nest_admin_net: + name: nest_admin_net diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..8f2eb78 --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,22 @@ +const { cpus } = require('os') + +const cpuLen = cpus().length + +module.exports = { + apps: [ + { + name: 'nest-admin', + script: './dist/main.js', + autorestart: true, + exec_mode: 'cluster', + watch: false, + instances: cpuLen, + max_memory_restart: '520M', + args: '', + env: { + NODE_ENV: 'production', + PORT: process.env.APP_PORT, + }, + }, + ], +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c3ca7c3 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,36 @@ +const antfu = require('@antfu/eslint-config').default + +module.exports = antfu({ + stylistic: { + indent: 2, + quotes: 'single', + }, + typescript: true, +}, { + rules: { + 'no-console': 'off', + 'unused-imports/no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 2, + + 'ts/consistent-type-imports': 'off', + 'node/prefer-global/process': 'off', + 'node/prefer-global/buffer': 'off', + + 'import/order': [ + 2, + { + 'pathGroups': [ + { + pattern: '~/**', + group: 'external', + position: 'after', + }, + ], + 'alphabetize': { order: 'asc', caseInsensitive: false }, + 'newlines-between': 'always-and-inside-groups', + 'warnOnUnassignedImports': true, + }, + ], + + }, +}) diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..86963b2 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true, + "assets": [ + { "include": "assets/**/*", "outDir": "dist", "watchAssets": true } + ], + "plugins": [{ + "name": "@nestjs/swagger", + "options": { + "introspectComments": true + } + }] + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e137183 --- /dev/null +++ b/package.json @@ -0,0 +1,174 @@ +{ + "name": "nest-admin", + "version": "2.0.0", + "private": true, + "packageManager": "pnpm@8.10.2", + "license": "MIT", + "engines": { + "node": ">=18", + "pnpm": ">=8.1.0" + }, + "scripts": { + "postinstall": "npm run gen-env-types", + "prebuild": "rimraf dist", + "build": "nest build", + "dev": "npm run start", + "dev:debug": "npm run start:debug", + "repl": "npm run start -- --entryFile repl", + "bundle": "rimraf out && npm run build && ncc build dist/main.js -o out -m -t && chmod +x out/index.js", + "start": "cross-env NODE_ENV=development nest start -w --path tsconfig.json", + "start:debug": "cross-env NODE_ENV=development nest start --debug --watch", + "start:prod": "cross-env NODE_ENV=production node dist/main", + "prod": "cross-env NODE_ENV=production pm2-runtime start ecosystem.config.js", + "prod:pm2": "cross-env NODE_ENV=production pm2 restart ecosystem.config.js", + "prod:stop": "pm2 stop ecosystem.config.js", + "prod:debug": "cross-env NODE_ENV=production nest start --debug --watch", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "test": "jest", + "test:watch": "jest --watch", + "doc": "compodoc -p tsconfig.json -s", + "gen-env-types": "npx tsx scripts/genEnvTypes.ts", + "typeorm": "NODE_ENV=development typeorm-ts-node-esm -d ./dist/config/database.config.js", + "migration:create": "npm run typeorm migration:create ./src/migrations/initData", + "migration:generate": "npm run typeorm migration:generate ./src/migrations/update-table_$(echo $npm_package_version | sed 's/\\./_/g')", + "migration:run": "npm run typeorm -- migration:run", + "migration:revert": "npm run typeorm -- migration:revert", + "cleanlog": "rimraf logs", + "docker:build:dev": "docker compose --env-file .env --env-file .env.development up --build", + "docker:build": "docker compose --env-file .env --env-file .env.production up --build", + "docker:up": "docker compose --env-file .env --env-file .env.production up -d --no-build", + "docker:down": "docker compose --env-file .env --env-file .env.production down", + "docker:rmi": "docker compose --env-file .env --env-file .env.production stop nest-admin-server && docker container rm nest-admin-server && docker rmi nest-admin-server", + "docker:logs": "docker compose --env-file .env --env-file .env.production logs -f", + "c": "git add . && git cz && git push", + "release": "standard-version", + "commitlint": "commitlint --config commitlint.config.cjs -e -V", + "lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,md}\"" + }, + "dependencies": { + "@fastify/cookie": "^9.3.1", + "@fastify/multipart": "^8.1.0", + "@fastify/static": "^7.0.1", + "@liaoliaots/nestjs-redis": "^9.0.5", + "@nestjs-modules/mailer": "^1.10.3", + "@nestjs/axios": "^3.0.2", + "@nestjs/bull": "^10.1.0", + "@nestjs/cache-manager": "^2.2.1", + "@nestjs/common": "^10.3.3", + "@nestjs/config": "^3.2.0", + "@nestjs/core": "^10.3.3", + "@nestjs/event-emitter": "^2.0.4", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.3", + "@nestjs/platform-fastify": "^10.3.3", + "@nestjs/platform-socket.io": "^10.3.3", + "@nestjs/schedule": "^4.0.1", + "@nestjs/swagger": "^7.3.0", + "@nestjs/terminus": "^10.2.2", + "@nestjs/throttler": "^5.1.2", + "@nestjs/typeorm": "^10.0.2", + "@nestjs/websockets": "^10.3.3", + "@socket.io/redis-adapter": "^8.2.1", + "@socket.io/redis-emitter": "^5.1.0", + "@types/lodash": "^4.14.202", + "axios": "^1.6.7", + "bull": "^4.12.2", + "cache-manager": "^5.4.0", + "cache-manager-ioredis-yet": "^1.2.2", + "chalk": "^5.3.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "cron": "^3.1.6", + "cron-parser": "^4.9.0", + "crypto-js": "^4.2.0", + "dayjs": "^1.11.10", + "dotenv": "16.4.4", + "handlebars": "^4.7.8", + "helmet": "^7.1.0", + "ioredis": "^5.3.2", + "lodash": "^4.17.21", + "mysql2": "^3.9.1", + "nanoid": "^3.3.7", + "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", + "qiniu": "^7.11.0", + "reflect-metadata": "^0.2.1", + "rimraf": "^5.0.5", + "rxjs": "^7.8.1", + "socket.io": "^4.7.4", + "stacktrace-js": "^2.0.2", + "svg-captcha": "^1.4.0", + "systeminformation": "^5.22.0", + "typeorm": "0.3.17", + "ua-parser-js": "^1.0.37", + "winston": "^3.11.0", + "winston-daily-rotate-file": "^5.0.0" + }, + "devDependencies": { + "@antfu/eslint-config": "^2.6.4", + "@compodoc/compodoc": "^1.1.23", + "@nestjs/cli": "^10.3.2", + "@nestjs/schematics": "^10.1.1", + "@nestjs/testing": "^10.3.2", + "@types/cache-manager": "^4.0.6", + "@types/jest": "29.5.12", + "@types/multer": "^1.4.11", + "@types/node": "^20.11.16", + "@types/supertest": "^6.0.2", + "@types/ua-parser-js": "^0.7.39", + "cross-env": "^7.0.3", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "lint-staged": "^15.2.2", + "source-map-support": "^0.5.21", + "supertest": "^6.3.4", + "ts-jest": "^29.1.2", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.3.3", + "cliui": "^8.0.1", + "commitizen": "^4.3.0", + "cz-customizable": "^7.0.0", + "standard-version": "^9.5.0", + "husky": "^8.0.0" + }, + "pnpm": { + "overrides": { + "@liaoliaots/nestjs-redis": "npm:@songkeys/nestjs-redis" + } + }, + "config": { + "commitizen": { + "path": "cz-customizable" + } + }, + "lint-staged": { + "*": [ + "npm run lint" + ] + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "moduleNameMapper": { + "^~/(.*)$": "/$1" + }, + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..f084228 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,12758 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + '@liaoliaots/nestjs-redis': npm:@songkeys/nestjs-redis + +dependencies: + '@fastify/cookie': + specifier: ^9.3.1 + version: 9.3.1 + '@fastify/multipart': + specifier: ^8.1.0 + version: 8.1.0 + '@fastify/static': + specifier: ^7.0.1 + version: 7.0.1 + '@liaoliaots/nestjs-redis': + specifier: npm:@songkeys/nestjs-redis + version: /@songkeys/nestjs-redis@10.0.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(ioredis@5.3.2) + '@nestjs-modules/mailer': + specifier: ^1.10.3 + version: 1.10.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(nodemailer@6.9.9) + '@nestjs/axios': + specifier: ^3.0.2 + version: 3.0.2(@nestjs/common@10.3.3)(axios@1.6.7)(rxjs@7.8.1) + '@nestjs/bull': + specifier: ^10.1.0 + version: 10.1.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(bull@4.12.2) + '@nestjs/cache-manager': + specifier: ^2.2.1 + version: 2.2.1(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(cache-manager@5.4.0)(rxjs@7.8.1) + '@nestjs/common': + specifier: ^10.3.3 + version: 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^3.2.0 + version: 3.2.0(@nestjs/common@10.3.3)(rxjs@7.8.1) + '@nestjs/core': + specifier: ^10.3.3 + version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/event-emitter': + specifier: ^2.0.4 + version: 2.0.4(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.3.3) + '@nestjs/passport': + specifier: ^10.0.3 + version: 10.0.3(@nestjs/common@10.3.3)(passport@0.7.0) + '@nestjs/platform-fastify': + specifier: ^10.3.3 + version: 10.3.3(@fastify/static@7.0.1)(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) + '@nestjs/platform-socket.io': + specifier: ^10.3.3 + version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(rxjs@7.8.1) + '@nestjs/schedule': + specifier: ^4.0.1 + version: 4.0.1(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) + '@nestjs/swagger': + specifier: ^7.3.0 + version: 7.3.0(@fastify/static@7.0.1)(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1) + '@nestjs/terminus': + specifier: ^10.2.2 + version: 10.2.2(@nestjs/axios@3.0.2)(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/typeorm@10.0.2)(reflect-metadata@0.2.1)(rxjs@7.8.1)(typeorm@0.3.17) + '@nestjs/throttler': + specifier: ^5.1.2 + version: 5.1.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.2.1) + '@nestjs/typeorm': + specifier: ^10.0.2 + version: 10.0.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1)(typeorm@0.3.17) + '@nestjs/websockets': + specifier: ^10.3.3 + version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-socket.io@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@socket.io/redis-adapter': + specifier: ^8.2.1 + version: 8.2.1(socket.io-adapter@2.5.2) + '@socket.io/redis-emitter': + specifier: ^5.1.0 + version: 5.1.0 + '@types/lodash': + specifier: ^4.14.202 + version: 4.14.202 + axios: + specifier: ^1.6.7 + version: 1.6.7 + bull: + specifier: ^4.12.2 + version: 4.12.2 + cache-manager: + specifier: ^5.4.0 + version: 5.4.0 + cache-manager-ioredis-yet: + specifier: ^1.2.2 + version: 1.2.2 + chalk: + specifier: ^5.3.0 + version: 5.3.0 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + cron: + specifier: ^3.1.6 + version: 3.1.6 + cron-parser: + specifier: ^4.9.0 + version: 4.9.0 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + dayjs: + specifier: ^1.11.10 + version: 1.11.10 + dotenv: + specifier: 16.4.4 + version: 16.4.4 + handlebars: + specifier: ^4.7.8 + version: 4.7.8 + helmet: + specifier: ^7.1.0 + version: 7.1.0 + ioredis: + specifier: ^5.3.2 + version: 5.3.2 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + mysql2: + specifier: ^3.9.1 + version: 3.9.1 + nanoid: + specifier: ^3.3.7 + version: 3.3.7 + nodemailer: + specifier: ^6.9.9 + version: 6.9.9 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-google-oauth20: + specifier: ^2.0.0 + version: 2.0.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + passport-local: + specifier: ^1.0.0 + version: 1.0.0 + qiniu: + specifier: ^7.11.0 + version: 7.11.0 + reflect-metadata: + specifier: ^0.2.1 + version: 0.2.1 + rimraf: + specifier: ^5.0.5 + version: 5.0.5 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + socket.io: + specifier: ^4.7.4 + version: 4.7.4 + stacktrace-js: + specifier: ^2.0.2 + version: 2.0.2 + svg-captcha: + specifier: ^1.4.0 + version: 1.4.0 + systeminformation: + specifier: ^5.22.0 + version: 5.22.0 + typeorm: + specifier: 0.3.17 + version: 0.3.17(ioredis@5.3.2)(mysql2@3.9.1)(ts-node@10.9.2) + ua-parser-js: + specifier: ^1.0.37 + version: 1.0.37 + winston: + specifier: ^3.11.0 + version: 3.11.0 + winston-daily-rotate-file: + specifier: ^5.0.0 + version: 5.0.0(winston@3.11.0) + +devDependencies: + '@antfu/eslint-config': + specifier: ^2.6.4 + version: 2.6.4(@vue/compiler-sfc@3.4.19)(eslint@8.56.0)(typescript@5.3.3) + '@compodoc/compodoc': + specifier: ^1.1.23 + version: 1.1.23(typescript@5.3.3) + '@nestjs/cli': + specifier: ^10.3.2 + version: 10.3.2 + '@nestjs/schematics': + specifier: ^10.1.1 + version: 10.1.1(chokidar@3.6.0)(typescript@5.3.3) + '@nestjs/testing': + specifier: ^10.3.2 + version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) + '@types/cache-manager': + specifier: ^4.0.6 + version: 4.0.6 + '@types/jest': + specifier: 29.5.12 + version: 29.5.12 + '@types/multer': + specifier: ^1.4.11 + version: 1.4.11 + '@types/node': + specifier: ^20.11.16 + version: 20.11.18 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.2 + '@types/ua-parser-js': + specifier: ^0.7.39 + version: 0.7.39 + cliui: + specifier: ^8.0.1 + version: 8.0.1 + commitizen: + specifier: ^4.3.0 + version: 4.3.0(@types/node@20.11.18)(typescript@5.3.3) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + cz-customizable: + specifier: ^7.0.0 + version: 7.0.0 + eslint: + specifier: ^8.56.0 + version: 8.56.0 + husky: + specifier: ^8.0.0 + version: 8.0.3 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + lint-staged: + specifier: ^15.2.2 + version: 15.2.2 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + standard-version: + specifier: ^9.5.0 + version: 9.5.0 + supertest: + specifier: ^6.3.4 + version: 6.3.4 + ts-jest: + specifier: ^29.1.2 + version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3) + ts-loader: + specifier: ^9.5.1 + version: 9.5.1(typescript@5.3.3)(webpack@5.90.1) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.18)(typescript@5.3.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.3.3 + version: 5.3.3 + +packages: + + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + + /@aduh95/viz.js@3.4.0: + resolution: {integrity: sha512-KI2nVf9JdwWCXqK6RVf+9/096G7VWN4Z84mnynlyZKao2xQENW8WNEjLmvdlxS5X8PNWXFC1zqwm7tveOXw/4A==} + dev: true + + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.22 + dev: true + + /@angular-devkit/core@14.2.12(chokidar@3.6.0): + resolution: {integrity: sha512-tg1+deEZdm3fgk2BQ6y7tujciL6qhtN5Ums266lX//kAZeZ4nNNXTBT+oY5xgfjvmLbW+xKg0XZrAS0oIRKY5g==} + engines: {node: ^14.15.0 || >=16.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + dependencies: + ajv: 8.11.0 + ajv-formats: 2.1.1(ajv@8.11.0) + chokidar: 3.6.0 + jsonc-parser: 3.1.0 + rxjs: 6.6.7 + source-map: 0.7.4 + dev: true + + /@angular-devkit/core@17.1.2(chokidar@3.6.0): + resolution: {integrity: sha512-ku+/W/HMCBacSWFppenr9y6Lx8mDuTuQvn1IkTyBLiJOpWnzgVbx9kHDeaDchGa1PwLlJUBBrv27t3qgJOIDPw==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + chokidar: 3.6.0 + jsonc-parser: 3.2.0 + picomatch: 3.0.1 + rxjs: 7.8.1 + source-map: 0.7.4 + dev: true + + /@angular-devkit/schematics-cli@17.1.2(chokidar@3.6.0): + resolution: {integrity: sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + dependencies: + '@angular-devkit/core': 17.1.2(chokidar@3.6.0) + '@angular-devkit/schematics': 17.1.2(chokidar@3.6.0) + ansi-colors: 4.1.3 + inquirer: 9.2.12 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - chokidar + dev: true + + /@angular-devkit/schematics@14.2.12(chokidar@3.6.0): + resolution: {integrity: sha512-MN5yGR+SSSPPBBVMf4cifDJn9u0IYvxiHst+HWokH2AkBYy+vB1x8jYES2l1wkiISD7nvjTixfqX+Y95oMBoLg==} + engines: {node: ^14.15.0 || >=16.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + dependencies: + '@angular-devkit/core': 14.2.12(chokidar@3.6.0) + jsonc-parser: 3.1.0 + magic-string: 0.26.2 + ora: 5.4.1 + rxjs: 6.6.7 + transitivePeerDependencies: + - chokidar + dev: true + + /@angular-devkit/schematics@17.1.2(chokidar@3.6.0): + resolution: {integrity: sha512-8S9RuM8olFN/gwN+mjbuF1CwHX61f0i59EGXz9tXLnKRUTjsRR+8vVMTAmX0dvVAT5fJTG/T69X+HX7FeumdqA==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + dependencies: + '@angular-devkit/core': 17.1.2(chokidar@3.6.0) + jsonc-parser: 3.2.0 + magic-string: 0.30.5 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + dev: true + + /@antfu/eslint-config@2.6.4(@vue/compiler-sfc@3.4.19)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-dMD/QC5KWS1OltdpKLhfZM7W7y7zils85opk8d4lyNr7yn0OFjZs7eMYtcC6DrrN2kQ1JrFvBM7uB0QdWn5PUQ==} + hasBin: true + peerDependencies: + '@unocss/eslint-plugin': '>=0.50.0' + eslint: '>=8.40.0' + eslint-plugin-format: '>=0.1.0' + eslint-plugin-react: ^7.33.2 + eslint-plugin-react-hooks: ^4.6.0 + eslint-plugin-react-refresh: ^0.4.4 + eslint-plugin-svelte: ^2.34.1 + svelte-eslint-parser: ^0.33.1 + peerDependenciesMeta: + '@unocss/eslint-plugin': + optional: true + eslint-plugin-format: + optional: true + eslint-plugin-react: + optional: true + eslint-plugin-react-hooks: + optional: true + eslint-plugin-react-refresh: + optional: true + eslint-plugin-svelte: + optional: true + svelte-eslint-parser: + optional: true + dependencies: + '@antfu/eslint-define-config': 1.23.0-2 + '@antfu/install-pkg': 0.3.1 + '@eslint-types/jsdoc': 46.8.2-1 + '@eslint-types/typescript-eslint': 6.21.0 + '@eslint-types/unicorn': 50.0.1 + '@stylistic/eslint-plugin': 1.6.2(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-config-flat-gitignore: 0.1.3 + eslint-merge-processors: 0.1.0(eslint@8.56.0) + eslint-plugin-antfu: 2.1.2(eslint@8.56.0) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.56.0) + eslint-plugin-i: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint@8.56.0) + eslint-plugin-jsdoc: 48.1.0(eslint@8.56.0) + eslint-plugin-jsonc: 2.13.0(eslint@8.56.0) + eslint-plugin-markdown: 3.0.1(eslint@8.56.0) + eslint-plugin-n: 16.6.2(eslint@8.56.0) + eslint-plugin-no-only-tests: 3.1.0 + eslint-plugin-perfectionist: 2.5.0(eslint@8.56.0)(typescript@5.3.3)(vue-eslint-parser@9.4.2) + eslint-plugin-toml: 0.9.2(eslint@8.56.0) + eslint-plugin-unicorn: 50.0.1(eslint@8.56.0) + eslint-plugin-unused-imports: 3.1.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0) + eslint-plugin-vitest: 0.3.22(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(typescript@5.3.3) + eslint-plugin-vue: 9.21.1(eslint@8.56.0) + eslint-plugin-yml: 1.12.2(eslint@8.56.0) + eslint-processor-vue-blocks: 0.1.1(@vue/compiler-sfc@3.4.19)(eslint@8.56.0) + globals: 13.24.0 + jsonc-eslint-parser: 2.4.0 + local-pkg: 0.5.0 + parse-gitignore: 2.0.0 + picocolors: 1.0.0 + prompts: 2.4.2 + toml-eslint-parser: 0.9.3 + vue-eslint-parser: 9.4.2(eslint@8.56.0) + yaml-eslint-parser: 1.2.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@vue/compiler-sfc' + - astro-eslint-parser + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + - svelte + - typescript + - vitest + dev: true + + /@antfu/eslint-define-config@1.23.0-2: + resolution: {integrity: sha512-LvxY21+ZhpuBf/aHeBUtGQhSEfad4PkNKXKvDOSvukaM3XVTfBhwmHX2EKwAsdq5DlfjbT3qqYyMiueBIO5iDQ==} + engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>= 8.6.0'} + dev: true + + /@antfu/install-pkg@0.3.1: + resolution: {integrity: sha512-A3zWY9VeTPnxlMiZtsGHw2lSd3ghwvL8s9RiGOtqvDxhhFfZ781ynsGBa/iUnDJ5zBrmTFQrJDud3TGgRISaxw==} + dependencies: + execa: 8.0.1 + dev: true + + /@babel/code-frame@7.23.5: + resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.23.4 + chalk: 2.4.2 + dev: true + + /@babel/compat-data@7.23.5: + resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/core@7.23.9: + resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.23.6 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) + '@babel/helpers': 7.23.9 + '@babel/parser': 7.23.9 + '@babel/template': 7.23.9 + '@babel/traverse': 7.23.9 + '@babel/types': 7.23.9 + convert-source-map: 2.0.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/generator@7.23.6: + resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.22 + jsesc: 2.5.2 + dev: true + + /@babel/helper-annotate-as-pure@7.22.5: + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: + resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-compilation-targets@7.23.6: + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.23.5 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: true + + /@babel/helper-create-class-features-plugin@7.23.10(@babel/core@7.23.9): + resolution: {integrity: sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-member-expression-to-functions': 7.23.0 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + semver: 6.3.1 + dev: true + + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.9): + resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-annotate-as-pure': 7.22.5 + regexpu-core: 5.3.2 + semver: 6.3.1 + dev: true + + /@babel/helper-define-polyfill-provider@0.5.0(@babel/core@7.23.9): + resolution: {integrity: sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.23.9 + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-member-expression-to-functions@7.23.0: + resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-module-imports@7.22.15: + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + + /@babel/helper-optimise-call-expression@7.22.5: + resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-plugin-utils@7.22.5: + resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.9): + resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-wrap-function': 7.22.20 + dev: true + + /@babel/helper-replace-supers@7.22.20(@babel/core@7.23.9): + resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-member-expression-to-functions': 7.23.0 + '@babel/helper-optimise-call-expression': 7.22.5 + dev: true + + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-skip-transparent-expression-wrappers@7.22.5: + resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@babel/helper-string-parser@7.23.4: + resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-option@7.23.5: + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-wrap-function@7.22.20: + resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.23.0 + '@babel/template': 7.23.9 + '@babel/types': 7.23.9 + dev: true + + /@babel/helpers@7.23.9: + resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.23.9 + '@babel/traverse': 7.23.9 + '@babel/types': 7.23.9 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/highlight@7.23.4: + resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/parser@7.23.9: + resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.9 + + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.9) + dev: true + + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7(@babel/core@7.23.9): + resolution: {integrity: sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.23.9): + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + dev: true + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.9): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.9): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.9): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-async-generator-functions@7.23.9(@babel/core@7.23.9): + resolution: {integrity: sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.9) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-classes@7.23.8(@babel/core@7.23.9): + resolution: {integrity: sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9) + '@babel/helper-split-export-declaration': 7.22.6 + globals: 11.12.0 + dev: true + + /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/template': 7.23.9 + dev: true + + /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.9): + resolution: {integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: true + + /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + dev: true + + /@babel/plugin-transform-modules-systemjs@7.23.9(@babel/core@7.23.9): + resolution: {integrity: sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + dev: true + + /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.9): + resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.23.5 + '@babel/core': 7.23.9 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.23.9): + resolution: {integrity: sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.23.10(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.9) + dev: true + + /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + regenerator-transform: 0.15.2 + dev: true + + /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: true + + /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.9): + resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) + '@babel/helper-plugin-utils': 7.22.5 + dev: true + + /@babel/preset-env@7.23.9(@babel/core@7.23.9): + resolution: {integrity: sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.23.5 + '@babel/core': 7.23.9 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.23.5 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.7(@babel/core@7.23.9) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.9) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.9) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.9) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.9) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.9) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.9) + '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-async-generator-functions': 7.23.9(@babel/core@7.23.9) + '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-block-scoping': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-class-static-block': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-classes': 7.23.8(@babel/core@7.23.9) + '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-dynamic-import': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-export-namespace-from': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-for-of': 7.23.6(@babel/core@7.23.9) + '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-json-strings': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-logical-assignment-operators': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-modules-systemjs': 7.23.9(@babel/core@7.23.9) + '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.23.9) + '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-numeric-separator': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-object-rest-spread': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-optional-catch-binding': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-private-property-in-object': 7.23.4(@babel/core@7.23.9) + '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.23.9) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.9) + babel-plugin-polyfill-corejs2: 0.4.8(@babel/core@7.23.9) + babel-plugin-polyfill-corejs3: 0.9.0(@babel/core@7.23.9) + babel-plugin-polyfill-regenerator: 0.5.5(@babel/core@7.23.9) + core-js-compat: 3.36.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.9): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/types': 7.23.9 + esutils: 2.0.3 + dev: true + + /@babel/regjsgen@0.8.0: + resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + dev: true + + /@babel/runtime@7.23.9: + resolution: {integrity: sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.1 + + /@babel/template@7.23.9: + resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.23.5 + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 + dev: true + + /@babel/traverse@7.23.9: + resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.23.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types@7.23.9: + resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.23.4 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true + + /@colors/colors@1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: true + optional: true + + /@colors/colors@1.6.0: + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + dev: false + + /@commitlint/config-validator@18.6.1: + resolution: {integrity: sha512-05uiToBVfPhepcQWE1ZQBR/Io3+tb3gEotZjnI4tTzzPk16NffN6YABgwFQCLmzZefbDcmwWqJWc2XT47q7Znw==} + engines: {node: '>=v18'} + requiresBuild: true + dependencies: + '@commitlint/types': 18.6.1 + ajv: 8.12.0 + dev: true + optional: true + + /@commitlint/execute-rule@18.6.1: + resolution: {integrity: sha512-7s37a+iWyJiGUeMFF6qBlyZciUkF8odSAnHijbD36YDctLhGKoYltdvuJ/AFfRm6cBLRtRk9cCVPdsEFtt/2rg==} + engines: {node: '>=v18'} + requiresBuild: true + dev: true + optional: true + + /@commitlint/load@18.6.1(@types/node@20.11.18)(typescript@5.3.3): + resolution: {integrity: sha512-p26x8734tSXUHoAw0ERIiHyW4RaI4Bj99D8YgUlVV9SedLf8hlWAfyIFhHRIhfPngLlCe0QYOdRKYFt8gy56TA==} + engines: {node: '>=v18'} + requiresBuild: true + dependencies: + '@commitlint/config-validator': 18.6.1 + '@commitlint/execute-rule': 18.6.1 + '@commitlint/resolve-extends': 18.6.1 + '@commitlint/types': 18.6.1 + chalk: 4.1.2 + cosmiconfig: 8.3.6(typescript@5.3.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.18)(cosmiconfig@8.3.6)(typescript@5.3.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + resolve-from: 5.0.0 + transitivePeerDependencies: + - '@types/node' + - typescript + dev: true + optional: true + + /@commitlint/resolve-extends@18.6.1: + resolution: {integrity: sha512-ifRAQtHwK+Gj3Bxj/5chhc4L2LIc3s30lpsyW67yyjsETR6ctHAHRu1FSpt0KqahK5xESqoJ92v6XxoDRtjwEQ==} + engines: {node: '>=v18'} + requiresBuild: true + dependencies: + '@commitlint/config-validator': 18.6.1 + '@commitlint/types': 18.6.1 + import-fresh: 3.3.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + dev: true + optional: true + + /@commitlint/types@18.6.1: + resolution: {integrity: sha512-gwRLBLra/Dozj2OywopeuHj2ac26gjGkz2cZ+86cTJOdtWfiRRr4+e77ZDAGc6MDWxaWheI+mAV5TLWWRwqrFg==} + engines: {node: '>=v18'} + requiresBuild: true + dependencies: + chalk: 4.1.2 + dev: true + optional: true + + /@compodoc/compodoc@1.1.23(typescript@5.3.3): + resolution: {integrity: sha512-5Zfx+CHKTxLD+TxCGt1U8krnEBCWPVxCLt3jCJEN55AzhTluo8xlMenaXlJsuVqL4Lmo/OTTzEXrm9zoQKh/3w==} + engines: {node: '>= 14.0.0'} + hasBin: true + requiresBuild: true + dependencies: + '@angular-devkit/schematics': 14.2.12(chokidar@3.6.0) + '@babel/core': 7.23.9 + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.23.9) + '@babel/preset-env': 7.23.9(@babel/core@7.23.9) + '@compodoc/live-server': 1.2.3 + '@compodoc/ngd-transformer': 2.1.3 + bootstrap.native: 5.0.11 + chalk: 4.1.2 + cheerio: 1.0.0-rc.12 + chokidar: 3.6.0 + colors: 1.4.0 + commander: 11.1.0 + cosmiconfig: 8.3.6(typescript@5.3.3) + decache: 4.6.2 + es6-shim: 0.35.8 + fancy-log: 2.0.0 + fast-glob: 3.3.2 + fs-extra: 11.2.0 + glob: 10.3.10 + handlebars: 4.7.8 + html-entities: 2.4.0 + i18next: 23.8.2 + json5: 2.2.3 + lodash: 4.17.21 + loglevel: 1.9.1 + loglevel-plugin-prefix: 0.8.4 + lunr: 2.3.9 + marked: 7.0.3 + minimist: 1.2.8 + opencollective-postinstall: 2.0.3 + os-name: 4.0.1 + pdfjs-dist: 2.12.313 + pdfmake: 0.2.9 + prismjs: 1.29.0 + semver: 7.6.0 + svg-pan-zoom: 3.6.1 + tablesort: 5.3.0 + traverse: 0.6.8 + ts-morph: 20.0.0 + uuid: 9.0.1 + vis: 4.21.0-EOL + zepto: 1.2.0 + transitivePeerDependencies: + - supports-color + - typescript + - worker-loader + dev: true + + /@compodoc/live-server@1.2.3: + resolution: {integrity: sha512-hDmntVCyjjaxuJzPzBx68orNZ7TW4BtHWMnXlIVn5dqhK7vuFF/11hspO1cMmc+2QTYgqde1TBcb3127S7Zrow==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + chokidar: 3.6.0 + colors: 1.4.0 + connect: 3.7.0 + cors: 2.8.5 + event-stream: 4.0.1 + faye-websocket: 0.11.4 + http-auth: 4.1.9 + http-auth-connect: 1.0.6 + morgan: 1.10.0 + object-assign: 4.1.1 + open: 8.4.0 + proxy-middleware: 0.15.0 + send: 0.18.0 + serve-index: 1.9.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@compodoc/ngd-core@2.1.1: + resolution: {integrity: sha512-Z+wE6wWZYVnudRYg6qunDlyh3Orw39Ib66Gvrz5kX5u7So+iu3tr6sQJdqH6yGS3hAjig5avlfhWLlgsb6/x1Q==} + engines: {node: '>= 10.0.0'} + dependencies: + ansi-colors: 4.1.3 + fancy-log: 2.0.0 + typescript: 5.3.3 + dev: true + + /@compodoc/ngd-transformer@2.1.3: + resolution: {integrity: sha512-oWxJza7CpWR8/FeWYfE6j+jgncnGBsTWnZLt5rD2GUpsGSQTuGrsFPnmbbaVLgRS5QIVWBJYke7QFBr/7qVMWg==} + engines: {node: '>= 10.0.0'} + dependencies: + '@aduh95/viz.js': 3.4.0 + '@compodoc/ngd-core': 2.1.1 + dot: 2.0.0-beta.1 + fs-extra: 11.2.0 + dev: true + + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + /@dabh/diagnostics@2.0.3: + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + dev: false + + /@es-joy/jsdoccomment@0.42.0: + resolution: {integrity: sha512-R1w57YlVA6+YE01wch3GPYn6bCsrOV3YW/5oGGE2tmX6JcL9Nr+b5IikrjMPF+v9CV3ay+obImEdsDhovhJrzw==} + engines: {node: '>=16'} + dependencies: + comment-parser: 1.4.1 + esquery: 1.5.0 + jsdoc-type-pratt-parser: 4.0.0 + dev: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.56.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint-types/jsdoc@46.8.2-1: + resolution: {integrity: sha512-FwD7V0xX0jyaqj8Ul5ZY+TAAPohDfVqtbuXJNHb+OIv1aTIqZi5+Zn3F2UwQ5O3BnQd2mTduyK0+HjGx3/AMFg==} + dev: true + + /@eslint-types/typescript-eslint@6.21.0: + resolution: {integrity: sha512-ao4TdMLw+zFdAJ9q6iBBxC5GSrJ14Hpv0VKaergr++jRTDaGgoYiAq84tx1FYqUJzQgzJC7dm6s52IAQP7EiHA==} + dev: true + + /@eslint-types/unicorn@50.0.1: + resolution: {integrity: sha512-nuJuipTNcg9f+oxZ+3QZw4tuDLmir4RJOPfM/oujgToiy1s+tePDZhwg5jUGc3q8OzTtPbVpsFSYX7QApjO3EA==} + dev: true + + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.56.0: + resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@fastify/accept-negotiator@1.1.0: + resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} + engines: {node: '>=14'} + dev: false + + /@fastify/ajv-compiler@3.5.0: + resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + fast-uri: 2.3.0 + dev: false + + /@fastify/busboy@1.2.1: + resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==} + engines: {node: '>=14'} + dependencies: + text-decoding: 1.0.0 + dev: false + + /@fastify/cookie@9.3.1: + resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==} + dependencies: + cookie-signature: 1.2.1 + fastify-plugin: 4.5.1 + dev: false + + /@fastify/cors@9.0.1: + resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==} + dependencies: + fastify-plugin: 4.5.1 + mnemonist: 0.39.6 + dev: false + + /@fastify/deepmerge@1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + dev: false + + /@fastify/error@3.4.1: + resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==} + dev: false + + /@fastify/fast-json-stringify-compiler@4.3.0: + resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} + dependencies: + fast-json-stringify: 5.12.0 + dev: false + + /@fastify/formbody@7.4.0: + resolution: {integrity: sha512-H3C6h1GN56/SMrZS8N2vCT2cZr7mIHzBHzOBa5OPpjfB/D6FzP9mMpE02ZzrFX0ANeh0BAJdoXKOF2e7IbV+Og==} + dependencies: + fast-querystring: 1.1.2 + fastify-plugin: 4.5.1 + dev: false + + /@fastify/merge-json-schemas@0.1.1: + resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + dependencies: + fast-deep-equal: 3.1.3 + dev: false + + /@fastify/middie@8.3.0: + resolution: {integrity: sha512-h+zBxCzMlkEkh4fM7pZaSGzqS7P9M0Z6rXnWPdUEPfe7x1BCj++wEk/pQ5jpyYY4pF8AknFqb77n7uwh8HdxEA==} + dependencies: + '@fastify/error': 3.4.1 + fastify-plugin: 4.5.1 + path-to-regexp: 6.2.1 + reusify: 1.0.4 + dev: false + + /@fastify/multipart@8.1.0: + resolution: {integrity: sha512-sRX9X4ZhAqRbe2kDvXY2NK7i6Wf1Rm2g/CjpGYYM7+Np8E6uWQXcj761j08qPfPO8PJXM+vJ7yrKbK1GPB+OeQ==} + dependencies: + '@fastify/busboy': 1.2.1 + '@fastify/deepmerge': 1.3.0 + '@fastify/error': 3.4.1 + fastify-plugin: 4.5.1 + secure-json-parse: 2.7.0 + stream-wormhole: 1.1.0 + dev: false + + /@fastify/send@2.1.0: + resolution: {integrity: sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==} + dependencies: + '@lukeed/ms': 2.0.2 + escape-html: 1.0.3 + fast-decode-uri-component: 1.0.1 + http-errors: 2.0.0 + mime: 3.0.0 + dev: false + + /@fastify/static@7.0.1: + resolution: {integrity: sha512-i1p/nELMknAisNfnjo7yhfoUOdKzA+n92QaMirv2NkZrJ1Wl12v2nyTYlDwPN8XoStMBAnRK/Kx6zKmfrXUPXw==} + dependencies: + '@fastify/accept-negotiator': 1.1.0 + '@fastify/send': 2.1.0 + content-disposition: 0.5.4 + fastify-plugin: 4.5.1 + fastq: 1.17.1 + glob: 10.3.10 + dev: false + + /@foliojs-fork/fontkit@1.9.1: + resolution: {integrity: sha512-U589voc2/ROnvx1CyH9aNzOQWJp127JGU1QAylXGQ7LoEAF6hMmahZLQ4eqAcgHUw+uyW4PjtCItq9qudPkK3A==} + dependencies: + '@foliojs-fork/restructure': 2.0.2 + brfs: 2.0.2 + brotli: 1.3.3 + browserify-optional: 1.0.1 + clone: 1.0.4 + deep-equal: 1.1.2 + dfa: 1.2.0 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + dev: true + + /@foliojs-fork/linebreak@1.1.1: + resolution: {integrity: sha512-pgY/+53GqGQI+mvDiyprvPWgkTlVBS8cxqee03ejm6gKAQNsR1tCYCIvN9FHy7otZajzMqCgPOgC4cHdt4JPig==} + dependencies: + base64-js: 1.3.1 + brfs: 2.0.2 + unicode-trie: 2.0.0 + dev: true + + /@foliojs-fork/pdfkit@0.14.0: + resolution: {integrity: sha512-nMOiQAv6id89MT3tVTCgc7HxD5ZMANwio2o5yvs5sexQkC0KI3BLaLakpsrHmFfeGFAhqPmZATZGbJGXTUebpg==} + dependencies: + '@foliojs-fork/fontkit': 1.9.1 + '@foliojs-fork/linebreak': 1.1.1 + crypto-js: 4.2.0 + png-js: 1.0.0 + dev: true + + /@foliojs-fork/restructure@2.0.2: + resolution: {integrity: sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==} + dev: true + + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 2.0.2 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@2.0.2: + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + dev: true + + /@hutson/parse-repository-url@3.0.2: + resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} + engines: {node: '>=6.9.0'} + dev: true + + /@ioredis/commands@1.2.0: + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/core@29.7.0(ts-node@10.9.2): + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + jest-mock: 29.7.0 + dev: true + + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + dev: true + + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.11.18 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.22 + '@types/node': 20.11.18 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.1 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.22 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + dev: true + + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/transform@29.7.0: + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.23.9 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.22 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.5 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.11.18 + '@types/yargs': 17.0.32 + chalk: 4.1.2 + dev: true + + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.22 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/source-map@0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.22 + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + /@jridgewell/trace-mapping@0.3.22: + resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + /@ljharb/through@2.3.12: + resolution: {integrity: sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + dev: true + + /@lukeed/csprng@1.1.0: + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + /@lukeed/ms@2.0.2: + resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==} + engines: {node: '>=8'} + dev: false + + /@microsoft/tsdoc@0.14.2: + resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + dev: false + + /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2: + resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2: + resolution: {integrity: sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2: + resolution: {integrity: sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2: + resolution: {integrity: sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2: + resolution: {integrity: sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2: + resolution: {integrity: sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@nestjs-modules/mailer@1.10.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(nodemailer@6.9.9): + resolution: {integrity: sha512-k2gs2NH8Ygq4JnETX+EDBXixLAS8DDZEI/Wbr9LGL3HwO3Qz8zVh8dBJ4ESpySuWniW+a8rARzGXtTUHC4KFlw==} + peerDependencies: + '@nestjs/common': '>=7.0.9' + '@nestjs/core': '>=7.0.9' + nodemailer: '>=6.4.6' + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + css-inline: 0.11.2 + glob: 10.3.10 + mjml: 4.14.1 + nodemailer: 6.9.9 + preview-email: 3.0.19 + optionalDependencies: + '@types/ejs': 3.1.5 + '@types/pug': 2.0.10 + ejs: 3.1.9 + handlebars: 4.7.8 + pug: 3.0.2 + transitivePeerDependencies: + - encoding + dev: false + + /@nestjs/axios@3.0.2(@nestjs/common@10.3.3)(axios@1.6.7)(rxjs@7.8.1): + resolution: {integrity: sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + axios: 1.6.7 + rxjs: 7.8.1 + dev: false + + /@nestjs/bull-shared@10.1.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3): + resolution: {integrity: sha512-E1lAvVTCwbtBXySElkVrleXzr1bNuTCOLaQ1GmLSQGGlzXIvrXFXEIS1Dh1JCULICC25b7rGOfD3yL7uKRaMzw==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + tslib: 2.6.2 + dev: false + + /@nestjs/bull@10.1.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(bull@4.12.2): + resolution: {integrity: sha512-JEw4eFCtgECg1A9UGxa8eJtaxjwSk2XPLAG1xahZGnoozAYlDzvO6W6mFpCbKvoBbNSh1p+p+lccUbrbQnUd8w==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + bull: ^3.3 || ^4.0.0 + dependencies: + '@nestjs/bull-shared': 10.1.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3) + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + bull: 4.12.2 + tslib: 2.6.2 + dev: false + + /@nestjs/cache-manager@2.2.1(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(cache-manager@5.4.0)(rxjs@7.8.1): + resolution: {integrity: sha512-mXj0zenuyMPJICokwVud4Kjh0+pzBNBAgfpx3I48LozNkd8Qfv/MAhZsb15GihGpbFRxafUo3p6XvtAqRm8GRw==} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + cache-manager: <=5 + rxjs: ^7.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + cache-manager: 5.4.0 + rxjs: 7.8.1 + dev: false + + /@nestjs/cli@10.3.2: + resolution: {integrity: sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==} + engines: {node: '>= 16.14'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + dependencies: + '@angular-devkit/core': 17.1.2(chokidar@3.6.0) + '@angular-devkit/schematics': 17.1.2(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.1.2(chokidar@3.6.0) + '@nestjs/schematics': 10.1.1(chokidar@3.6.0)(typescript@5.3.3) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.3 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.3.3)(webpack@5.90.1) + glob: 10.3.10 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + rimraf: 4.4.1 + shelljs: 0.8.5 + source-map-support: 0.5.21 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.1.0 + typescript: 5.3.3 + webpack: 5.90.1 + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - esbuild + - uglify-js + - webpack-cli + dev: true + + /@nestjs/common@10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1): + resolution: {integrity: sha512-LAkTe8/CF0uNWM0ecuDwUNTHCi1lVSITmmR4FQ6Ftz1E7ujQCnJ5pMRzd8JRN14vdBkxZZ8VbVF0BDUKoKNxMQ==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 + iterare: 1.2.1 + reflect-metadata: 0.2.1 + rxjs: 7.8.1 + tslib: 2.6.2 + uid: 2.0.2 + + /@nestjs/config@3.2.0(@nestjs/common@10.3.3)(rxjs@7.8.1): + resolution: {integrity: sha512-BpYRn57shg7CH35KGT6h+hT7ZucB6Qn2B3NBNdvhD4ApU8huS5pX/Wc2e/aO5trIha606Bz2a9t9/vbiuTBTww==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + dotenv: 16.4.1 + dotenv-expand: 10.0.0 + lodash: 4.17.21 + rxjs: 7.8.1 + uuid: 9.0.1 + dev: false + + /@nestjs/core@10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1): + resolution: {integrity: sha512-kxJWggQAPX3RuZx9JVec69eSLaYLNIox2emkZJpfBJ5Qq7cAq7edQIt1r4LGjTKq6kFubNTPsqhWf5y7yFRBPw==} + requiresBuild: true + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/websockets': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-socket.io@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nuxtjs/opencollective': 0.3.2 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 3.2.0 + reflect-metadata: 0.2.1 + rxjs: 7.8.1 + tslib: 2.6.2 + uid: 2.0.2 + transitivePeerDependencies: + - encoding + + /@nestjs/event-emitter@2.0.4(@nestjs/common@10.3.3)(@nestjs/core@10.3.3): + resolution: {integrity: sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + eventemitter2: 6.4.9 + dev: false + + /@nestjs/jwt@10.2.0(@nestjs/common@10.3.3): + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + dev: false + + /@nestjs/mapped-types@2.0.5(@nestjs/common@10.3.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1): + resolution: {integrity: sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + class-transformer: 0.5.1 + class-validator: 0.14.1 + reflect-metadata: 0.2.1 + dev: false + + /@nestjs/passport@10.0.3(@nestjs/common@10.3.3)(passport@0.7.0): + resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + passport: 0.7.0 + dev: false + + /@nestjs/platform-fastify@10.3.3(@fastify/static@7.0.1)(@nestjs/common@10.3.3)(@nestjs/core@10.3.3): + resolution: {integrity: sha512-OTKcKGnWWrSk/nDl5bFmv2gcPhbF6nsU/EHxkh6tguc0YY4aopQR9GaodseJn8isEOtZzcx8UUBsnLTtqWKxaA==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@fastify/view': ^7.0.0 || ^8.0.0 + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + '@fastify/view': + optional: true + dependencies: + '@fastify/cors': 9.0.1 + '@fastify/formbody': 7.4.0 + '@fastify/middie': 8.3.0 + '@fastify/static': 7.0.1 + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + fastify: 4.26.0 + light-my-request: 5.11.0 + path-to-regexp: 3.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@nestjs/platform-socket.io@10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(rxjs@7.8.1): + resolution: {integrity: sha512-QqM9BMTdYPvXOqx3oWrv130HOtc2krPvfgqgDsPWkBLfR+TssrA5QDaTW8HSjEQAfmugvHwhEAAU4+yXRl6tKg==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + rxjs: ^7.1.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/websockets': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-socket.io@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + rxjs: 7.8.1 + socket.io: 4.7.4 + tslib: 2.6.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + /@nestjs/schedule@4.0.1(@nestjs/common@10.3.3)(@nestjs/core@10.3.3): + resolution: {integrity: sha512-cz2FNjsuoma+aGsG0cMmG6Dqg/BezbBWet1UTHtAuu6d2mXNTVcmoEQM2DIVG5Lfwb2hfSE2yZt8Moww+7y+mA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + cron: 3.1.6 + uuid: 9.0.1 + dev: false + + /@nestjs/schematics@10.1.1(chokidar@3.6.0)(typescript@5.3.3): + resolution: {integrity: sha512-o4lfCnEeIkfJhGBbLZxTuVWcGuqDCFwg5OrvpgRUBM7vI/vONvKKiB5riVNpO+JqXoH0I42NNeDb0m4V5RREig==} + peerDependencies: + typescript: '>=4.8.2' + dependencies: + '@angular-devkit/core': 17.1.2(chokidar@3.6.0) + '@angular-devkit/schematics': 17.1.2(chokidar@3.6.0) + comment-json: 4.2.3 + jsonc-parser: 3.2.1 + pluralize: 8.0.0 + typescript: 5.3.3 + transitivePeerDependencies: + - chokidar + dev: true + + /@nestjs/swagger@7.3.0(@fastify/static@7.0.1)(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1): + resolution: {integrity: sha512-zLkfKZ+ioYsIZ3dfv7Bj8YHnZMNAGWFUmx2ZDuLp/fBE4P8BSjB7hldzDueFXsmwaPL90v7lgyd82P+s7KME1Q==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + dependencies: + '@fastify/static': 7.0.1 + '@microsoft/tsdoc': 0.14.2 + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.3.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1) + class-transformer: 0.5.1 + class-validator: 0.14.1 + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 3.2.0 + reflect-metadata: 0.2.1 + swagger-ui-dist: 5.11.2 + dev: false + + /@nestjs/terminus@10.2.2(@nestjs/axios@3.0.2)(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/typeorm@10.0.2)(reflect-metadata@0.2.1)(rxjs@7.8.1)(typeorm@0.3.17): + resolution: {integrity: sha512-tZdTSqgHyxekN8PJmJJ1ptZG97q/1nBIBwLdMcmB7Dsz4XDTQvYuhs20F1qkEgFuQwarNkb/2AF5Qib31g2bmA==} + peerDependencies: + '@grpc/grpc-js': '*' + '@grpc/proto-loader': '*' + '@mikro-orm/core': '*' + '@mikro-orm/nestjs': '*' + '@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + '@nestjs/microservices': ^9.0.0 || ^10.0.0 + '@nestjs/mongoose': ^9.0.0 || ^10.0.0 + '@nestjs/sequelize': ^9.0.0 || ^10.0.0 + '@nestjs/typeorm': ^9.0.0 || ^10.0.0 + '@prisma/client': '*' + mongoose: '*' + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: '*' + typeorm: '*' + peerDependenciesMeta: + '@grpc/grpc-js': + optional: true + '@grpc/proto-loader': + optional: true + '@mikro-orm/core': + optional: true + '@mikro-orm/nestjs': + optional: true + '@nestjs/axios': + optional: true + '@nestjs/microservices': + optional: true + '@nestjs/mongoose': + optional: true + '@nestjs/sequelize': + optional: true + '@nestjs/typeorm': + optional: true + '@prisma/client': + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + dependencies: + '@nestjs/axios': 3.0.2(@nestjs/common@10.3.3)(axios@1.6.7)(rxjs@7.8.1) + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/typeorm': 10.0.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1)(typeorm@0.3.17) + boxen: 5.1.2 + check-disk-space: 3.4.0 + reflect-metadata: 0.2.1 + rxjs: 7.8.1 + typeorm: 0.3.17(ioredis@5.3.2)(mysql2@3.9.1)(ts-node@10.9.2) + dev: false + + /@nestjs/testing@10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3): + resolution: {integrity: sha512-kX20GfjAImL5grd/i69uD/x7sc00BaqGcP2dRG3ilqshQUuy5DOmspLCr3a2C8xmVU7kzK4spT0oTxhe6WcCAA==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + tslib: 2.6.2 + dev: true + + /@nestjs/throttler@5.1.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.2.1): + resolution: {integrity: sha512-60MqhSLYUqWOgc38P6C6f76JIpf6mVjly7gpuPBCKtVd0p5e8Fq855j7bJuO4/v25vgaOo1OdVs0U1qtgYioGw==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + reflect-metadata: 0.2.1 + dev: false + + /@nestjs/typeorm@10.0.2(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1)(typeorm@0.3.17): + resolution: {integrity: sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + typeorm: ^0.3.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + reflect-metadata: 0.2.1 + rxjs: 7.8.1 + typeorm: 0.3.17(ioredis@5.3.2)(mysql2@3.9.1)(ts-node@10.9.2) + uuid: 9.0.1 + dev: false + + /@nestjs/websockets@10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-socket.io@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1): + resolution: {integrity: sha512-cR5cB0bLS87vd0iu7Nud/4x2EH1Vs0aIgwGWd0eH/5SAw0rrDNU81PiOde+rnMXETbxvSVfOZuLRyn7/WQtGUg==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/platform-socket.io': ^10.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/platform-socket.io': + optional: true + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/platform-socket.io': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(rxjs@7.8.1) + iterare: 1.2.1 + object-hash: 3.0.0 + reflect-metadata: 0.2.1 + rxjs: 7.8.1 + tslib: 2.6.2 + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + + /@nuxtjs/opencollective@0.3.2: + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + consola: 2.15.3 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + /@one-ini/wasm@0.1.1: + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + dev: false + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + optional: true + + /@selderee/plugin-htmlparser2@0.11.0: + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + dev: false + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + dependencies: + '@sinonjs/commons': 3.0.1 + dev: true + + /@socket.io/component-emitter@3.1.0: + resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} + + /@socket.io/redis-adapter@8.2.1(socket.io-adapter@2.5.2): + resolution: {integrity: sha512-6Dt7EZgGSBP0qvXeOKGx7NnSr2tPMbVDfDyL97zerZo+v69hMfL99skMCL3RKZlWVqLyRme2T0wcy3udHhtOsg==} + engines: {node: '>=10.0.0'} + peerDependencies: + socket.io-adapter: ^2.4.0 + dependencies: + debug: 4.3.4 + notepack.io: 3.0.1 + socket.io-adapter: 2.5.2 + uid2: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@socket.io/redis-emitter@5.1.0: + resolution: {integrity: sha512-QQUFPBq6JX7JIuM/X1811ymKlAfwufnQ8w6G2/59Jaqp09hdF1GJ/+e8eo/XdcmT0TqkvcSa2TT98ggTXa5QYw==} + dependencies: + debug: 4.3.4 + notepack.io: 3.0.1 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - supports-color + dev: false + + /@songkeys/nestjs-redis@10.0.0(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(ioredis@5.3.2): + resolution: {integrity: sha512-s56+NECuJXzcaPLYzpvA2xjL0e/1Zy55UE0q6b1UqpbQSKI06TFPFCWCMUadJigiuB26O1hxi+lmDbzahKvcLg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + ioredis: ^5.0.0 + dependencies: + '@nestjs/common': 10.3.3(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.1)(rxjs@7.8.1) + '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/websockets@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1) + ioredis: 5.3.2 + tslib: 2.6.0 + dev: false + + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: false + + /@stylistic/eslint-plugin-js@1.6.2(eslint@8.56.0): + resolution: {integrity: sha512-ndT6X2KgWGxv8101pdMOxL8pihlYIHcOv3ICd70cgaJ9exwkPn8hJj4YQwslxoAlre1TFHnXd/G1/hYXgDrjIA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@types/eslint': 8.56.2 + acorn: 8.11.3 + escape-string-regexp: 4.0.0 + eslint: 8.56.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + dev: true + + /@stylistic/eslint-plugin-jsx@1.6.2(eslint@8.56.0): + resolution: {integrity: sha512-hbbouazSJbHD/fshBIOLh9JgtSphKNoTCfHLSNBjAkXLK+GR4i2jhEZZF9P0mtXrNuy2WWInmpq/g0pfWBmSBA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@stylistic/eslint-plugin-js': 1.6.2(eslint@8.56.0) + '@types/eslint': 8.56.2 + eslint: 8.56.0 + estraverse: 5.3.0 + picomatch: 4.0.1 + dev: true + + /@stylistic/eslint-plugin-plus@1.6.2(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-EDMwa6gzKw4bXRqdIAUvZDfIgwotbjJs8o+vYE22chAYtVAnA0Pcq+cPx0Uk35t2gvJWb5OaLDjqA6oy1tD0jg==} + peerDependencies: + eslint: '*' + dependencies: + '@types/eslint': 8.56.2 + '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@stylistic/eslint-plugin-ts@1.6.2(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-FizV58em0OjO/xFHRIy/LJJVqzxCNmYC/xVtKDf8aGDRgZpLo+lkaBKfBrbMkAGzhBKbYj+iLEFI4WEl6aVZGQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@stylistic/eslint-plugin-js': 1.6.2(eslint@8.56.0) + '@types/eslint': 8.56.2 + '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@stylistic/eslint-plugin@1.6.2(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-EFnVcKOE5HTiMlVwisL9hHjz8a69yBbJRscWF/z+/vl6M4ew8NVrBlY8ea7KdV8QtyCY4Yapmsbg5ZDfhWlEgg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: '>=8.40.0' + dependencies: + '@stylistic/eslint-plugin-js': 1.6.2(eslint@8.56.0) + '@stylistic/eslint-plugin-jsx': 1.6.2(eslint@8.56.0) + '@stylistic/eslint-plugin-plus': 1.6.2(eslint@8.56.0)(typescript@5.3.3) + '@stylistic/eslint-plugin-ts': 1.6.2(eslint@8.56.0)(typescript@5.3.3) + '@types/eslint': 8.56.2 + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@thednp/event-listener@2.0.4: + resolution: {integrity: sha512-sc4B7AzYAIvnGnivirq0XyR7LfzEDhGiiB70Q0qdNn8wSJ2pL1buVAsEZxrlc47qRJiBV4YIP+BFkyMm2r3NLg==} + engines: {node: '>=16', pnpm: '>=8.6.0'} + dev: true + + /@thednp/shorty@2.0.0: + resolution: {integrity: sha512-kwtLivCxYIoFfGIVU4NlZtfdA/zxZ6X8UcWaJrb7XqU3WQ4Q1p5IaZlLBfOVAO06WH5oWE87QUdK/dS56Wnfjg==} + engines: {node: '>=16', pnpm: '>=8.6.0'} + dev: true + + /@ts-morph/common@0.21.0: + resolution: {integrity: sha512-ES110Mmne5Vi4ypUKrtVQfXFDtCsDXiUiGxF6ILVlE90dDD4fdpC1LSjydl/ml7xJWKSDZwUYD2zkOePMSrPBA==} + dependencies: + fast-glob: 3.3.2 + minimatch: 7.4.6 + mkdirp: 2.1.6 + path-browserify: 1.0.1 + dev: true + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + /@types/babel__core@7.20.5: + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + dependencies: + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.5 + dev: true + + /@types/babel__generator@7.6.8: + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@types/babel__template@7.4.4: + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + dependencies: + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 + dev: true + + /@types/babel__traverse@7.20.5: + resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + dependencies: + '@babel/types': 7.23.9 + dev: true + + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.11.18 + dev: true + + /@types/cache-manager@4.0.6: + resolution: {integrity: sha512-8qL93MF05/xrzFm/LSPtzNEOE1eQF3VwGHAcQEylgp5hDSTe41jtFwbSYAPfyYcVa28y1vYSjIt0c1fLLUiC/Q==} + dev: true + + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + dependencies: + '@types/node': 20.11.18 + dev: true + + /@types/cookie@0.4.1: + resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + + /@types/cookiejar@2.1.5: + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + dev: true + + /@types/cors@2.8.17: + resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} + dependencies: + '@types/node': 20.11.18 + + /@types/ejs@3.1.5: + resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} + dev: false + optional: true + + /@types/eslint-scope@3.7.7: + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + dependencies: + '@types/eslint': 8.56.2 + '@types/estree': 1.0.5 + dev: true + + /@types/eslint@8.56.2: + resolution: {integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==} + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/express-serve-static-core@4.17.43: + resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} + dependencies: + '@types/node': 20.11.18 + '@types/qs': 6.9.11 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.43 + '@types/qs': 6.9.11 + '@types/serve-static': 1.15.5 + dev: true + + /@types/graceful-fs@4.1.9: + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + dependencies: + '@types/node': 20.11.18 + dev: true + + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true + + /@types/istanbul-lib-coverage@2.0.6: + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + dev: true + + /@types/istanbul-lib-report@3.0.3: + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + dev: true + + /@types/istanbul-reports@3.0.4: + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + dependencies: + '@types/istanbul-lib-report': 3.0.3 + dev: true + + /@types/jest@29.5.12: + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true + + /@types/jsonwebtoken@9.0.5: + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + dependencies: + '@types/node': 20.11.18 + dev: false + + /@types/lodash@4.14.202: + resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + dev: false + + /@types/luxon@3.3.8: + resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==} + dev: false + + /@types/mdast@3.0.15: + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + dependencies: + '@types/unist': 2.0.10 + dev: true + + /@types/methods@1.1.4: + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + dev: true + + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true + + /@types/mime@3.0.4: + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + dev: true + + /@types/minimist@1.2.5: + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + dev: true + + /@types/multer@1.4.11: + resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==} + dependencies: + '@types/express': 4.17.21 + dev: true + + /@types/node@20.11.18: + resolution: {integrity: sha512-ABT5VWnnYneSBcNWYSCuR05M826RoMyMSGiFivXGx6ZUIsXb9vn4643IEwkg2zbEOSgAiSogtapN2fgc4mAPlw==} + dependencies: + undici-types: 5.26.5 + + /@types/normalize-package-data@2.4.4: + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + dev: true + + /@types/pug@2.0.10: + resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + dev: false + optional: true + + /@types/qs@6.9.11: + resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==} + dev: true + + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true + + /@types/semver@7.5.7: + resolution: {integrity: sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==} + dev: true + + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.11.18 + dev: true + + /@types/serve-static@1.15.5: + resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + dependencies: + '@types/http-errors': 2.0.4 + '@types/mime': 3.0.4 + '@types/node': 20.11.18 + dev: true + + /@types/stack-utils@2.0.3: + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + dev: true + + /@types/superagent@8.1.3: + resolution: {integrity: sha512-R/CfN6w2XsixLb1Ii8INfn+BT9sGPvw74OavfkW4SwY+jeUcAwLZv2+bXLJkndnimxjEBm0RPHgcjW9pLCa8cw==} + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 20.11.18 + dev: true + + /@types/supertest@6.0.2: + resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.3 + dev: true + + /@types/triple-beam@1.3.5: + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + dev: false + + /@types/ua-parser-js@0.7.39: + resolution: {integrity: sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==} + dev: true + + /@types/unist@2.0.10: + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + dev: true + + /@types/validator@13.11.9: + resolution: {integrity: sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==} + + /@types/yargs-parser@21.0.3: + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + dev: true + + /@types/yargs@17.0.32: + resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + dependencies: + '@types/yargs-parser': 21.0.3 + dev: true + + /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.4 + eslint: 8.56.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.2.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.4 + eslint: 8.56.0 + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@6.21.0: + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + dev: true + + /@typescript-eslint/type-utils@6.21.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + debug: 4.3.4 + eslint: 8.56.0 + ts-api-utils: 1.2.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@6.21.0: + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3): + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.0 + ts-api-utils: 1.2.1(typescript@5.3.3) + typescript: 5.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@6.21.0(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.7 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) + eslint: 8.56.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@6.21.0: + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + + /@vue/compiler-core@3.4.19: + resolution: {integrity: sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==} + dependencies: + '@babel/parser': 7.23.9 + '@vue/shared': 3.4.19 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + dev: true + + /@vue/compiler-dom@3.4.19: + resolution: {integrity: sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==} + dependencies: + '@vue/compiler-core': 3.4.19 + '@vue/shared': 3.4.19 + dev: true + + /@vue/compiler-sfc@3.4.19: + resolution: {integrity: sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==} + dependencies: + '@babel/parser': 7.23.9 + '@vue/compiler-core': 3.4.19 + '@vue/compiler-dom': 3.4.19 + '@vue/compiler-ssr': 3.4.19 + '@vue/shared': 3.4.19 + estree-walker: 2.0.2 + magic-string: 0.30.7 + postcss: 8.4.35 + source-map-js: 1.0.2 + dev: true + + /@vue/compiler-ssr@3.4.19: + resolution: {integrity: sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==} + dependencies: + '@vue/compiler-dom': 3.4.19 + '@vue/shared': 3.4.19 + dev: true + + /@vue/shared@3.4.19: + resolution: {integrity: sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==} + dev: true + + /@webassemblyjs/ast@1.11.6: + resolution: {integrity: sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==} + dependencies: + '@webassemblyjs/helper-numbers': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + dev: true + + /@webassemblyjs/floating-point-hex-parser@1.11.6: + resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + dev: true + + /@webassemblyjs/helper-api-error@1.11.6: + resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + dev: true + + /@webassemblyjs/helper-buffer@1.11.6: + resolution: {integrity: sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==} + dev: true + + /@webassemblyjs/helper-numbers@1.11.6: + resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.11.6 + '@webassemblyjs/helper-api-error': 1.11.6 + '@xtuc/long': 4.2.2 + dev: true + + /@webassemblyjs/helper-wasm-bytecode@1.11.6: + resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + dev: true + + /@webassemblyjs/helper-wasm-section@1.11.6: + resolution: {integrity: sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==} + dependencies: + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/helper-buffer': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/wasm-gen': 1.11.6 + dev: true + + /@webassemblyjs/ieee754@1.11.6: + resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + dependencies: + '@xtuc/ieee754': 1.2.0 + dev: true + + /@webassemblyjs/leb128@1.11.6: + resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + dependencies: + '@xtuc/long': 4.2.2 + dev: true + + /@webassemblyjs/utf8@1.11.6: + resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + dev: true + + /@webassemblyjs/wasm-edit@1.11.6: + resolution: {integrity: sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==} + dependencies: + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/helper-buffer': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.11.6 + '@webassemblyjs/wasm-gen': 1.11.6 + '@webassemblyjs/wasm-opt': 1.11.6 + '@webassemblyjs/wasm-parser': 1.11.6 + '@webassemblyjs/wast-printer': 1.11.6 + dev: true + + /@webassemblyjs/wasm-gen@1.11.6: + resolution: {integrity: sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==} + dependencies: + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + dev: true + + /@webassemblyjs/wasm-opt@1.11.6: + resolution: {integrity: sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==} + dependencies: + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/helper-buffer': 1.11.6 + '@webassemblyjs/wasm-gen': 1.11.6 + '@webassemblyjs/wasm-parser': 1.11.6 + dev: true + + /@webassemblyjs/wasm-parser@1.11.6: + resolution: {integrity: sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==} + dependencies: + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + dev: true + + /@webassemblyjs/wast-printer@1.11.6: + resolution: {integrity: sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==} + dependencies: + '@webassemblyjs/ast': 1.11.6 + '@xtuc/long': 4.2.2 + dev: true + + /@xtuc/ieee754@1.2.0: + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + dev: true + + /@xtuc/long@4.2.2: + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + dev: true + + /JSONStream@1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + dev: true + + /abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: false + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + + /abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: false + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + /acorn-import-assertions@1.9.0(acorn@8.11.3): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.11.3 + dev: true + + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.11.3 + dev: true + + /acorn-node@1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + xtend: 4.0.2 + dev: true + + /acorn-walk@7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + + /acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + /add-stream@1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + dev: true + + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + + /ajv-formats@2.1.1(ajv@8.11.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.11.0 + dev: true + + /ajv-formats@2.1.1(ajv@8.12.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + + /ajv-keywords@3.5.2(ajv@6.12.6): + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + dependencies: + ajv: 6.12.6 + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ajv@8.11.0: + resolution: {integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: true + + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + /alce@1.2.0: + resolution: {integrity: sha512-XppPf2S42nO2WhvKzlwzlfcApcXHzjlod30pKmcWjRgLOtqoe5DMuqdiYoM6AgyXksc6A6pV4v1L/WW217e57w==} + engines: {node: '>=0.8.0'} + dependencies: + esprima: 1.2.5 + estraverse: 1.9.3 + dev: false + + /amdefine@1.0.1: + resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} + engines: {node: '>=0.4.2'} + requiresBuild: true + dev: true + optional: true + + /ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + dependencies: + string-width: 4.2.3 + dev: false + + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + /ansi-escapes@3.2.0: + resolution: {integrity: sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==} + engines: {node: '>=4'} + dev: true + + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + + /ansi-escapes@6.2.0: + resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} + engines: {node: '>=14.16'} + dependencies: + type-fest: 3.13.1 + dev: true + + /ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + dev: true + + /ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /apache-crypt@1.2.6: + resolution: {integrity: sha512-072WetlM4blL8PREJVeY+WHiUh1R5VNt2HfceGS8aKqttPHcmqE5pkKuXPz/ULmJOFkc8Hw3kfKl6vy7Qka6DA==} + engines: {node: '>=8'} + dependencies: + unix-crypt-td-js: 1.1.4 + dev: true + + /apache-md5@1.1.8: + resolution: {integrity: sha512-FCAJojipPn0bXjuEpjOOOMN8FZDkxfWWp4JGN9mifU2IhxvKyXZYqpzPHdnTSUpmPDy+tsslB6Z1g+Vg6nVbYA==} + engines: {node: '>=8'} + dev: true + + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: false + + /archy@1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + dev: false + + /are-docs-informative@0.0.2: + resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} + engines: {node: '>=14'} + dev: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + /array-from@2.1.1: + resolution: {integrity: sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==} + dev: true + + /array-ify@1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + dev: true + + /array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + /assert-never@1.2.1: + resolution: {integrity: sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==} + requiresBuild: true + dev: false + + /ast-transform@0.0.0: + resolution: {integrity: sha512-e/JfLiSoakfmL4wmTGPjv0HpTICVmxwXgYOB8x+mzozHL8v+dSfCbrJ8J8hJ0YBP0XcYu1aLZ6b/3TnxNK3P2A==} + dependencies: + escodegen: 1.2.0 + esprima: 1.0.4 + through: 2.3.8 + dev: true + + /ast-types@0.7.8: + resolution: {integrity: sha512-RIOpVnVlltB6PcBJ5BMLx+H+6JJ/zjDGU0t7f0L6c2M1dqcK92VQopLBlPQ9R80AVXelfqYgjcPLtHtDbNFg0Q==} + engines: {node: '>= 0.6'} + dev: true + + /async@3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + /at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + dev: true + + /atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + dev: false + + /avvio@8.3.0: + resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==} + dependencies: + '@fastify/error': 3.4.1 + archy: 1.0.0 + debug: 4.3.4 + fastq: 1.17.1 + transitivePeerDependencies: + - supports-color + dev: false + + /axios@1.6.7: + resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} + dependencies: + follow-redirects: 1.15.5 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /babel-jest@29.7.0(@babel/core@7.23.9): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.23.9 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.23.9) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.23.9 + '@babel/types': 7.23.9 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.5 + dev: true + + /babel-plugin-polyfill-corejs2@0.4.8(@babel/core@7.23.9): + resolution: {integrity: sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.23.5 + '@babel/core': 7.23.9 + '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-corejs3@0.9.0(@babel/core@7.23.9): + resolution: {integrity: sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9) + core-js-compat: 3.36.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-regenerator@0.5.5(@babel/core@7.23.9): + resolution: {integrity: sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9) + transitivePeerDependencies: + - supports-color + dev: true + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.9): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.9) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.9) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.9) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.23.9): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.9 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.9) + dev: true + + /babel-walk@3.0.0-canary-5: + resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + '@babel/types': 7.23.9 + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.3.1: + resolution: {integrity: sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==} + dev: true + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + /base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + + /base64url@3.0.1: + resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} + engines: {node: '>=6.0.0'} + dev: false + + /basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + dependencies: + safe-buffer: 5.1.2 + dev: true + + /batch@0.6.1: + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + dev: true + + /bcryptjs@2.4.3: + resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + dev: true + + /before@0.0.1: + resolution: {integrity: sha512-1J5SWbkoVJH9DTALN8igB4p+nPKZzPrJ/HomqBDLpfUvDXCdjdBmBUcH5McZfur0lftVssVU6BZug5NYh87zTw==} + dev: false + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + + /block-stream2@2.1.0: + resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} + dependencies: + readable-stream: 3.6.2 + dev: false + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + /bootstrap.native@5.0.11: + resolution: {integrity: sha512-bk2i4sQcQk2KuCTs1yygTa+JGjZOpKzIZ/It6TZZOO/Q+PmVGuKuIbrznXF64BUFxXaPNy7gO9LnE7vjGdauSQ==} + engines: {node: '>=16', pnpm: '>=8.6.0'} + dependencies: + '@thednp/event-listener': 2.0.4 + '@thednp/shorty': 2.0.0 + dev: true + + /boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /brfs@2.0.2: + resolution: {integrity: sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==} + hasBin: true + dependencies: + quote-stream: 1.0.2 + resolve: 1.22.8 + static-module: 3.0.4 + through2: 2.0.5 + dev: true + + /brotli@1.3.3: + resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + dependencies: + base64-js: 1.5.1 + dev: true + + /browser-resolve@1.11.3: + resolution: {integrity: sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==} + dependencies: + resolve: 1.1.7 + dev: true + + /browserify-optional@1.0.1: + resolution: {integrity: sha512-VrhjbZ+Ba5mDiSYEuPelekQMfTbhcA2DhLk2VQWqdcCROWeFqlTcXZ7yfRkXCIl8E+g4gINJYJiRB7WEtfomAQ==} + dependencies: + ast-transform: 0.0.0 + ast-types: 0.7.8 + browser-resolve: 1.11.3 + dev: true + + /browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001587 + electron-to-chromium: 1.4.670 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.23.0) + dev: true + + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 + dev: true + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + + /buffer-equal@0.0.1: + resolution: {integrity: sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==} + engines: {node: '>=0.4.0'} + dev: true + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + + /builtins@5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.6.0 + dev: true + + /bull@4.12.2: + resolution: {integrity: sha512-WPuc0VCYx+cIVMiZtPwRpWyyJFBrj4/OgKJ6n9Jf4tIw7rQNV+HAKQv15UDkcTvfpGFehvod7Fd1YztbYSJIDQ==} + engines: {node: '>=12'} + dependencies: + cron-parser: 4.9.0 + get-port: 5.1.1 + ioredis: 5.3.2 + lodash: 4.17.21 + msgpackr: 1.10.1 + semver: 7.6.0 + uuid: 8.3.2 + transitivePeerDependencies: + - supports-color + dev: false + + /cache-manager-ioredis-yet@1.2.2: + resolution: {integrity: sha512-o03N/tQxfFONZ1XLGgIxOFHuQQpjpRdnSAL1THG1YWZIVp1JMUfjU3ElSAjFN1LjbJXa55IpC8waG+VEoLUCUw==} + engines: {node: '>= 16.17.0'} + dependencies: + cache-manager: 5.4.0 + ioredis: 5.3.2 + transitivePeerDependencies: + - supports-color + dev: false + + /cache-manager@5.4.0: + resolution: {integrity: sha512-FS7o8vqJosnLpu9rh2gQTo8EOzCRJLF1BJ4XDEUDMqcfvs7SJZs5iuoFTXLauzQ3S5v8sBAST1pCwMaurpyi1A==} + dependencies: + lodash.clonedeep: 4.5.0 + lru-cache: 10.2.0 + promise-coalesce: 1.1.2 + dev: false + + /cachedir@2.3.0: + resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} + engines: {node: '>=6'} + dev: true + + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + 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 + + /callsite@1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + dev: true + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camel-case@3.0.0: + resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} + dependencies: + no-case: 2.3.2 + upper-case: 1.1.3 + dev: false + + /camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + /caniuse-lite@1.0.30001587: + resolution: {integrity: sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA==} + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true + + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: true + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: true + + /character-parser@2.2.0: + resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} + requiresBuild: true + dependencies: + is-regex: 1.1.4 + dev: false + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: true + + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + dev: false + + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + htmlparser2: 8.0.2 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + /chrome-trace-event@1.0.3: + resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} + engines: {node: '>=6.0'} + dev: true + + /ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + /ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + engines: {node: '>=8'} + dev: true + + /cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + dev: true + + /class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + /class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + dependencies: + '@types/validator': 13.11.9 + libphonenumber-js: 1.10.56 + validator: 13.11.0 + + /clean-css@4.2.4: + resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==} + engines: {node: '>= 4.0'} + dependencies: + source-map: 0.6.1 + dev: false + + /clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + + /cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + dev: false + + /cli-cursor@2.1.0: + resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + engines: {node: '>=4'} + dependencies: + restore-cursor: 2.0.0 + dev: true + + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + restore-cursor: 4.0.0 + dev: true + + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + dev: false + + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: true + + /cli-table3@0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + dev: true + + /cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + dependencies: + slice-ansi: 5.0.0 + string-width: 7.1.0 + dev: true + + /cli-width@2.2.1: + resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} + dev: true + + /cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: true + + /cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + dev: true + + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + dev: false + + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true + + /code-block-writer@12.0.0: + resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} + dev: true + + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: true + + /color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + dev: false + + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true + + /colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + dev: true + + /colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + dev: false + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + + /commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + dev: false + + /commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + dev: true + + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: false + + /comment-json@4.2.3: + resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} + engines: {node: '>= 6'} + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + dev: true + + /comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + dev: true + + /commitizen@4.3.0(@types/node@20.11.18)(typescript@5.3.3): + resolution: {integrity: sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==} + engines: {node: '>= 12'} + hasBin: true + dependencies: + cachedir: 2.3.0 + cz-conventional-changelog: 3.3.0(@types/node@20.11.18)(typescript@5.3.3) + dedent: 0.7.0 + detect-indent: 6.1.0 + find-node-modules: 2.1.3 + find-root: 1.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + inquirer: 8.2.5 + is-utf8: 0.2.1 + lodash: 4.17.21 + minimist: 1.2.7 + strip-bom: 4.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@types/node' + - typescript + dev: true + + /compare-func@2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + dev: true + + /component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + dev: true + + /concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + dev: true + + /config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: false + + /connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + + /constantinople@4.0.1: + resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} + requiresBuild: true + dependencies: + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 + dev: false + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /conventional-changelog-angular@5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + dev: true + + /conventional-changelog-atom@2.0.8: + resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-codemirror@2.0.8: + resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-config-spec@2.1.0: + resolution: {integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==} + dev: true + + /conventional-changelog-conventionalcommits@4.6.3: + resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + lodash: 4.17.21 + q: 1.5.1 + dev: true + + /conventional-changelog-core@4.2.4: + resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} + engines: {node: '>=10'} + dependencies: + add-stream: 1.0.0 + conventional-changelog-writer: 5.0.1 + conventional-commits-parser: 3.2.4 + dateformat: 3.0.3 + get-pkg-repo: 4.2.1 + git-raw-commits: 2.0.11 + git-remote-origin-url: 2.0.0 + git-semver-tags: 4.1.1 + lodash: 4.17.21 + normalize-package-data: 3.0.3 + q: 1.5.1 + read-pkg: 3.0.0 + read-pkg-up: 3.0.0 + through2: 4.0.2 + dev: true + + /conventional-changelog-ember@2.0.9: + resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-eslint@3.0.9: + resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-express@2.0.6: + resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-jquery@3.0.11: + resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-jshint@2.0.9: + resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + dev: true + + /conventional-changelog-preset-loader@2.3.4: + resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} + engines: {node: '>=10'} + dev: true + + /conventional-changelog-writer@5.0.1: + resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + conventional-commits-filter: 2.0.7 + dateformat: 3.0.3 + handlebars: 4.7.8 + json-stringify-safe: 5.0.1 + lodash: 4.17.21 + meow: 8.1.2 + semver: 6.3.1 + split: 1.0.1 + through2: 4.0.2 + dev: true + + /conventional-changelog@3.1.25: + resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} + engines: {node: '>=10'} + dependencies: + conventional-changelog-angular: 5.0.13 + conventional-changelog-atom: 2.0.8 + conventional-changelog-codemirror: 2.0.8 + conventional-changelog-conventionalcommits: 4.6.3 + conventional-changelog-core: 4.2.4 + conventional-changelog-ember: 2.0.9 + conventional-changelog-eslint: 3.0.9 + conventional-changelog-express: 2.0.6 + conventional-changelog-jquery: 3.0.11 + conventional-changelog-jshint: 2.0.9 + conventional-changelog-preset-loader: 2.3.4 + dev: true + + /conventional-commit-types@3.0.0: + resolution: {integrity: sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==} + dev: true + + /conventional-commits-filter@2.0.7: + resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} + engines: {node: '>=10'} + dependencies: + lodash.ismatch: 4.4.0 + modify-values: 1.0.1 + dev: true + + /conventional-commits-parser@3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + JSONStream: 1.3.5 + is-text-path: 1.0.1 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + + /conventional-recommended-bump@6.1.0: + resolution: {integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + concat-stream: 2.0.0 + conventional-changelog-preset-loader: 2.3.4 + conventional-commits-filter: 2.0.7 + conventional-commits-parser: 3.2.4 + git-raw-commits: 2.0.11 + git-semver-tags: 4.1.1 + meow: 8.1.2 + q: 1.5.1 + dev: true + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true + + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true + + /cookie-signature@1.2.1: + resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} + engines: {node: '>=6.6.0'} + dev: false + + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + dev: true + + /copy-to@2.0.1: + resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + dev: false + + /core-js-compat@3.36.0: + resolution: {integrity: sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==} + dependencies: + browserslist: 4.23.0 + dev: true + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + /cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.18)(cosmiconfig@8.3.6)(typescript@5.3.3): + resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==} + engines: {node: '>=v16'} + requiresBuild: true + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=8.2' + typescript: '>=4' + dependencies: + '@types/node': 20.11.18 + cosmiconfig: 8.3.6(typescript@5.3.3) + jiti: 1.21.0 + typescript: 5.3.3 + dev: true + optional: true + + /cosmiconfig@8.3.6(typescript@5.3.3): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + typescript: 5.3.3 + dev: true + + /crc32@0.2.2: + resolution: {integrity: sha512-PFZEGbDUeoNbL2GHIEpJRQGheXReDody/9axKTxhXtQqIL443wnNigtVZO9iuCIMPApKZRv7k2xr8euXHqNxQQ==} + engines: {node: '>= 0.4.0'} + hasBin: true + dev: false + + /create-jest@29.7.0(@types/node@20.11.18)(ts-node@10.9.2): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + /cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + dependencies: + luxon: 3.4.4 + dev: false + + /cron@3.1.6: + resolution: {integrity: sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==} + dependencies: + '@types/luxon': 3.3.8 + luxon: 3.4.4 + dev: false + + /cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dev: true + + /cross-spawn@6.0.5: + resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + engines: {node: '>=4.8'} + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + dev: false + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /crypto-js@4.2.0: + resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + + /css-inline@0.11.2: + resolution: {integrity: sha512-c/oie5Yqa2lVRwUO7A8nd3c3r0x7yE6MQH2PPB/R1LaUb6ohZD7vNXj23fod5y4QNsNhsQi98/AWfUwo1K6R7g==} + dev: false + + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /cz-conventional-changelog@3.3.0(@types/node@20.11.18)(typescript@5.3.3): + resolution: {integrity: sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==} + engines: {node: '>= 10'} + dependencies: + chalk: 2.4.2 + commitizen: 4.3.0(@types/node@20.11.18)(typescript@5.3.3) + conventional-commit-types: 3.0.0 + lodash.map: 4.6.0 + longest: 2.0.1 + word-wrap: 1.2.5 + optionalDependencies: + '@commitlint/load': 18.6.1(@types/node@20.11.18)(typescript@5.3.3) + transitivePeerDependencies: + - '@types/node' + - typescript + dev: true + + /cz-customizable@7.0.0: + resolution: {integrity: sha512-pQKkGSm+8SY9VY/yeJqDOla1MjrGaG7WG4EYLLEV4VNctGO7WdzdGtWEr2ydKSkrpmTs7f8fmBksg/FaTrUAyw==} + hasBin: true + dependencies: + editor: 1.0.0 + find-config: 1.0.0 + inquirer: 6.5.2 + lodash: 4.17.21 + temp: 0.9.4 + word-wrap: 1.2.5 + dev: true + + /d@1.0.1: + resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} + dependencies: + es5-ext: 0.10.62 + type: 1.2.0 + dev: true + + /dargs@7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + dev: true + + /dash-ast@2.0.1: + resolution: {integrity: sha512-5TXltWJGc+RdnabUGzhRae1TRq6m4gr+3K2wQX0is5/F2yS6MJXJvLyI3ErAnsAXuJoGqvfVD5icRgim07DrxQ==} + dev: true + + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.23.9 + dev: false + + /dateformat@3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + dev: true + + /dayjs@1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: false + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decache@4.6.2: + resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} + dependencies: + callsite: 1.0.0 + dev: true + + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + dev: true + + /dedent@1.5.1: + resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true + + /deep-equal@1.1.2: + resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} + engines: {node: '>= 0.4'} + dependencies: + is-arguments: 1.1.1 + is-date-object: 1.0.5 + is-regex: 1.1.4 + object-is: 1.1.5 + object-keys: 1.1.1 + regexp.prototype.flags: 1.5.2 + dev: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + /default-user-agent@1.0.0: + resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==} + engines: {node: '>= 0.10.0'} + dependencies: + os-name: 1.0.3 + dev: false + + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: true + + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + /define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + dev: true + + /define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + dev: true + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false + + /depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: true + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + /detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + dev: true + + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + /detect-node@2.0.4: + resolution: {integrity: sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==} + dev: false + + /detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + dev: false + + /dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + dev: true + + /dfa@1.2.0: + resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + dev: true + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + /digest-header@1.1.0: + resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==} + engines: {node: '>= 8.0.0'} + dev: false + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /display-notification@2.0.0: + resolution: {integrity: sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==} + engines: {node: '>=4'} + dependencies: + escape-string-applescript: 1.0.0 + run-applescript: 3.2.0 + dev: false + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /doctypes@1.1.0: + resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==} + requiresBuild: true + dev: false + + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + dev: false + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + /domhandler@3.3.0: + resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + dev: false + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + /dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + dependencies: + is-obj: 2.0.0 + dev: true + + /dot@2.0.0-beta.1: + resolution: {integrity: sha512-kxM7fSnNQTXOmaeGuBSXM8O3fEsBb7XSDBllkGbRwa0lJSJTxxDE/4eSNGLKZUmlFw0f1vJ5qSV2BljrgQtgIA==} + dev: true + + /dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + dev: false + + /dotenv@16.4.1: + resolution: {integrity: sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==} + engines: {node: '>=12'} + dev: false + + /dotenv@16.4.4: + resolution: {integrity: sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==} + engines: {node: '>=12'} + dev: false + + /dotgitignore@2.1.0: + resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} + engines: {node: '>=6'} + dependencies: + find-up: 3.0.0 + minimatch: 3.1.2 + dev: true + + /duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + dependencies: + readable-stream: 2.3.8 + dev: true + + /duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: true + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /editor@1.0.0: + resolution: {integrity: sha512-SoRmbGStwNYHgKfjOrX2L0mUvp9bUVv0uPppZSOMAntEbcFtoC3MKF5b3T6HQPXKIV+QGY3xPO3JK5it5lVkuw==} + dev: true + + /editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.6.0 + dev: false + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + /ejs@3.1.9: + resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.7 + dev: false + optional: true + + /electron-to-chromium@1.4.670: + resolution: {integrity: sha512-hcijYOWjOtjKrKPtNA6tuLlA/bTLO3heFG8pQA6mLpq7dRydSWicXova5lyxDzp1iVJaYhK7J2OQlGE52KYn7A==} + dev: true + + /emitter-component@1.1.2: + resolution: {integrity: sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==} + dev: true + + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + dev: true + + /emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + /enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + dev: false + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + /encoding-japanese@2.0.0: + resolution: {integrity: sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==} + engines: {node: '>=8.10.0'} + dev: false + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + + /engine.io-parser@5.2.2: + resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==} + engines: {node: '>=10.0.0'} + + /engine.io@6.5.4: + resolution: {integrity: sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==} + engines: {node: '>=10.2.0'} + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.17 + '@types/node': 20.11.18 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.4 + engine.io-parser: 5.2.2 + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + /enhanced-resolve@5.15.0: + resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + dev: true + + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + dev: false + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + dependencies: + stackframe: 1.3.4 + dev: false + + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + /es-module-lexer@1.4.1: + resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} + dev: true + + /es5-ext@0.10.62: + resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} + engines: {node: '>=0.10'} + requiresBuild: true + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + next-tick: 1.1.0 + dev: true + + /es6-iterator@2.0.3: + resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-symbol: 3.1.3 + dev: true + + /es6-map@0.1.5: + resolution: {integrity: sha512-mz3UqCh0uPCIqsw1SSAkB/p0rOzF/M0V++vyN7JqlPtSW/VsYgQBvVvqMLmfBuyMzTpLnNqi6JmcSizs4jy19A==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + es6-set: 0.1.6 + es6-symbol: 3.1.3 + event-emitter: 0.3.5 + dev: true + + /es6-set@0.1.6: + resolution: {integrity: sha512-TE3LgGLDIBX332jq3ypv6bcOpkLO0AslAQo7p2VqX/1N46YNsvIWgvjojjSEnWEGWMhr1qUbYeTSir5J6mFHOw==} + engines: {node: '>=0.12'} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + event-emitter: 0.3.5 + type: 2.7.2 + dev: true + + /es6-shim@0.35.8: + resolution: {integrity: sha512-Twf7I2v4/1tLoIXMT8HlqaBSS5H2wQTs2wx3MNYCI8K1R1/clXyCazrcVCPm/FuO9cyV8+leEaZOWD5C253NDg==} + dev: true + + /es6-symbol@3.1.3: + resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} + dependencies: + d: 1.0.1 + ext: 1.7.0 + dev: true + + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + /escape-goat@3.0.0: + resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==} + engines: {node: '>=10'} + dev: false + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + /escape-string-applescript@1.0.0: + resolution: {integrity: sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==} + engines: {node: '>=0.10.0'} + dev: false + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + + /escodegen@1.14.3: + resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} + engines: {node: '>=4.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 4.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + + /escodegen@1.2.0: + resolution: {integrity: sha512-yLy3Cc+zAC0WSmoT2fig3J87TpQ8UaZGx8ahCAs9FL8qNbyV7CVyPKS74DG4bsHiL5ew9sxdYx131OkBQMFnvA==} + engines: {node: '>=0.4.0'} + hasBin: true + dependencies: + esprima: 1.0.4 + estraverse: 1.5.1 + esutils: 1.0.0 + optionalDependencies: + source-map: 0.1.43 + dev: true + + /escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + dev: true + + /eslint-compat-utils@0.1.2(eslint@8.56.0): + resolution: {integrity: sha512-Jia4JDldWnFNIru1Ehx1H5s9/yxiRHY/TimCuUc0jNexew3cF1gI6CYZil1ociakfWO3rRqFjl1mskBblB3RYg==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + eslint: 8.56.0 + dev: true + + /eslint-compat-utils@0.4.1(eslint@8.56.0): + resolution: {integrity: sha512-5N7ZaJG5pZxUeNNJfUchurLVrunD1xJvyg5kYOIVF8kg1f3ajTikmAu/5fZ9w100omNPOoMjngRszh/Q/uFGMg==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + eslint: 8.56.0 + semver: 7.6.0 + dev: true + + /eslint-config-flat-gitignore@0.1.3: + resolution: {integrity: sha512-oQD+dEZv3RThN60tFqGFt+NJcO1DmssUcP+T/nlX+ZzEoEvVUYH0GU9X/VlmDXsbMsS9mONI1HrlxLgtKojw7w==} + dependencies: + find-up: 7.0.0 + parse-gitignore: 2.0.0 + dev: true + + /eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + dependencies: + debug: 3.2.7 + is-core-module: 2.13.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-merge-processors@0.1.0(eslint@8.56.0): + resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} + peerDependencies: + eslint: '*' + dependencies: + eslint: 8.56.0 + dev: true + + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + debug: 3.2.7 + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-antfu@2.1.2(eslint@8.56.0): + resolution: {integrity: sha512-s7ZTOM3uq0iqpp6gF0UEotnvup7f2PHBUftCytLZX0+6C9j9KadKZQh6bVVngAyFgsmeD9+gcBopOYLClb2oDg==} + peerDependencies: + eslint: '*' + dependencies: + eslint: 8.56.0 + dev: true + + /eslint-plugin-es-x@7.5.0(eslint@8.56.0): + resolution: {integrity: sha512-ODswlDSO0HJDzXU0XvgZ3lF3lS3XAZEossh15Q2UHjwrJggWeBoKqqEsLTZLXl+dh5eOAozG0zRcYtuE35oTuQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/regexpp': 4.10.0 + eslint: 8.56.0 + eslint-compat-utils: 0.1.2(eslint@8.56.0) + dev: true + + /eslint-plugin-eslint-comments@3.2.0(eslint@8.56.0): + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + escape-string-regexp: 1.0.5 + eslint: 8.56.0 + ignore: 5.3.1 + dev: true + + /eslint-plugin-i@2.29.1(@typescript-eslint/parser@6.21.0)(eslint@8.56.0): + resolution: {integrity: sha512-ORizX37MelIWLbMyqI7hi8VJMf7A0CskMmYkB+lkCX3aF4pkGV7kwx5bSEb4qx7Yce2rAf9s34HqDRPjGRZPNQ==} + engines: {node: '>=12'} + peerDependencies: + eslint: ^7.2.0 || ^8 + dependencies: + debug: 4.3.4 + doctrine: 3.0.0 + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + get-tsconfig: 4.7.2 + is-glob: 4.0.3 + minimatch: 3.1.2 + semver: 7.6.0 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-jsdoc@48.1.0(eslint@8.56.0): + resolution: {integrity: sha512-g9S8ukmTd1DVcV/xeBYPPXOZ6rc8WJ4yi0+MVxJ1jBOrz5kmxV9gJJQ64ltCqIWFnBChLIhLVx3tbTSarqVyFA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + dependencies: + '@es-joy/jsdoccomment': 0.42.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.3.4 + escape-string-regexp: 4.0.0 + eslint: 8.56.0 + esquery: 1.5.0 + is-builtin-module: 3.2.1 + semver: 7.6.0 + spdx-expression-parse: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-jsonc@2.13.0(eslint@8.56.0): + resolution: {integrity: sha512-2wWdJfpO/UbZzPDABuUVvlUQjfMJa2p2iQfYt/oWxOMpXCcjuiMUSaA02gtY/Dbu82vpaSqc+O7Xq6ECHwtIxA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + eslint: 8.56.0 + eslint-compat-utils: 0.4.1(eslint@8.56.0) + espree: 9.6.1 + graphemer: 1.4.0 + jsonc-eslint-parser: 2.4.0 + natural-compare: 1.4.0 + synckit: 0.6.2 + dev: true + + /eslint-plugin-markdown@3.0.1(eslint@8.56.0): + resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + eslint: 8.56.0 + mdast-util-from-markdown: 0.8.5 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-n@16.6.2(eslint@8.56.0): + resolution: {integrity: sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + builtins: 5.0.1 + eslint: 8.56.0 + eslint-plugin-es-x: 7.5.0(eslint@8.56.0) + get-tsconfig: 4.7.2 + globals: 13.24.0 + ignore: 5.3.1 + is-builtin-module: 3.2.1 + is-core-module: 2.13.1 + minimatch: 3.1.2 + resolve: 1.22.8 + semver: 7.6.0 + dev: true + + /eslint-plugin-no-only-tests@3.1.0: + resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==} + engines: {node: '>=5.0.0'} + dev: true + + /eslint-plugin-perfectionist@2.5.0(eslint@8.56.0)(typescript@5.3.3)(vue-eslint-parser@9.4.2): + resolution: {integrity: sha512-F6XXcq4mKKUe/SREoMGQqzgw6cgCgf3pFzkFfQVIGtqD1yXVpQjnhTepzhBeZfxZwgMzR9HO4yH4CUhIQ2WBcQ==} + peerDependencies: + astro-eslint-parser: ^0.16.0 + eslint: '>=8.0.0' + svelte: '>=3.0.0' + svelte-eslint-parser: ^0.33.0 + vue-eslint-parser: '>=9.0.0' + peerDependenciesMeta: + astro-eslint-parser: + optional: true + svelte: + optional: true + svelte-eslint-parser: + optional: true + vue-eslint-parser: + optional: true + dependencies: + '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + minimatch: 9.0.3 + natural-compare-lite: 1.4.0 + vue-eslint-parser: 9.4.2(eslint@8.56.0) + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /eslint-plugin-toml@0.9.2(eslint@8.56.0): + resolution: {integrity: sha512-ri0xf63PYf3pIq/WY9BIwrqxZmGTIwSkAO0bHddI0ajUwN4KGz6W8vOvdXFHOpRdRfzxlmXze/vfsY/aTEXESg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.4 + eslint: 8.56.0 + eslint-compat-utils: 0.4.1(eslint@8.56.0) + lodash: 4.17.21 + toml-eslint-parser: 0.9.3 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-unicorn@50.0.1(eslint@8.56.0): + resolution: {integrity: sha512-KxenCZxqSYW0GWHH18okDlOQcpezcitm5aOSz6EnobyJ6BIByiPDviQRjJIUAjG/tMN11958MxaQ+qCoU6lfDA==} + engines: {node: '>=16'} + peerDependencies: + eslint: '>=8.56.0' + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint/eslintrc': 2.1.4 + ci-info: 4.0.0 + clean-regexp: 1.0.0 + core-js-compat: 3.36.0 + eslint: 8.56.0 + esquery: 1.5.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.1 + jsesc: 3.0.2 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.27 + regjsparser: 0.10.0 + semver: 7.6.0 + strip-indent: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-unused-imports@3.1.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0): + resolution: {integrity: sha512-9l1YFCzXKkw1qtAru1RWUtG2EVDZY0a0eChKXcL+EZ5jitG7qxdctu4RnvhOJHv4xfmUf7h+JJPINlVpGhZMrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': 6 - 7 + eslint: '8' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + eslint-rule-composer: 0.3.0 + dev: true + + /eslint-plugin-vitest@0.3.22(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.56.0)(typescript@5.3.3): + resolution: {integrity: sha512-atkFGQ7aVgcuSeSMDqnyevIyUpfBPMnosksgEPrKE7Y8xQlqG/5z2IQ6UDau05zXaaFv7Iz8uzqvIuKshjZ0Zw==} + engines: {node: ^18.0.0 || >= 20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: '>=8.0.0' + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + vitest: + optional: true + dependencies: + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + eslint: 8.56.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /eslint-plugin-vue@9.21.1(eslint@8.56.0): + resolution: {integrity: sha512-XVtI7z39yOVBFJyi8Ljbn7kY9yHzznKXL02qQYn+ta63Iy4A9JFBw6o4OSB9hyD2++tVT+su9kQqetUyCCwhjw==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + eslint: 8.56.0 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.0.15 + semver: 7.6.0 + vue-eslint-parser: 9.4.2(eslint@8.56.0) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-yml@1.12.2(eslint@8.56.0): + resolution: {integrity: sha512-hvS9p08FhPT7i/ynwl7/Wt7ke7Rf4P2D6fT8lZlL43peZDTsHtH2A0SIFQ7Kt7+mJ6if6P+FX3iJhMkdnxQwpg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.4 + eslint: 8.56.0 + eslint-compat-utils: 0.4.1(eslint@8.56.0) + lodash: 4.17.21 + natural-compare: 1.4.0 + yaml-eslint-parser: 1.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-processor-vue-blocks@0.1.1(@vue/compiler-sfc@3.4.19)(eslint@8.56.0): + resolution: {integrity: sha512-9+dU5lU881log570oBwpelaJmOfOzSniben7IWEDRYQPPWwlvaV7NhOtsTuUWDqpYT+dtKKWPsgz4OkOi+aZnA==} + peerDependencies: + '@vue/compiler-sfc': ^3.3.0 + eslint: ^8.50.0 + dependencies: + '@vue/compiler-sfc': 3.4.19 + eslint: 8.56.0 + dev: true + + /eslint-rule-composer@0.3.0: + resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} + engines: {node: '>=4.0.0'} + dev: true + + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.56.0: + resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.56.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + dev: true + + /esprima@1.0.4: + resolution: {integrity: sha512-rp5dMKN8zEs9dfi9g0X1ClLmV//WRyk/R15mppFNICIFRG5P92VP7Z04p8pk++gABo9W2tY+kHyu6P1mEHgmTA==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /esprima@1.2.5: + resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: false + + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@1.5.1: + resolution: {integrity: sha512-FpCjJDfmo3vsc/1zKSeqR5k42tcIhxFIlvq+h9j0fO2q/h2uLKyweq7rYJ+0CoVvrGQOxIS5wyBrW/+vF58BUQ==} + engines: {node: '>=0.4.0'} + dev: true + + /estraverse@1.9.3: + resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==} + engines: {node: '>=0.10.0'} + dev: false + + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /estree-is-function@1.0.0: + resolution: {integrity: sha512-nSCWn1jkSq2QAtkaVLJZY2ezwcFO161HVc174zL1KPW3RJ+O6C3eJb8Nx7OXzvhoEv+nLgSR1g71oWUHUDTrJA==} + dev: true + + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + + /esutils@1.0.0: + resolution: {integrity: sha512-x/iYH53X3quDwfHRz4y8rn4XcEwwCJeWsul9pF1zldMbGtgOtMNBEOuYWwB1EQlK2LRa1fev3YAgym/RElp5Cg==} + engines: {node: '>=0.10.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: true + + /event-emitter@0.3.5: + resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} + dependencies: + d: 1.0.1 + es5-ext: 0.10.62 + dev: true + + /event-stream@4.0.1: + resolution: {integrity: sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.0.7 + pause-stream: 0.0.11 + split: 1.0.1 + stream-combiner: 0.2.2 + through: 2.3.8 + dev: true + + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + + /eventemitter2@6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + dev: false + + /eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + dev: true + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + /execa@0.10.0: + resolution: {integrity: sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==} + engines: {node: '>=4'} + dependencies: + cross-spawn: 6.0.5 + get-stream: 3.0.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + dev: false + + /execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + + /execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.2.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + dev: true + + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + dev: true + + /expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + dependencies: + homedir-polyfill: 1.0.3 + dev: true + + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + dev: true + + /ext@1.7.0: + resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + dependencies: + type: 2.7.2 + dev: true + + /extend-object@1.0.0: + resolution: {integrity: sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==} + dev: false + + /extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + dependencies: + is-extendable: 0.1.1 + dev: false + + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fancy-log@2.0.0: + resolution: {integrity: sha512-9CzxZbACXMUXW13tS0tI8XsGGmxWzO2DmYrGuBJOJ8k8q2K7hwfJA5qHjuPPe8wtsco33YR9wc+Rlr5wYFvhSA==} + engines: {node: '>=10.13.0'} + dependencies: + color-support: 1.1.3 + dev: true + + /fast-content-type-parse@1.1.0: + resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} + dev: false + + /fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + dev: false + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-json-stringify@5.12.0: + resolution: {integrity: sha512-7Nnm9UPa7SfHRbHVA1kJQrGXCRzB7LMlAAqHXQFkEQqueJm1V8owm0FsE/2Do55/4CcdhwiLQERaKomOnKQkyA==} + dependencies: + '@fastify/merge-json-schemas': 0.1.1 + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + fast-deep-equal: 3.1.3 + fast-uri: 2.3.0 + json-schema-ref-resolver: 1.0.1 + rfdc: 1.3.1 + dev: false + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + dependencies: + fast-decode-uri-component: 1.0.1 + dev: false + + /fast-redact@3.3.0: + resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} + engines: {node: '>=6'} + dev: false + + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + /fast-uri@2.3.0: + resolution: {integrity: sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==} + dev: false + + /fastify-plugin@4.5.1: + resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} + dev: false + + /fastify@4.26.0: + resolution: {integrity: sha512-Fq/7ziWKc6pYLYLIlCRaqJqEVTIZ5tZYfcW/mDK2AQ9v/sqjGFpj0On0/7hU50kbPVjLO4de+larPA1WwPZSfw==} + dependencies: + '@fastify/ajv-compiler': 3.5.0 + '@fastify/error': 3.4.1 + '@fastify/fast-json-stringify-compiler': 4.3.0 + abstract-logging: 2.0.1 + avvio: 8.3.0 + fast-content-type-parse: 1.1.0 + fast-json-stringify: 5.12.0 + find-my-way: 8.1.0 + light-my-request: 5.11.0 + pino: 8.18.0 + process-warning: 3.0.0 + proxy-addr: 2.0.7 + rfdc: 1.3.1 + secure-json-parse: 2.7.0 + semver: 7.6.0 + toad-cache: 3.7.0 + transitivePeerDependencies: + - supports-color + dev: false + + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + + /faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + dependencies: + websocket-driver: 0.7.4 + dev: true + + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + dependencies: + bser: 2.1.1 + dev: true + + /fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + dev: false + + /figures@2.0.0: + resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} + engines: {node: '>=4'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + + /figures@5.0.0: + resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} + engines: {node: '>=14'} + dependencies: + escape-string-regexp: 5.0.0 + is-unicode-supported: 1.3.0 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.2.0 + dev: true + + /file-stream-rotator@0.6.1: + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + dependencies: + moment: 2.30.1 + dev: false + + /filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + requiresBuild: true + dependencies: + minimatch: 5.1.6 + dev: false + optional: true + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + + /finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /find-config@1.0.0: + resolution: {integrity: sha512-Z+suHH+7LSE40WfUeZPIxSxypCWvrzdVc60xAjUShZeT5eMWM0/FQUduq3HjluyfAHWvC/aOBkT1pTZktyF/jg==} + engines: {node: '>= 0.12'} + dependencies: + user-home: 2.0.0 + dev: true + + /find-my-way@8.1.0: + resolution: {integrity: sha512-41QwjCGcVTODUmLLqTMeoHeiozbMXYMAE1CKFiDyi9zVZ2Vjh0yz3MF0WQZoIb+cmzP/XlbFjlF2NtJmvZHznA==} + engines: {node: '>=14'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 2.0.0 + dev: false + + /find-node-modules@2.1.3: + resolution: {integrity: sha512-UC2I2+nx1ZuOBclWVNdcnbDR5dlrOdVb7xNjmT/lHE+LsgztWks3dG7boJ37yTS/venXw84B/mAW9uHVoC5QRg==} + dependencies: + findup-sync: 4.0.0 + merge: 2.1.1 + dev: true + + /find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: true + + /find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + dependencies: + locate-path: 2.0.0 + dev: true + + /find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + dependencies: + locate-path: 3.0.0 + dev: true + + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /find-up@7.0.0: + resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} + engines: {node: '>=18'} + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + dev: true + + /findup-sync@4.0.0: + resolution: {integrity: sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==} + engines: {node: '>= 8'} + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.5 + resolve-dir: 1.0.1 + dev: true + + /fixpack@4.0.0: + resolution: {integrity: sha512-5SM1+H2CcuJ3gGEwTiVo/+nd/hYpNj9Ch3iMDOQ58ndY+VGQ2QdvaUTkd3otjZvYnd/8LF/HkJ5cx7PBq0orCQ==} + hasBin: true + dependencies: + alce: 1.2.0 + chalk: 3.0.0 + detect-indent: 6.1.0 + detect-newline: 3.1.0 + extend-object: 1.0.0 + rc: 1.2.8 + dev: false + + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.9 + keyv: 4.5.4 + rimraf: 3.0.2 + dev: true + + /flatted@3.2.9: + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + dev: true + + /fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + dev: false + + /follow-redirects@1.15.5: + resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + + /fork-ts-checker-webpack-plugin@9.0.2(typescript@5.3.3)(webpack@5.90.1): + resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + dependencies: + '@babel/code-frame': 7.23.5 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.3.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.6.0 + tapable: 2.2.1 + typescript: 5.3.3 + webpack: 5.90.1 + dev: true + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + /formidable@2.1.2: + resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.11.2 + dev: true + + /formstream@1.3.1: + resolution: {integrity: sha512-FkW++ub+VbE5dpwukJVDizNWhSgp8FhmhI65pF7BZSVStBqe6Wgxe2Z9/Vhsn7l7nXCPwP+G1cyYlX8VwWOf0g==} + dependencies: + destroy: 1.2.0 + mime: 2.6.0 + pause-stream: 0.0.11 + dev: false + + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: true + + /from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + dev: true + + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: true + + /fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: true + + /fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: true + + /fs-monkey@1.0.5: + resolution: {integrity: sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==} + dev: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + dependencies: + is-property: 1.0.2 + dev: false + + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true + + /get-assigned-identifiers@1.2.0: + resolution: {integrity: sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==} + dev: true + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + /get-east-asian-width@1.2.0: + resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + engines: {node: '>=18'} + dev: true + + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.1 + + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-pkg-repo@4.2.1: + resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} + engines: {node: '>=6.9.0'} + hasBin: true + dependencies: + '@hutson/parse-repository-url': 3.0.2 + hosted-git-info: 4.1.0 + through2: 2.0.5 + yargs: 16.2.0 + dev: true + + /get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + dev: false + + /get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + dev: false + + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.0 + dev: true + + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + + /get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + dev: true + + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + + /git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + dargs: 7.0.0 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + + /git-remote-origin-url@2.0.0: + resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} + engines: {node: '>=4'} + dependencies: + gitconfiglocal: 1.0.0 + pify: 2.3.0 + dev: true + + /git-semver-tags@4.1.1: + resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + meow: 8.1.2 + semver: 6.3.1 + dev: true + + /gitconfiglocal@1.0.0: + resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + dependencies: + ini: 1.3.8 + dev: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + dev: true + + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.4 + path-scurry: 1.10.1 + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + + /glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.10.1 + dev: true + + /global-dirs@0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + requiresBuild: true + dependencies: + ini: 1.3.8 + dev: true + optional: true + + /global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + dev: true + + /global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + dev: true + + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true + + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.4 + + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true + + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true + + /hammerjs@2.0.8: + resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} + engines: {node: '>=0.8.0'} + dev: true + + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + dependencies: + es-define-property: 1.0.0 + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + + /has@1.0.4: + resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} + engines: {node: '>= 0.4.0'} + dev: true + + /hasown@2.0.1: + resolution: {integrity: sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: false + + /helmet@7.1.0: + resolution: {integrity: sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==} + engines: {node: '>=16.0.0'} + dev: false + + /hexoid@1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + dev: true + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + + /homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + dependencies: + parse-passwd: 1.0.0 + dev: true + + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + + /html-entities@2.4.0: + resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==} + dev: true + + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true + + /html-minifier@4.0.0: + resolution: {integrity: sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==} + engines: {node: '>=6'} + hasBin: true + dependencies: + camel-case: 3.0.0 + clean-css: 4.2.4 + commander: 2.20.3 + he: 1.2.0 + param-case: 2.1.1 + relateurl: 0.2.7 + uglify-js: 3.17.4 + dev: false + + /html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + dev: false + + /htmlparser2@5.0.1: + resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} + dependencies: + domelementtype: 2.3.0 + domhandler: 3.3.0 + domutils: 2.8.0 + entities: 2.2.0 + dev: false + + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + + /http-auth-connect@1.0.6: + resolution: {integrity: sha512-yaO0QSCPqGCjPrl3qEEHjJP+lwZ6gMpXLuCBE06eWwcXomkI5TARtu0kxf9teFuBj6iaV3Ybr15jaWUvbzNzHw==} + engines: {node: '>=8'} + dev: true + + /http-auth@4.1.9: + resolution: {integrity: sha512-kvPYxNGc9EKGTXvOMnTBQw2RZfuiSihK/mLw/a4pbtRueTE45S55Lw/3k5CktIf7Ak0veMKEIteDj4YkNmCzmQ==} + engines: {node: '>=8'} + dependencies: + apache-crypt: 1.2.6 + apache-md5: 1.1.8 + bcryptjs: 2.4.3 + uuid: 8.3.2 + dev: true + + /http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + dev: true + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + /http-parser-js@0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + dev: true + + /human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + dev: true + + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + + /husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /i18next@23.8.2: + resolution: {integrity: sha512-Z84zyEangrlERm0ZugVy4bIt485e/H8VecGUZkZWrH7BDePG6jT73QdL9EA1tRTTVVMpry/MgWIP1FjEn0DRXA==} + dependencies: + '@babel/runtime': 7.23.9 + dev: true + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + dev: true + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + dev: true + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + /inquirer@6.5.2: + resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==} + engines: {node: '>=6.0.0'} + dependencies: + ansi-escapes: 3.2.0 + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-width: 2.2.1 + external-editor: 3.1.0 + figures: 2.0.0 + lodash: 4.17.21 + mute-stream: 0.0.7 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 2.1.1 + strip-ansi: 5.2.0 + through: 2.3.8 + dev: true + + /inquirer@8.2.5: + resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} + engines: {node: '>=12.0.0'} + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + + /inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + dev: true + + /inquirer@9.2.12: + resolution: {integrity: sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==} + engines: {node: '>=14.18.0'} + dependencies: + '@ljharb/through': 2.3.12 + ansi-escapes: 4.3.2 + chalk: 5.3.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 5.0.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + dev: true + + /ioredis@5.3.2: + resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} + engines: {node: '>=12.22.0'} + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + 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 + transitivePeerDependencies: + - supports-color + dev: false + + /ip@1.1.8: + resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} + dev: false + + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: true + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: true + + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + + /is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true + + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + dependencies: + hasown: 2.0.1 + + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.2 + dev: true + + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: true + + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + /is-expression@4.0.0: + resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==} + requiresBuild: true + dependencies: + acorn: 7.4.1 + object-assign: 4.1.1 + dev: false + + /is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + dev: false + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + /is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + dev: true + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + + /is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + dependencies: + get-east-asian-width: 1.2.0 + dev: true + + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: true + + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + dev: true + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + requiresBuild: true + dev: false + + /is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + dev: false + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + /is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + dev: false + + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /is-text-path@1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} + dependencies: + text-extensions: 1.9.0 + dev: true + + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true + + /is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + dev: true + + /is-utf8@0.2.1: + resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} + dev: true + + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + dev: true + + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + dependencies: + '@babel/core': 7.23.9 + '@babel/parser': 7.23.9 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-instrument@6.0.1: + resolution: {integrity: sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.23.9 + '@babel/parser': 7.23.9 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true + + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true + + /iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + /jake@10.8.7: + resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + dependencies: + async: 3.2.5 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + dev: false + optional: true + + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + dev: true + + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.1 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.0.4 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + + /jest-cli@29.7.0(@types/node@20.11.18)(ts-node@10.9.2): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /jest-config@29.7.0(@types/node@20.11.18)(ts-node@10.9.2): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.23.9 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + babel-jest: 29.7.0(@babel/core@7.23.9) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.9.2(@types/node@20.11.18)(typescript@5.3.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + dev: true + + /jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + dev: true + + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + dev: true + + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.11.18 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + dev: true + + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.23.5 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true + + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + jest-util: 29.7.0 + dev: true + + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.7.0 + dev: true + + /jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 + dev: true + + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.23.9 + '@babel/generator': 7.23.6 + '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.9) + '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.9) + '@babel/types': 7.23.9 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.9) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: true + + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + dev: true + + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + dev: true + + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.11.18 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + dev: true + + /jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/node': 20.11.18 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@types/node': 20.11.18 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true + + /jest@29.7.0(@types/node@20.11.18)(ts-node@10.9.2): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + hasBin: true + requiresBuild: true + dev: true + optional: true + + /js-beautify@1.14.11: + resolution: {integrity: sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==} + engines: {node: '>=14'} + hasBin: true + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.3.10 + nopt: 7.2.0 + dev: false + + /js-stringify@1.0.2: + resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} + requiresBuild: true + dev: false + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + dev: true + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + + /jsdoc-type-pratt-parser@4.0.0: + resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + engines: {node: '>=12.0.0'} + dev: true + + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: true + + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + dev: true + + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true + + /json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: true + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + + /json-schema-ref-resolver@1.0.1: + resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} + dependencies: + fast-deep-equal: 3.1.3 + dev: false + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true + + /jsonc-eslint-parser@2.4.0: + resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.11.3 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.6.0 + dev: true + + /jsonc-parser@3.1.0: + resolution: {integrity: sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==} + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: true + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + + /jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + dev: true + + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.0 + dev: false + + /jstransformer@1.0.0: + resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} + requiresBuild: true + dependencies: + is-promise: 2.2.2 + promise: 7.3.1 + dev: false + + /juice@9.1.0: + resolution: {integrity: sha512-odblShmPrUoHUwRuC8EmLji5bPP2MLO1GL+gt4XU3tT2ECmbSrrMjtMQaqg3wgMFP2zvUzdPZGfxc5Trk3Z+fQ==} + engines: {node: '>=10.0.0'} + hasBin: true + dependencies: + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + mensch: 0.3.4 + slick: 1.12.2 + web-resource-inliner: 6.0.1 + transitivePeerDependencies: + - encoding + dev: false + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + + /keycharm@0.2.0: + resolution: {integrity: sha512-i/XBRTiLqRConPKioy2oq45vbv04e8x59b0mnsIRQM+7Ec/8BC7UcL5pnC4FMeGb8KwG7q4wOMw7CtNZf5tiIg==} + dev: true + + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + dependencies: + json-buffer: 3.0.1 + dev: true + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + dev: true + + /kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + dev: false + + /leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + dev: false + + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + + /levn@0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + type-check: 0.3.2 + dev: true + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /libbase64@1.2.1: + resolution: {integrity: sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==} + dev: false + + /libmime@5.2.0: + resolution: {integrity: sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==} + dependencies: + encoding-japanese: 2.0.0 + iconv-lite: 0.6.3 + libbase64: 1.2.1 + libqp: 2.0.1 + dev: false + + /libmime@5.2.1: + resolution: {integrity: sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==} + dependencies: + encoding-japanese: 2.0.0 + iconv-lite: 0.6.3 + libbase64: 1.2.1 + libqp: 2.0.1 + dev: false + + /libphonenumber-js@1.10.56: + resolution: {integrity: sha512-d0GdKshNnyfl5gM7kZ9rXjGiAbxT/zCXp0k+EAzh8H4zrb2R7GXtMCrULrX7UQxtfx6CLy/vz/lomvW79FAFdA==} + + /libqp@2.0.1: + resolution: {integrity: sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==} + dev: false + + /light-my-request@5.11.0: + resolution: {integrity: sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==} + dependencies: + cookie: 0.5.0 + process-warning: 2.3.2 + set-cookie-parser: 2.6.0 + dev: false + + /lilconfig@3.0.0: + resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + engines: {node: '>=14'} + dev: true + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + dependencies: + uc.micro: 2.0.0 + dev: false + + /lint-staged@15.2.2: + resolution: {integrity: sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==} + engines: {node: '>=18.12.0'} + hasBin: true + dependencies: + chalk: 5.3.0 + commander: 11.1.0 + debug: 4.3.4 + execa: 8.0.1 + lilconfig: 3.0.0 + listr2: 8.0.1 + micromatch: 4.0.5 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /listr2@8.0.1: + resolution: {integrity: sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==} + engines: {node: '>=18.0.0'} + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.0.0 + rfdc: 1.3.1 + wrap-ansi: 9.0.0 + dev: true + + /load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: true + + /loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + dev: true + + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.5.0 + pkg-types: 1.0.3 + dev: true + + /locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + dev: true + + /locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + dev: true + + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-locate: 6.0.0 + dev: true + + /lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false + + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: true + + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + dev: false + + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + + /lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + dev: false + + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.ismatch@4.4.0: + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} + dev: true + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + + /lodash.map@4.6.0: + resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==} + dev: true + + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash.mergewith@4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + requiresBuild: true + dev: true + optional: true + + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + requiresBuild: true + dev: true + optional: true + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + dev: true + + /log-update@6.0.0: + resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} + engines: {node: '>=18'} + dependencies: + ansi-escapes: 6.2.0 + cli-cursor: 4.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + dev: true + + /logform@2.6.0: + resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==} + engines: {node: '>= 12.0.0'} + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + dev: false + + /loglevel-plugin-prefix@0.8.4: + resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==} + dev: true + + /loglevel@1.9.1: + resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==} + engines: {node: '>= 0.6.0'} + dev: true + + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false + + /longest@2.0.1: + resolution: {integrity: sha512-Ajzxb8CM6WAnFjgiloPsI3bF+WCxcvhdIG3KNA2KN962+tdBsHcuQ4k4qX/EcS/2CRkcc0iAkR956Nib6aXU/Q==} + engines: {node: '>=0.10.0'} + dev: true + + /lower-case@1.1.4: + resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} + dev: false + + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false + + /lru-cache@8.0.5: + resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} + engines: {node: '>=16.14'} + dev: false + + /lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + dev: true + + /luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + dev: false + + /macos-release@2.5.1: + resolution: {integrity: sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==} + engines: {node: '>=6'} + dev: true + + /magic-string@0.25.1: + resolution: {integrity: sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + + /magic-string@0.26.2: + resolution: {integrity: sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==} + engines: {node: '>=12'} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /magic-string@0.30.7: + resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /mailparser@3.6.7: + resolution: {integrity: sha512-/3x8HW70DNehw+3vdOPKdlLuxOHoWcGB5jfx5vJ5XUbY9/2jUJbrrhda5Si8Dj/3w08U0y5uGAkqs5+SPTPKoA==} + dependencies: + encoding-japanese: 2.0.0 + he: 1.2.0 + html-to-text: 9.0.5 + iconv-lite: 0.6.3 + libmime: 5.2.1 + linkify-it: 5.0.0 + mailsplit: 5.4.0 + nodemailer: 6.9.9 + tlds: 1.248.0 + dev: false + + /mailsplit@5.4.0: + resolution: {integrity: sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==} + dependencies: + libbase64: 1.2.1 + libmime: 5.2.0 + libqp: 2.0.1 + dev: false + + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.6.0 + dev: true + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + dependencies: + tmpl: 1.0.5 + dev: true + + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + + /map-stream@0.0.7: + resolution: {integrity: sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==} + dev: true + + /marked@7.0.3: + resolution: {integrity: sha512-ev2uM40p0zQ/GbvqotfKcSWEa59fJwluGZj5dcaUOwDRrB1F3dncdXy8NWUApk4fi8atU3kTBOwjyjZ0ud0dxw==} + engines: {node: '>= 16'} + hasBin: true + dev: true + + /mdast-util-from-markdown@0.8.5: + resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-string: 2.0.0 + micromark: 2.11.4 + parse-entities: 2.0.0 + unist-util-stringify-position: 2.0.3 + transitivePeerDependencies: + - supports-color + dev: true + + /mdast-util-to-string@2.0.0: + resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + dev: true + + /memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + dependencies: + fs-monkey: 1.0.5 + dev: true + + /mensch@0.3.4: + resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==} + dev: false + + /meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + dev: true + + /merge-source-map@1.0.4: + resolution: {integrity: sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==} + dependencies: + source-map: 0.5.7 + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /merge@2.1.1: + resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==} + dev: true + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: true + + /micromark@2.11.4: + resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + dependencies: + debug: 4.3.4 + parse-entities: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + /mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: false + + /mimic-fn@1.2.0: + resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + engines: {node: '>=4'} + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + + /minimist@1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + /minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + dev: true + + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + + /mjml-accordion@4.14.1: + resolution: {integrity: sha512-dpNXyjnhYwhM75JSjD4wFUa9JgHm86M2pa0CoTzdv1zOQz67ilc4BoK5mc2S0gOjJpjBShM5eOJuCyVIuAPC6w==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-body@4.14.1: + resolution: {integrity: sha512-YpXcK3o2o1U+fhI8f60xahrhXuHmav6BZez9vIN3ZEJOxPFSr+qgr1cT2iyFz50L5+ZsLIVj2ZY+ALQjdsg8ig==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-button@4.14.1: + resolution: {integrity: sha512-V1Tl1vQ3lXYvvqHJHvGcc8URr7V1l/ZOsv7iLV4QRrh7kjKBXaRS7uUJtz6/PzEbNsGQCiNtXrODqcijLWlgaw==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-carousel@4.14.1: + resolution: {integrity: sha512-Ku3MUWPk/TwHxVgKEUtzspy/ePaWtN/3z6/qvNik0KIn0ZUIZ4zvR2JtaVL5nd30LHSmUaNj30XMPkCjYiKkFA==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-cli@4.14.1: + resolution: {integrity: sha512-Gy6MnSygFXs0U1qOXTHqBg2vZX2VL/fAacgQzD4MHq4OuybWaTNSzXRwxBXYCxT3IJB874n2Q0Mxp+Xka+tnZg==} + hasBin: true + dependencies: + '@babel/runtime': 7.23.9 + chokidar: 3.6.0 + glob: 7.2.3 + html-minifier: 4.0.0 + js-beautify: 1.14.11 + lodash: 4.17.21 + mjml-core: 4.14.1 + mjml-migrate: 4.14.1 + mjml-parser-xml: 4.14.1 + mjml-validator: 4.13.0 + yargs: 16.2.0 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-column@4.14.1: + resolution: {integrity: sha512-iixVCIX1YJtpQuwG2WbDr7FqofQrlTtGQ4+YAZXGiLThs0En3xNIJFQX9xJ8sgLEGGltyooHiNICBRlzSp9fDg==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-core@4.14.1: + resolution: {integrity: sha512-di88rSfX+8r4r+cEqlQCO7CRM4mYZrfe2wSCu2je38i+ujjkLpF72cgLnjBlSG5aOUCZgYvlsZ85stqIz9LQfA==} + dependencies: + '@babel/runtime': 7.23.9 + cheerio: 1.0.0-rc.12 + detect-node: 2.1.0 + html-minifier: 4.0.0 + js-beautify: 1.14.11 + juice: 9.1.0 + lodash: 4.17.21 + mjml-migrate: 4.14.1 + mjml-parser-xml: 4.14.1 + mjml-validator: 4.13.0 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-divider@4.14.1: + resolution: {integrity: sha512-agqWY0aW2xaMiUOhYKDvcAAfOLalpbbtjKZAl1vWmNkURaoK4L7MgDilKHSJDFUlHGm2ZOArTrq8i6K0iyThBQ==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-group@4.14.1: + resolution: {integrity: sha512-dJt5batgEJ7wxlxzqOfHOI94ABX+8DZBvAlHuddYO4CsLFHYv6XRIArLAMMnAKU76r6p3X8JxYeOjKZXdv49kg==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-attributes@4.14.1: + resolution: {integrity: sha512-XdUNOp2csK28kBDSistInOyzWNwmu5HDNr4y1Z7vSQ1PfkmiuS6jWG7jHUjdoMhs27e6Leuyyc6a8gWSpqSWrg==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-breakpoint@4.14.1: + resolution: {integrity: sha512-Qw9l/W/I5Z9p7I4ShgnEpAL9if4472ejcznbBnp+4Gq+sZoPa7iYoEPsa9UCGutlaCh3N3tIi2qKhl9qD8DFxA==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-font@4.14.1: + resolution: {integrity: sha512-oBYm1gaOdEMjE5BoZouRRD4lCNZ1jcpz92NR/F7xDyMaKCGN6T/+r4S5dq1gOLm9zWqClRHaECdFJNEmrDpZqA==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-html-attributes@4.14.1: + resolution: {integrity: sha512-vlJsJc1Sm4Ml2XvLmp01zsdmWmzm6+jNCO7X3eYi9ngEh8LjMCLIQOncnOgjqm9uGpQu2EgUhwvYFZP2luJOVg==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-preview@4.14.1: + resolution: {integrity: sha512-89gQtt3fhl2dkYpHLF5HDQXz/RLpzecU6wmAIT7Dz6etjLGE1dgq2Ay6Bu/OeHjDcT1gbM131zvBwuXw8OydNw==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-style@4.14.1: + resolution: {integrity: sha512-XryOuf32EDuUCBT2k99C1+H87IOM919oY6IqxKFJCDkmsbywKIum7ibhweJdcxiYGONKTC6xjuibGD3fQTTYNQ==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head-title@4.14.1: + resolution: {integrity: sha512-aIfpmlQdf1eJZSSrFodmlC4g5GudBti2eMyG42M7/3NeLM6anEWoe+UkF/6OG4Zy0tCQ40BDJ5iBZlMsjQICzw==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-head@4.14.1: + resolution: {integrity: sha512-KoCbtSeTAhx05Ugn9TB2UYt5sQinSCb7RGRer5iPQ3CrXj8hT5B5Svn6qvf/GACPkWl4auExHQh+XgLB+r3OEA==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-hero@4.14.1: + resolution: {integrity: sha512-TQJ3yfjrKYGkdEWjHLHhL99u/meKFYgnfJvlo9xeBvRjSM696jIjdqaPHaunfw4CP6d2OpCIMuacgOsvqQMWOA==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-image@4.14.1: + resolution: {integrity: sha512-jfKLPHXuFq83okwlNM1Um/AEWeVDgs2JXIOsWp2TtvXosnRvGGMzA5stKLYdy1x6UfKF4c1ovpMS162aYGp+xQ==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-migrate@4.14.1: + resolution: {integrity: sha512-d+9HKQOhZi3ZFAaFSDdjzJX9eDQGjMf3BArLWNm2okC4ZgfJSpOc77kgCyFV8ugvwc8fFegPnSV60Jl4xtvK2A==} + hasBin: true + dependencies: + '@babel/runtime': 7.23.9 + js-beautify: 1.14.11 + lodash: 4.17.21 + mjml-core: 4.14.1 + mjml-parser-xml: 4.14.1 + yargs: 16.2.0 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-navbar@4.14.1: + resolution: {integrity: sha512-rNy1Kw8CR3WQ+M55PFBAUDz2VEOjz+sk06OFnsnmNjoMVCjo1EV7OFLDAkmxAwqkC8h4zQWEOFY0MBqqoAg7+A==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-parser-xml@4.14.1: + resolution: {integrity: sha512-9WQVeukbXfq9DUcZ8wOsHC6BTdhaVwTAJDYMIQglXLwKwN7I4pTCguDDHy5d0kbbzK5OCVxCdZe+bfVI6XANOQ==} + dependencies: + '@babel/runtime': 7.23.9 + detect-node: 2.0.4 + htmlparser2: 8.0.2 + lodash: 4.17.21 + dev: false + + /mjml-preset-core@4.14.1: + resolution: {integrity: sha512-uUCqK9Z9d39rwB/+JDV2KWSZGB46W7rPQpc9Xnw1DRP7wD7qAfJwK6AZFCwfTgWdSxw0PwquVNcrUS9yBa9uhw==} + dependencies: + '@babel/runtime': 7.23.9 + mjml-accordion: 4.14.1 + mjml-body: 4.14.1 + mjml-button: 4.14.1 + mjml-carousel: 4.14.1 + mjml-column: 4.14.1 + mjml-divider: 4.14.1 + mjml-group: 4.14.1 + mjml-head: 4.14.1 + mjml-head-attributes: 4.14.1 + mjml-head-breakpoint: 4.14.1 + mjml-head-font: 4.14.1 + mjml-head-html-attributes: 4.14.1 + mjml-head-preview: 4.14.1 + mjml-head-style: 4.14.1 + mjml-head-title: 4.14.1 + mjml-hero: 4.14.1 + mjml-image: 4.14.1 + mjml-navbar: 4.14.1 + mjml-raw: 4.14.1 + mjml-section: 4.14.1 + mjml-social: 4.14.1 + mjml-spacer: 4.14.1 + mjml-table: 4.14.1 + mjml-text: 4.14.1 + mjml-wrapper: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-raw@4.14.1: + resolution: {integrity: sha512-9+4wzoXnCtfV6QPmjfJkZ50hxFB4Z8QZnl2Ac0D1Cn3dUF46UkmO5NLMu7UDIlm5DdFyycZrMOwvZS4wv9ksPw==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-section@4.14.1: + resolution: {integrity: sha512-Ik5pTUhpT3DOfB3hEmAWp8rZ0ilWtIivnL8XdUJRfgYE9D+MCRn+reIO+DAoJHxiQoI6gyeKkIP4B9OrQ7cHQw==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-social@4.14.1: + resolution: {integrity: sha512-G44aOZXgZHukirjkeQWTTV36UywtE2YvSwWGNfo/8d+k5JdJJhCIrlwaahyKEAyH63G1B0Zt8b2lEWx0jigYUw==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-spacer@4.14.1: + resolution: {integrity: sha512-5SfQCXTd3JBgRH1pUy6NVZ0lXBiRqFJPVHBdtC3OFvUS3q1w16eaAXlIUWMKTfy8CKhQrCiE6m65kc662ZpYxA==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-table@4.14.1: + resolution: {integrity: sha512-aVBdX3WpyKVGh/PZNn2KgRem+PQhWlvnD00DKxDejRBsBSKYSwZ0t3EfFvZOoJ9DzfHsN0dHuwd6Z18Ps44NFQ==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-text@4.14.1: + resolution: {integrity: sha512-yZuvf5z6qUxEo5CqOhCUltJlR6oySKVcQNHwoV5sneMaKdmBiaU4VDnlYFera9gMD9o3KBHIX6kUg7EHnCwBRQ==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml-validator@4.13.0: + resolution: {integrity: sha512-uURYfyQYtHJ6Qz/1A7/+E9ezfcoISoLZhYK3olsxKRViwaA2Mm8gy/J3yggZXnsUXWUns7Qymycm5LglLEIiQg==} + dependencies: + '@babel/runtime': 7.23.9 + dev: false + + /mjml-wrapper@4.14.1: + resolution: {integrity: sha512-aA5Xlq6d0hZ5LY+RvSaBqmVcLkvPvdhyAv3vQf3G41Gfhel4oIPmkLnVpHselWhV14A0KwIOIAKVxHtSAxyOTQ==} + dependencies: + '@babel/runtime': 7.23.9 + lodash: 4.17.21 + mjml-core: 4.14.1 + mjml-section: 4.14.1 + transitivePeerDependencies: + - encoding + dev: false + + /mjml@4.14.1: + resolution: {integrity: sha512-f/wnWWIVbeb/ge3ff7c/KYYizI13QbGIp03odwwkCThsJsacw4gpZZAU7V4gXY3HxSXP2/q3jxOfaHVbkfNpOQ==} + hasBin: true + dependencies: + '@babel/runtime': 7.23.9 + mjml-cli: 4.14.1 + mjml-core: 4.14.1 + mjml-migrate: 4.14.1 + mjml-preset-core: 4.14.1 + mjml-validator: 4.13.0 + transitivePeerDependencies: + - encoding + dev: false + + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + + /mlly@1.5.0: + resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.4.0 + dev: true + + /mnemonist@0.39.6: + resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==} + dependencies: + obliterator: 2.0.4 + dev: false + + /mockdate@3.0.5: + resolution: {integrity: sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==} + dev: false + + /modify-values@1.0.1: + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} + dev: true + + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + /morgan@1.10.0: + resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} + engines: {node: '>= 0.8.0'} + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /msgpackr-extract@3.0.2: + resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} + hasBin: true + requiresBuild: true + dependencies: + node-gyp-build-optional-packages: 5.0.7 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.2 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2 + dev: false + optional: true + + /msgpackr@1.10.1: + resolution: {integrity: sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==} + optionalDependencies: + msgpackr-extract: 3.0.2 + dev: false + + /mute-stream@0.0.7: + resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} + dev: true + + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true + + /mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dev: true + + /mysql2@3.9.1: + resolution: {integrity: sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==} + engines: {node: '>= 8.0'} + dependencies: + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.6.3 + long: 5.2.3 + lru-cache: 8.0.5 + named-placeholders: 1.1.3 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + dev: false + + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: false + + /named-placeholders@1.1.3: + resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} + engines: {node: '>=12.0.0'} + dependencies: + lru-cache: 7.18.3 + dev: false + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /natural-compare-lite@1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + /next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + dev: true + + /nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: false + + /no-case@2.3.2: + resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} + dependencies: + lower-case: 1.1.4 + dev: false + + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: true + + /node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + dependencies: + lodash: 4.17.21 + dev: true + + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + + /node-gyp-build-optional-packages@5.0.7: + resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==} + hasBin: true + requiresBuild: true + dev: false + optional: true + + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + dev: true + + /node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + dev: true + + /nodemailer@6.9.9: + resolution: {integrity: sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==} + engines: {node: '>=6.0.0'} + dev: false + + /nopt@7.2.0: + resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + abbrev: 2.0.0 + dev: false + + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.8 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.13.1 + semver: 7.6.0 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + /notepack.io@3.0.1: + resolution: {integrity: sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==} + dev: false + + /npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + dependencies: + path-key: 2.0.1 + dev: false + + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + + /npm-run-path@5.2.0: + resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + + /oauth@0.10.0: + resolution: {integrity: sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==} + dev: false + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + + /object-is@1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + dev: true + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /obliterator@2.0.4: + resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + dev: false + + /on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + dev: false + + /on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: true + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: true + + /on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + dependencies: + fn.name: 1.1.0 + dev: false + + /onetime@2.0.1: + resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + engines: {node: '>=4'} + dependencies: + mimic-fn: 1.2.0 + dev: true + + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + + /open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: false + + /open@8.4.0: + resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + + /opencollective-postinstall@2.0.3: + resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} + hasBin: true + dev: true + + /opentype.js@0.7.3: + resolution: {integrity: sha512-Veui5vl2bLonFJ/SjX/WRWJT3SncgiZNnKUyahmXCc2sa1xXW15u3R/3TN5+JFiP7RsjK5ER4HA5eWaEmV9deA==} + hasBin: true + dependencies: + tiny-inflate: 1.0.3 + dev: false + + /optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.3.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 + word-wrap: 1.2.5 + dev: true + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: true + + /os-homedir@1.0.2: + resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} + engines: {node: '>=0.10.0'} + dev: true + + /os-name@1.0.3: + resolution: {integrity: sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + osx-release: 1.1.0 + win-release: 1.1.1 + dev: false + + /os-name@4.0.1: + resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==} + engines: {node: '>=10'} + dependencies: + macos-release: 2.5.1 + windows-release: 4.0.0 + dev: true + + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /osx-release@1.1.0: + resolution: {integrity: sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: false + + /p-event@4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + dependencies: + p-timeout: 3.2.0 + dev: false + + /p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + dev: false + + /p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + dependencies: + p-try: 1.0.0 + dev: true + + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + + /p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + dependencies: + p-limit: 1.3.0 + dev: true + + /p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-limit: 4.0.0 + dev: true + + /p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + dependencies: + p-finally: 1.0.0 + dev: false + + /p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + dev: true + + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + + /p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} + engines: {node: '>=8'} + dependencies: + p-timeout: 3.2.0 + dev: false + + /pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + dev: true + + /param-case@2.1.1: + resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} + dependencies: + no-case: 2.3.2 + dev: false + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: true + + /parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + dev: true + + /parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: true + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.23.5 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + + /parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + dev: true + + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + dependencies: + parse5: 6.0.1 + dev: false + + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: false + + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false + + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + + /parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + dev: false + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: true + + /passport-google-oauth20@2.0.0: + resolution: {integrity: sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-oauth2: 1.8.0 + dev: false + + /passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + dev: false + + /passport-local@1.0.0: + resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + dev: false + + /passport-oauth2@1.8.0: + resolution: {integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==} + engines: {node: '>= 0.4.0'} + dependencies: + base64url: 3.0.1 + oauth: 0.10.0 + passport-strategy: 1.0.0 + uid2: 0.0.4 + utils-merge: 1.0.1 + dev: false + + /passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + dev: false + + /passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + dev: false + + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + + /path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + dev: true + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + /path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + dev: false + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.2.0 + minipass: 7.0.4 + + /path-to-regexp@3.2.0: + resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} + + /path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: false + + /path-type@3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: true + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + + /pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + dev: true + + /pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + dependencies: + through: 2.3.8 + + /pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + dev: false + + /pdfjs-dist@2.12.313: + resolution: {integrity: sha512-1x6iXO4Qnv6Eb+YFdN5JdUzt4pAkxSp3aLAYPX93eQCyg/m7QFzXVWJHJVtoW48CI8HCXju4dSkhQZwoheL5mA==} + peerDependencies: + worker-loader: ^3.0.8 + peerDependenciesMeta: + worker-loader: + optional: true + dev: true + + /pdfmake@0.2.9: + resolution: {integrity: sha512-LAtYwlR8cCQqbxESK2d50DYaVAzAC9Id9NjilRte6Tb9pyHUB+Z50nhD0imuBL0eDyXQKvEYSNjo3P5AOc2ZCg==} + engines: {node: '>=12'} + dependencies: + '@foliojs-fork/linebreak': 1.1.1 + '@foliojs-fork/pdfkit': 0.14.0 + iconv-lite: 0.6.3 + xmldoc: 1.3.0 + dev: true + + /peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + dev: false + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + /picomatch@3.0.1: + resolution: {integrity: sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==} + engines: {node: '>=10'} + dev: true + + /picomatch@4.0.1: + resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} + engines: {node: '>=12'} + dev: true + + /pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true + + /pino-abstract-transport@1.1.0: + resolution: {integrity: sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==} + dependencies: + readable-stream: 4.5.2 + split2: 4.2.0 + dev: false + + /pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + dev: false + + /pino@8.18.0: + resolution: {integrity: sha512-Mz/gKiRyuXu4HnpHgi1YWdHQCoWMufapzooisvFn78zl4dZciAxS+YeRkUxXl1ee/SzU80YCz1zpECCh4oC6Aw==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.3.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.1.0 + pino-std-serializers: 6.2.2 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 3.8.0 + thread-stream: 2.4.1 + dev: false + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true + + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + dev: true + + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.1 + mlly: 1.5.0 + pathe: 1.1.2 + dev: true + + /pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true + + /png-js@1.0.0: + resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} + dev: true + + /postcss-selector-parser@6.0.15: + resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss@8.4.35: + resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prelude-ls@1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + + /preview-email@3.0.19: + resolution: {integrity: sha512-DBS3Nir18YtKc8loYCCOGitmiaQ0vTdahPoiXxwNweJDpmVZo+w3tppufOhoK0m8skpRxT56llYLs3VrORnmNQ==} + engines: {node: '>=14'} + dependencies: + ci-info: 3.9.0 + display-notification: 2.0.0 + fixpack: 4.0.0 + get-port: 5.1.1 + mailparser: 3.6.7 + nodemailer: 6.9.9 + open: 7.4.2 + p-event: 4.2.0 + p-wait-for: 3.2.0 + pug: 3.0.2 + uuid: 9.0.1 + dev: false + + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: true + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true + + /process-warning@2.3.2: + resolution: {integrity: sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==} + dev: false + + /process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + dev: false + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: false + + /promise-coalesce@1.1.2: + resolution: {integrity: sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==} + engines: {node: '>=16'} + dev: false + + /promise@7.3.1: + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + requiresBuild: true + dependencies: + asap: 2.0.6 + dev: false + + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: true + + /propagating-hammerjs@1.5.0: + resolution: {integrity: sha512-3PUXWmomwutoZfydC+lJwK1bKCh6sK6jZGB31RUX6+4EXzsbkDZrK4/sVR7gBrvJaEIwpTVyxQUAd29FKkmVdw==} + dependencies: + hammerjs: 2.0.8 + dev: true + + /proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: false + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + + /proxy-middleware@0.15.0: + resolution: {integrity: sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==} + engines: {node: '>=0.8.0'} + dev: true + + /pug-attrs@3.0.0: + resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} + requiresBuild: true + dependencies: + constantinople: 4.0.1 + js-stringify: 1.0.2 + pug-runtime: 3.0.1 + dev: false + + /pug-code-gen@3.0.2: + resolution: {integrity: sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==} + requiresBuild: true + dependencies: + constantinople: 4.0.1 + doctypes: 1.1.0 + js-stringify: 1.0.2 + pug-attrs: 3.0.0 + pug-error: 2.0.0 + pug-runtime: 3.0.1 + void-elements: 3.1.0 + with: 7.0.2 + dev: false + + /pug-error@2.0.0: + resolution: {integrity: sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==} + requiresBuild: true + dev: false + + /pug-filters@4.0.0: + resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==} + requiresBuild: true + dependencies: + constantinople: 4.0.1 + jstransformer: 1.0.0 + pug-error: 2.0.0 + pug-walk: 2.0.0 + resolve: 1.22.8 + dev: false + + /pug-lexer@5.0.1: + resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==} + requiresBuild: true + dependencies: + character-parser: 2.2.0 + is-expression: 4.0.0 + pug-error: 2.0.0 + dev: false + + /pug-linker@4.0.0: + resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==} + requiresBuild: true + dependencies: + pug-error: 2.0.0 + pug-walk: 2.0.0 + dev: false + + /pug-load@3.0.0: + resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==} + requiresBuild: true + dependencies: + object-assign: 4.1.1 + pug-walk: 2.0.0 + dev: false + + /pug-parser@6.0.0: + resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==} + requiresBuild: true + dependencies: + pug-error: 2.0.0 + token-stream: 1.0.0 + dev: false + + /pug-runtime@3.0.1: + resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==} + requiresBuild: true + dev: false + + /pug-strip-comments@2.0.0: + resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==} + requiresBuild: true + dependencies: + pug-error: 2.0.0 + dev: false + + /pug-walk@2.0.0: + resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} + requiresBuild: true + dev: false + + /pug@3.0.2: + resolution: {integrity: sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==} + dependencies: + pug-code-gen: 3.0.2 + pug-filters: 4.0.0 + pug-lexer: 5.0.1 + pug-linker: 4.0.0 + pug-load: 3.0.0 + pug-parser: 6.0.0 + pug-runtime: 3.0.1 + pug-strip-comments: 2.0.0 + dev: false + + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + /pure-rand@6.0.4: + resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==} + dev: true + + /q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + dev: true + + /qiniu@7.11.0: + resolution: {integrity: sha512-Pdux9AxQR5V8IrlkSWDBUIrBRoxyK98sfmdGm19R0jZyxBMM2+KMwB0zhjAJhb6+lxEzjyHO3EfsVRz0JeTj7A==} + engines: {node: '>= 6'} + dependencies: + agentkeepalive: 4.5.0 + before: 0.0.1 + block-stream2: 2.1.0 + crc32: 0.2.2 + destroy: 1.2.0 + encodeurl: 1.0.2 + formstream: 1.3.1 + mime: 2.6.0 + mockdate: 3.0.5 + tunnel-agent: 0.6.0 + urllib: 2.41.0 + transitivePeerDependencies: + - proxy-agent + - supports-color + dev: false + + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.5 + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: false + + /quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + + /quote-stream@1.0.2: + resolution: {integrity: sha512-kKr2uQ2AokadPjvTyKJQad9xELbZwYzWlNfI3Uz2j/ib5u6H9lDP7fUUR//rMycd0gv4Z5P1qXMfXR8YpIxrjQ==} + hasBin: true + dependencies: + buffer-equal: 0.0.1 + minimist: 1.2.8 + through2: 2.0.5 + dev: true + + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: true + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + + /read-pkg-up@3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} + dependencies: + find-up: 2.1.0 + read-pkg: 3.0.0 + dev: true + + /read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg@3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: true + + /read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + /readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + + /real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + dev: false + + /rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + dependencies: + resolve: 1.22.8 + dev: true + + /redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + + /redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + dev: false + + /redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + dependencies: + redis-errors: 1.2.0 + dev: false + + /reflect-metadata@0.1.14: + resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} + dev: false + + /reflect-metadata@0.2.1: + resolution: {integrity: sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==} + + /regenerate-unicode-properties@10.1.1: + resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: true + + /regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: true + + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + /regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + dependencies: + '@babel/runtime': 7.23.9 + dev: true + + /regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + dev: true + + /regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.1 + dev: true + + /regexpu-core@5.3.2: + resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + engines: {node: '>=4'} + dependencies: + '@babel/regjsgen': 0.8.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.1 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: true + + /regjsparser@0.10.0: + resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + + /regjsparser@0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + + /relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + dev: false + + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + /resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: true + + /resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + dev: true + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve-global@1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + requiresBuild: true + dependencies: + global-dirs: 0.1.1 + dev: true + optional: true + + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + dev: true + + /resolve@1.1.7: + resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} + dev: true + + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /restore-cursor@2.0.0: + resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + engines: {node: '>=4'} + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.7 + dev: true + + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /ret@0.2.2: + resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} + engines: {node: '>=4'} + dev: false + + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rfdc@1.3.1: + resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + + /rimraf@2.6.3: + resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rimraf@4.4.1: + resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 9.3.5 + dev: true + + /rimraf@5.0.5: + resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 10.3.10 + dev: false + + /run-applescript@3.2.0: + resolution: {integrity: sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==} + engines: {node: '>=4'} + dependencies: + execa: 0.10.0 + dev: false + + /run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true + + /run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /rxjs@6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + dependencies: + tslib: 1.14.1 + dev: true + + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.6.2 + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safe-regex2@2.0.0: + resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} + dependencies: + ret: 0.2.2 + dev: false + + /safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + dev: false + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + /sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + dev: true + + /schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + dev: true + + /scope-analyzer@2.1.2: + resolution: {integrity: sha512-5cfCmsTYV/wPaRIItNxatw02ua/MThdIUNnUOCYp+3LSEJvnG804ANw2VLaavNILIfWXF1D1G2KNANkBBvInwQ==} + dependencies: + array-from: 2.1.1 + dash-ast: 2.0.1 + es6-map: 0.1.5 + es6-set: 0.1.6 + es6-symbol: 3.1.3 + estree-is-function: 1.0.0 + get-assigned-identifiers: 1.2.0 + dev: true + + /secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: false + + /selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + dependencies: + parseley: 0.12.1 + dev: false + + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true + + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + dev: false + + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + dependencies: + randombytes: 2.1.0 + dev: true + + /serve-index@1.9.1: + resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + engines: {node: '>= 0.8.0'} + dependencies: + accepts: 1.3.8 + batch: 0.6.1 + debug: 2.6.9 + escape-html: 1.0.3 + http-errors: 1.6.3 + mime-types: 2.1.35 + parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color + dev: true + + /set-cookie-parser@2.6.0: + resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + dev: false + + /set-function-length@1.2.1: + resolution: {integrity: sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==} + engines: {node: '>= 0.4'} + 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 + + /set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + dev: true + + /setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + dev: true + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + + /shallow-copy@0.0.1: + resolution: {integrity: sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==} + dev: true + + /shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + + /shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: false + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + dev: true + + /side-channel@1.0.5: + resolution: {integrity: sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.1 + + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true + + /slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + dev: true + + /slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + dev: true + + /slick@1.12.2: + resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} + dev: false + + /socket.io-adapter@2.5.2: + resolution: {integrity: sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==} + dependencies: + ws: 8.11.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + /socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + dependencies: + '@socket.io/component-emitter': 3.1.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + + /socket.io@4.7.4: + resolution: {integrity: sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==} + engines: {node: '>=10.2.0'} + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.4 + engine.io: 6.5.4 + socket.io-adapter: 2.5.2 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + /sonic-boom@3.8.0: + resolution: {integrity: sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.1.43: + resolution: {integrity: sha512-VtCvB9SIQhk3aF6h+N85EaqIaBFIAfZ9Cu+NJHHVvc8BbEcnvDcFw6sqQ2dQrT6SlOrZq3tIvyD9+EGq/lJryQ==} + engines: {node: '>=0.8.0'} + requiresBuild: true + dependencies: + amdefine: 1.0.1 + dev: true + optional: true + + /source-map@0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} + dev: false + + /source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: true + + /sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + dev: true + + /spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.17 + dev: true + + /spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 + dev: true + + /spdx-expression-parse@4.0.0: + resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 + dev: true + + /spdx-license-ids@3.0.17: + resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} + dev: true + + /split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + dependencies: + readable-stream: 3.6.2 + dev: true + + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + + /split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + dependencies: + through: 2.3.8 + dev: true + + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true + + /sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + dev: false + + /stack-generator@2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + dependencies: + stackframe: 1.3.4 + dev: false + + /stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + dev: false + + /stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 2.0.0 + dev: true + + /stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + dev: false + + /stacktrace-gps@3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + dev: false + + /stacktrace-js@2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + dev: false + + /standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + dev: false + + /standard-version@9.5.0: + resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chalk: 2.4.2 + conventional-changelog: 3.1.25 + conventional-changelog-config-spec: 2.1.0 + conventional-changelog-conventionalcommits: 4.6.3 + conventional-recommended-bump: 6.1.0 + detect-indent: 6.1.0 + detect-newline: 3.1.0 + dotgitignore: 2.1.0 + figures: 3.2.0 + find-up: 5.0.0 + git-semver-tags: 4.1.1 + semver: 7.6.0 + stringify-package: 1.0.1 + yargs: 16.2.0 + dev: true + + /static-eval@2.1.1: + resolution: {integrity: sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==} + dependencies: + escodegen: 2.1.0 + dev: true + + /static-module@3.0.4: + resolution: {integrity: sha512-gb0v0rrgpBkifXCa3yZXxqVmXDVE+ETXj6YlC/jt5VzOnGXR2C15+++eXuMDUYsePnbhf+lwW0pE1UXyOLtGCw==} + dependencies: + acorn-node: 1.8.2 + concat-stream: 1.6.2 + convert-source-map: 1.9.0 + duplexer2: 0.1.4 + escodegen: 1.14.3 + has: 1.0.4 + magic-string: 0.25.1 + merge-source-map: 1.0.4 + object-inspect: 1.13.1 + readable-stream: 2.3.8 + scope-analyzer: 2.1.2 + shallow-copy: 0.0.1 + static-eval: 2.1.1 + through2: 2.0.5 + dev: true + + /statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + /stream-combiner@0.2.2: + resolution: {integrity: sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==} + dependencies: + duplexer: 0.1.2 + through: 2.3.8 + dev: true + + /stream-wormhole@1.1.0: + resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} + engines: {node: '>=4.0.0'} + dev: false + + /string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + dev: true + + /string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + dev: true + + /string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + dev: true + + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + /string-width@7.1.0: + resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} + engines: {node: '>=18'} + dependencies: + emoji-regex: 10.3.0 + get-east-asian-width: 1.2.0 + strip-ansi: 7.1.0 + dev: true + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + + /stringify-package@1.0.1: + resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} + deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json + dev: true + + /strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} + engines: {node: '>=4'} + dependencies: + ansi-regex: 3.0.1 + dev: true + + /strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + dependencies: + ansi-regex: 4.1.1 + dev: true + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true + + /strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + dev: false + + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /superagent@8.1.2: + resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} + engines: {node: '>=6.4.0 <13 || >=14'} + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.4 + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.11.2 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: true + + /supertest@6.3.4: + resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==} + engines: {node: '>=6.4.0'} + dependencies: + methods: 1.1.2 + superagent: 8.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /svg-captcha@1.4.0: + resolution: {integrity: sha512-/fkkhavXPE57zRRCjNqAP3txRCSncpMx3NnNZL7iEoyAtYwUjPhJxW6FQTQPG5UPEmCrbFoXS10C3YdJlW7PDg==} + engines: {node: '>=4.x'} + dependencies: + opentype.js: 0.7.3 + dev: false + + /svg-pan-zoom@3.6.1: + resolution: {integrity: sha512-JaKkGHHfGvRrcMPdJWkssLBeWqM+Isg/a09H7kgNNajT1cX5AztDTNs+C8UzpCxjCTRrG34WbquwaovZbmSk9g==} + dev: true + + /swagger-ui-dist@5.11.2: + resolution: {integrity: sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==} + dev: false + + /symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + dev: true + + /synckit@0.6.2: + resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==} + engines: {node: '>=12.20'} + dependencies: + tslib: 2.6.2 + dev: true + + /systeminformation@5.22.0: + resolution: {integrity: sha512-oAP80ymt8ssrAzjX8k3frbL7ys6AotqC35oikG6/SG15wBw+tG9nCk4oPaXIhEaAOAZ8XngxUv3ORq2IuR3r4Q==} + engines: {node: '>=8.0.0'} + os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] + hasBin: true + dev: false + + /tablesort@5.3.0: + resolution: {integrity: sha512-WkfcZBHsp47gVH9CBHG0ZXopriG01IA87arGrchvIe868d4RiXVvoYPS1zMq9IdW05kBs5iGsqxTABqLyWonbg==} + dev: true + + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + + /temp@0.9.4: + resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} + engines: {node: '>=6.0.0'} + dependencies: + mkdirp: 0.5.6 + rimraf: 2.6.3 + dev: true + + /terser-webpack-plugin@5.3.10(webpack@5.90.1): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.22 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.27.1 + webpack: 5.90.1 + dev: true + + /terser@5.27.1: + resolution: {integrity: sha512-29wAr6UU/oQpnTw5HoadwjUZnFQXGdOfj0LjZ4sVxzqwHh/QVkvr7m8y9WoR4iN3FRitVduTc6KdjcW38Npsug==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.5 + acorn: 8.11.3 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: true + + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + dev: true + + /text-decoding@1.0.0: + resolution: {integrity: sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==} + dev: false + + /text-extensions@1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + dev: true + + /text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + dev: false + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: false + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: false + + /thread-stream@2.4.1: + resolution: {integrity: sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==} + dependencies: + real-require: 0.2.0 + dev: false + + /through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + dev: true + + /through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + dependencies: + readable-stream: 3.6.2 + dev: true + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + /tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + /tlds@1.248.0: + resolution: {integrity: sha512-noj0KdpWTBhwsKxMOXk0rN9otg4kTgLm4WohERRHbJ9IY+kSDKr3RmjitaQ3JFzny+DyvBOQKlFZhp0G0qNSfg==} + hasBin: true + dev: false + + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + dev: true + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + + /toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + dev: false + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + /token-stream@1.0.0: + resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} + requiresBuild: true + dev: false + + /toml-eslint-parser@0.9.3: + resolution: {integrity: sha512-moYoCvkNUAPCxSW9jmHmRElhm4tVJpHL8ItC/+uYD0EpPSFXbck7yREz9tNdJVTSpHVod8+HoipcpbQ0oE6gsw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + eslint-visitor-keys: 3.4.3 + dev: true + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + /traverse@0.6.8: + resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} + engines: {node: '>= 0.4'} + dev: true + + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + + /trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + + /triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + dev: false + + /ts-api-utils@1.2.1(typescript@5.3.3): + resolution: {integrity: sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.3.3 + dev: true + + /ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.3.3): + resolution: {integrity: sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.9 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.11.18)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.0 + typescript: 5.3.3 + yargs-parser: 21.1.1 + dev: true + + /ts-loader@9.5.1(typescript@5.3.3)(webpack@5.90.1): + resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.15.0 + micromatch: 4.0.5 + semver: 7.6.0 + source-map: 0.7.4 + typescript: 5.3.3 + webpack: 5.90.1 + dev: true + + /ts-morph@20.0.0: + resolution: {integrity: sha512-JVmEJy2Wow5n/84I3igthL9sudQ8qzjh/6i4tmYCm6IqYyKFlNbJZi7oBdjyqcWSWYRu3CtL0xbT6fS03ESZIg==} + dependencies: + '@ts-morph/common': 0.21.0 + code-block-writer: 12.0.0 + dev: true + + /ts-node@10.9.2(@types/node@20.11.18)(typescript@5.3.3): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.11.18 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + /tsconfig-paths-webpack-plugin@4.1.0: + resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} + engines: {node: '>=10.13.0'} + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.15.0 + tsconfig-paths: 4.2.0 + dev: true + + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib@2.6.0: + resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + dev: false + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /type-check@0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + + /type-fest@3.13.1: + resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} + engines: {node: '>=14.16'} + dev: true + + /type@1.2.0: + resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} + dev: true + + /type@2.7.2: + resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} + dev: true + + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: true + + /typeorm@0.3.17(ioredis@5.3.2)(mysql2@3.9.1)(ts-node@10.9.2): + resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==} + engines: {node: '>= 12.9.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.2.0 + mssql: ^9.1.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^5.1.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + dependencies: + '@sqltools/formatter': 1.2.5 + app-root-path: 3.1.0 + buffer: 6.0.3 + chalk: 4.1.2 + cli-highlight: 2.1.11 + date-fns: 2.30.0 + debug: 4.3.4 + dotenv: 16.4.4 + glob: 8.1.0 + ioredis: 5.3.2 + mkdirp: 2.1.6 + mysql2: 3.9.1 + reflect-metadata: 0.1.14 + sha.js: 2.4.11 + ts-node: 10.9.2(@types/node@20.11.18)(typescript@5.3.3) + tslib: 2.6.2 + uuid: 9.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + dev: false + + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + + /ua-parser-js@1.0.37: + resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} + dev: false + + /uc.micro@2.0.0: + resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} + dev: false + + /ufo@1.4.0: + resolution: {integrity: sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==} + dev: true + + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + + /uid2@0.0.4: + resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} + dev: false + + /uid2@1.0.0: + resolution: {integrity: sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==} + engines: {node: '>= 4.0.0'} + dev: false + + /uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + dependencies: + '@lukeed/csprng': 1.1.0 + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + /unescape@1.0.1: + resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + dev: false + + /unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: true + + /unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: true + + /unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: true + + /unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + dev: true + + /unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: true + + /unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + dev: true + + /unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + dev: true + + /unist-util-stringify-position@2.0.3: + resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + dependencies: + '@types/unist': 2.0.10 + dev: true + + /universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + dev: true + + /unix-crypt-td-js@1.1.4: + resolution: {integrity: sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==} + dev: true + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: true + + /update-browserslist-db@1.0.13(browserslist@4.23.0): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.0 + dev: true + + /upper-case@1.1.3: + resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + dev: false + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.1 + + /urllib@2.41.0: + resolution: {integrity: sha512-pNXdxEv52L67jahLT+/7QE+Fup1y2Gc6EdmrAhQ6OpQIC2rl14oWwv9hvk1GXOZqEnJNwRXHABuwgPOs1CtL7g==} + engines: {node: '>= 0.10.0'} + peerDependencies: + proxy-agent: ^5.0.0 + peerDependenciesMeta: + proxy-agent: + optional: true + dependencies: + any-promise: 1.3.0 + content-type: 1.0.5 + debug: 2.6.9 + default-user-agent: 1.0.0 + digest-header: 1.1.0 + ee-first: 1.1.1 + formstream: 1.3.1 + humanize-ms: 1.2.1 + iconv-lite: 0.4.24 + ip: 1.1.8 + pump: 3.0.0 + qs: 6.11.2 + statuses: 1.5.0 + utility: 1.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /user-home@2.0.0: + resolution: {integrity: sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==} + engines: {node: '>=0.10.0'} + dependencies: + os-homedir: 1.0.2 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /utility@1.18.0: + resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==} + engines: {node: '>= 0.12.0'} + dependencies: + copy-to: 2.0.1 + escape-html: 1.0.3 + mkdirp: 0.5.6 + mz: 2.7.0 + unescape: 1.0.1 + dev: false + + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + /v8-to-istanbul@9.2.0: + resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + engines: {node: '>=10.12.0'} + dependencies: + '@jridgewell/trace-mapping': 0.3.22 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + dev: true + + /valid-data-url@3.0.1: + resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==} + engines: {node: '>=10'} + dev: false + + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + dev: true + + /validator@13.11.0: + resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} + engines: {node: '>= 0.10'} + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + /vis@4.21.0-EOL: + resolution: {integrity: sha512-JVS1mywKg5S88XbkDJPfCb3n+vlg5fMA8Ae2hzs3KHAwD4ryM5qwlbFZ6ReDfY8te7I4NLCpuCoywJQEehvJlQ==} + deprecated: Please consider using https://github.com/visjs + dependencies: + emitter-component: 1.1.2 + hammerjs: 2.0.8 + keycharm: 0.2.0 + moment: 2.30.1 + propagating-hammerjs: 1.5.0 + dev: true + + /void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dev: false + + /vue-eslint-parser@9.4.2(eslint@8.56.0): + resolution: {integrity: sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.4 + eslint: 8.56.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + lodash: 4.17.21 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + dev: true + + /walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + dependencies: + makeerror: 1.0.12 + dev: true + + /watchpack@2.4.0: + resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} + engines: {node: '>=10.13.0'} + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + dev: true + + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + + /web-resource-inliner@6.0.1: + resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==} + engines: {node: '>=10.0.0'} + dependencies: + ansi-colors: 4.1.3 + escape-goat: 3.0.0 + htmlparser2: 5.0.1 + mime: 2.6.0 + node-fetch: 2.7.0 + valid-data-url: 3.0.1 + transitivePeerDependencies: + - encoding + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + /webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + dev: true + + /webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + dev: true + + /webpack@5.90.1: + resolution: {integrity: sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.5 + '@webassemblyjs/ast': 1.11.6 + '@webassemblyjs/wasm-edit': 1.11.6 + '@webassemblyjs/wasm-parser': 1.11.6 + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + browserslist: 4.23.0 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.15.0 + es-module-lexer: 1.4.1 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.90.1) + watchpack: 2.4.0 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + dev: true + + /websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + dependencies: + http-parser-js: 0.5.8 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + dev: true + + /websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + dev: true + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + dependencies: + string-width: 4.2.3 + dev: false + + /win-release@1.1.1: + resolution: {integrity: sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==} + engines: {node: '>=0.10.0'} + dependencies: + semver: 5.7.2 + dev: false + + /windows-release@4.0.0: + resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==} + engines: {node: '>=10'} + dependencies: + execa: 4.1.0 + dev: true + + /winston-daily-rotate-file@5.0.0(winston@3.11.0): + resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} + engines: {node: '>=8'} + peerDependencies: + winston: ^3 + dependencies: + file-stream-rotator: 0.6.1 + object-hash: 3.0.0 + triple-beam: 1.4.1 + winston: 3.11.0 + winston-transport: 4.7.0 + dev: false + + /winston-transport@4.7.0: + resolution: {integrity: sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==} + engines: {node: '>= 12.0.0'} + dependencies: + logform: 2.6.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + dev: false + + /winston@3.11.0: + resolution: {integrity: sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==} + engines: {node: '>= 12.0.0'} + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.0 + dev: false + + /with@7.0.2: + resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + '@babel/parser': 7.23.9 + '@babel/types': 7.23.9 + assert-never: 1.2.1 + babel-walk: 3.0.0-canary-5 + dev: false + + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + /wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + dependencies: + ansi-styles: 6.2.1 + string-width: 7.1.0 + strip-ansi: 7.1.0 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: true + + /ws@8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} + 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 + + /xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + dev: true + + /xmldoc@1.3.0: + resolution: {integrity: sha512-y7IRWW6PvEnYQZNZFMRLNJw+p3pezM4nKYPfr15g4OOW9i8VpeydycFuipE2297OvZnh3jSb2pxOt9QpkZUVng==} + dependencies: + sax: 1.3.0 + dev: true + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true + + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yaml-eslint-parser@1.2.2: + resolution: {integrity: sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==} + engines: {node: ^14.17.0 || >=16.0.0} + dependencies: + eslint-visitor-keys: 3.4.3 + lodash: 4.17.21 + yaml: 2.3.4 + dev: true + + /yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + dev: true + + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + + /zepto@1.2.0: + resolution: {integrity: sha512-C1x6lfvBICFTQIMgbt3JqMOno3VOtkWat/xEakLTOurskYIHPmzJrzd1e8BnmtdDVJlGuk5D+FxyCA8MPmkIyA==} + dev: true diff --git a/scripts/genEnvTypes.ts b/scripts/genEnvTypes.ts new file mode 100644 index 0000000..1e90f3a --- /dev/null +++ b/scripts/genEnvTypes.ts @@ -0,0 +1,49 @@ +import fs from 'node:fs' +import path from 'node:path' + +import dotenv from 'dotenv' + +const directoryPath = path.resolve(__dirname, '..') + +const targets = ['.env', `.env.${process.env.NODE_ENV || 'development'}`] + +const envObj = targets.reduce((prev, file) => { + const result = dotenv.parse(fs.readFileSync(path.join(directoryPath, file))) + return { ...prev, ...result } +}, {}) + +const envType = Object.entries(envObj).reduce((prev, [key, value]) => { + return `${prev} + ${key}: '${value}';` +}, '').trim() + +fs.writeFile(path.join(directoryPath, 'types/env.d.ts'), ` +// generate by ./scripts/generateEnvTypes.ts +declare global { + namespace NodeJS { + interface ProcessEnv { + ${envType} + } + } +} +export {}; + `, (err) => { + if (err) + console.log('生成 env.d.ts 文件失败') + else + console.log('成功生成 env.d.ts 文件') +}) + +// console.log('envObj:', envObj) + +function formatValue(value) { + let _value + try { + const res = JSON.parse(value) + _value = typeof res === 'object' ? value : res + } + catch (error) { + _value = `'${value}'` + } + return _value +} diff --git a/scripts/resetScheduler.ts b/scripts/resetScheduler.ts new file mode 100644 index 0000000..2a85291 --- /dev/null +++ b/scripts/resetScheduler.ts @@ -0,0 +1,26 @@ +import { exec } from 'node:child_process' + +import { CronJob } from 'cron' + +/** 此文件仅供演示时使用 */ + +const runMigrationGenerate = async function () { + exec('npm run migration:revert && npm run migration:run', (error, stdout, stderr) => { + if (!error) + console.log('操作成功', error) + + else + console.log('操作失败', error) + }) +} + +const job = CronJob.from({ + /** 每天凌晨 4.30 恢复初始数据 */ + cronTime: '30 4 * * *', + timeZone: 'Asia/Shanghai', + start: true, + onTick() { + runMigrationGenerate() + console.log('Task executed daily at 4.30 AM:', new Date().toLocaleTimeString()) + }, +}) diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..34b38f7 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,67 @@ +import { ClassSerializerInterceptor, Module } from '@nestjs/common' + +import { ConfigModule } from '@nestjs/config' +import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core' + +import config from '~/config' +import { SharedModule } from '~/shared/shared.module' + +import { AllExceptionsFilter } from './common/filters/any-exception.filter' + +import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor' +import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor' +import { TransformInterceptor } from './common/interceptors/transform.interceptor' +import { AuthModule } from './modules/auth/auth.module' +import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard' +import { RbacGuard } from './modules/auth/guards/rbac.guard' +import { HealthModule } from './modules/health/health.module' +import { NetdiskModule } from './modules/netdisk/netdisk.module' +import { SseModule } from './modules/sse/sse.module' +import { SystemModule } from './modules/system/system.module' +import { TasksModule } from './modules/tasks/tasks.module' +import { TodoModule } from './modules/todo/todo.module' +import { ToolsModule } from './modules/tools/tools.module' +import { DatabaseModule } from './shared/database/database.module' + +import { SocketModule } from './socket/socket.module' + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + expandVariables: true, + // 指定多个 env 文件时,第一个优先级最高 + envFilePath: ['.env.local', `.env.${process.env.NODE_ENV}`, '.env'], + load: [...Object.values(config)], + }), + SharedModule, + DatabaseModule, + + AuthModule, + SystemModule, + TasksModule.forRoot(), + ToolsModule, + SocketModule, + HealthModule, + SseModule, + NetdiskModule, + + // biz + + // end biz + + TodoModule, + ], + providers: [ + { provide: APP_FILTER, useClass: AllExceptionsFilter }, + + { provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor }, + { provide: APP_INTERCEPTOR, useClass: TransformInterceptor }, + { provide: APP_INTERCEPTOR, useFactory: () => new TimeoutInterceptor(15 * 1000) }, + { provide: APP_INTERCEPTOR, useClass: IdempotenceInterceptor }, + + { provide: APP_GUARD, useClass: JwtAuthGuard }, + { provide: APP_GUARD, useClass: RbacGuard }, + ], +}) +export class AppModule {} diff --git a/src/assets/templates/verification-code-zh.hbs b/src/assets/templates/verification-code-zh.hbs new file mode 100644 index 0000000..df3702a --- /dev/null +++ b/src/assets/templates/verification-code-zh.hbs @@ -0,0 +1,4 @@ +

你的验证码是:

+

{{code}}

+

该验证码 10 分钟内有效,请勿将验证码告知给他人!

+本邮件由系统自动发出,请勿回复。 \ No newline at end of file diff --git a/src/assets/templates/verification-code.hbs b/src/assets/templates/verification-code.hbs new file mode 100644 index 0000000..6545c27 --- /dev/null +++ b/src/assets/templates/verification-code.hbs @@ -0,0 +1,5 @@ +

Your verification code is:

+

{{verificationCode}}

+

This code will expire in 10 minutes.

+This email is sent automatically by the system, please do not + reply. \ No newline at end of file diff --git a/src/common/adapters/fastify.adapter.ts b/src/common/adapters/fastify.adapter.ts new file mode 100644 index 0000000..e25d0ca --- /dev/null +++ b/src/common/adapters/fastify.adapter.ts @@ -0,0 +1,46 @@ +import FastifyCookie from '@fastify/cookie' +import FastifyMultipart from '@fastify/multipart' +import { FastifyAdapter } from '@nestjs/platform-fastify' + +const app: FastifyAdapter = new FastifyAdapter({ + trustProxy: true, + logger: false, + // forceCloseConnections: true, +}) +export { app as fastifyApp } + +app.register(FastifyMultipart, { + limits: { + fields: 10, // Max number of non-file fields + fileSize: 1024 * 1024 * 6, // limit size 6M + files: 5, // Max number of file fields + }, +}) + +app.register(FastifyCookie, { + secret: 'cookie-secret', // 这个 secret 不太重要,不存鉴权相关,无关紧要 +}) + +app.getInstance().addHook('onRequest', (request, reply, done) => { + // set undefined origin + const { origin } = request.headers + if (!origin) + request.headers.origin = request.headers.host + + // forbidden php + + const { url } = request + + if (url.endsWith('.php')) { + reply.raw.statusMessage + = 'Eh. PHP is not support on this machine. Yep, I also think PHP is bestest programming language. But for me it is beyond my reach.' + + return reply.code(418).send() + } + + // skip favicon request + if (url.match(/favicon.ico$/) || url.match(/manifest.json$/)) + return reply.code(204).send() + + done() +}) diff --git a/src/common/adapters/socket.adapter.ts b/src/common/adapters/socket.adapter.ts new file mode 100644 index 0000000..d766592 --- /dev/null +++ b/src/common/adapters/socket.adapter.ts @@ -0,0 +1,26 @@ +import { INestApplication } from '@nestjs/common' +import { IoAdapter } from '@nestjs/platform-socket.io' +import { createAdapter } from '@socket.io/redis-adapter' + +import { REDIS_PUBSUB } from '~/shared/redis/redis.constant' + +export const RedisIoAdapterKey = 'm-shop-socket' + +export class RedisIoAdapter extends IoAdapter { + constructor(private readonly app: INestApplication) { + super(app) + } + + createIOServer(port: number, options?: any) { + const server = super.createIOServer(port, options) + + const { pubClient, subClient } = this.app.get(REDIS_PUBSUB) + + const redisAdapter = createAdapter(pubClient, subClient, { + key: RedisIoAdapterKey, + requestsTimeout: 10000, + }) + server.adapter(redisAdapter) + return server + } +} diff --git a/src/common/decorators/api-result.decorator.ts b/src/common/decorators/api-result.decorator.ts new file mode 100644 index 0000000..1892762 --- /dev/null +++ b/src/common/decorators/api-result.decorator.ts @@ -0,0 +1,83 @@ +import { HttpStatus, Type, applyDecorators } from '@nestjs/common' +import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger' + +import { ResOp } from '~/common/model/response.model' + +const baseTypeNames = ['String', 'Number', 'Boolean'] + +function genBaseProp(type: Type) { + if (baseTypeNames.includes(type.name)) + return { type: type.name.toLocaleLowerCase() } + else + return { $ref: getSchemaPath(type) } +} + +/** + * @description: 生成返回结果装饰器 + */ +export function ApiResult>({ + type, + isPage, + status, +}: { + type?: TModel | TModel[] + isPage?: boolean + status?: HttpStatus +}) { + let prop = null + + if (Array.isArray(type)) { + if (isPage) { + prop = { + type: 'object', + properties: { + items: { + type: 'array', + items: { $ref: getSchemaPath(type[0]) }, + }, + meta: { + type: 'object', + properties: { + itemCount: { type: 'number', default: 0 }, + totalItems: { type: 'number', default: 0 }, + itemsPerPage: { type: 'number', default: 0 }, + totalPages: { type: 'number', default: 0 }, + currentPage: { type: 'number', default: 0 }, + }, + }, + }, + } + } + else { + prop = { + type: 'array', + items: genBaseProp(type[0]), + } + } + } + else if (type) { + prop = genBaseProp(type) + } + else { + prop = { type: 'null', default: null } + } + + const model = Array.isArray(type) ? type[0] : type + + return applyDecorators( + ApiExtraModels(model), + ApiResponse({ + status, + schema: { + allOf: [ + { $ref: getSchemaPath(ResOp) }, + { + properties: { + data: prop, + }, + }, + ], + }, + }), + ) +} diff --git a/src/common/decorators/bypass.decorator.ts b/src/common/decorators/bypass.decorator.ts new file mode 100644 index 0000000..8c7562d --- /dev/null +++ b/src/common/decorators/bypass.decorator.ts @@ -0,0 +1,10 @@ +import { SetMetadata } from '@nestjs/common' + +export const BYPASS_KEY = '__bypass_key__' + +/** + * 当不需要转换成基础返回格式时添加该装饰器 + */ +export function Bypass() { + return SetMetadata(BYPASS_KEY, true) +} diff --git a/src/common/decorators/cookie.decorator.ts b/src/common/decorators/cookie.decorator.ts new file mode 100644 index 0000000..102a527 --- /dev/null +++ b/src/common/decorators/cookie.decorator.ts @@ -0,0 +1,8 @@ +import type { ExecutionContext } from '@nestjs/common' +import { createParamDecorator } from '@nestjs/common' +import type { FastifyRequest } from 'fastify' + +export const Cookies = createParamDecorator((data: string, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest() + return data ? request.cookies?.[data] : request.cookies +}) diff --git a/src/common/decorators/cron-once.decorator.ts b/src/common/decorators/cron-once.decorator.ts new file mode 100644 index 0000000..39eab20 --- /dev/null +++ b/src/common/decorators/cron-once.decorator.ts @@ -0,0 +1,19 @@ +import cluster from 'node:cluster' + +import { Cron } from '@nestjs/schedule' + +import { isMainProcess } from '~/global/env' + +export const CronOnce: typeof Cron = (...rest): MethodDecorator => { + // If not in cluster mode, and PM2 main worker + if (isMainProcess) + // eslint-disable-next-line no-useless-call + return Cron.call(null, ...rest) + + if (cluster.isWorker && cluster.worker?.id === 1) + // eslint-disable-next-line no-useless-call + return Cron.call(null, ...rest) + + const returnNothing: MethodDecorator = () => {} + return returnNothing +} diff --git a/src/common/decorators/field.decorator.ts b/src/common/decorators/field.decorator.ts new file mode 100644 index 0000000..68fa483 --- /dev/null +++ b/src/common/decorators/field.decorator.ts @@ -0,0 +1,137 @@ +import { applyDecorators } from '@nestjs/common' +import { + IsBoolean, + IsDate, + IsInt, + IsNotEmpty, + IsNumber, + IsOptional, + IsPositive, + IsString, + Max, + MaxLength, + Min, + MinLength, +} from 'class-validator' +import { isNumber } from 'lodash' + +import { + ToArray, + ToBoolean, + ToDate, + ToLowerCase, + ToNumber, + ToTrim, + ToUpperCase, +} from './transform.decorator' + +interface IOptionalOptions { + required?: boolean +} + +interface INumberFieldOptions extends IOptionalOptions { + each?: boolean + int?: boolean + min?: number + max?: number + positive?: boolean +} + +interface IStringFieldOptions extends IOptionalOptions { + each?: boolean + minLength?: number + maxLength?: number + lowerCase?: boolean + upperCase?: boolean +} + +export function NumberField( + options: INumberFieldOptions = {}, +): PropertyDecorator { + const { each, min, max, int, positive, required = true } = options + + const decorators = [ToNumber()] + + if (each) + decorators.push(ToArray()) + + if (int) + decorators.push(IsInt({ each })) + else + decorators.push(IsNumber({}, { each })) + + if (isNumber(min)) + decorators.push(Min(min, { each })) + + if (isNumber(max)) + decorators.push(Max(max, { each })) + + if (positive) + decorators.push(IsPositive({ each })) + + if (!required) + decorators.push(IsOptional()) + + return applyDecorators(...decorators) +} + +export function StringField( + options: IStringFieldOptions = {}, +): PropertyDecorator { + const { + each, + minLength, + maxLength, + lowerCase, + upperCase, + required = true, + } = options + + const decorators = [IsString({ each }), ToTrim()] + + if (each) + decorators.push(ToArray()) + + if (isNumber(minLength)) + decorators.push(MinLength(minLength, { each })) + + if (isNumber(maxLength)) + decorators.push(MaxLength(maxLength, { each })) + + if (lowerCase) + decorators.push(ToLowerCase()) + + if (upperCase) + decorators.push(ToUpperCase()) + + if (!required) + decorators.push(IsOptional()) + else + decorators.push(IsNotEmpty({ each })) + + return applyDecorators(...decorators) +} + +export function BooleanField( + options: IOptionalOptions = {}, +): PropertyDecorator { + const decorators = [ToBoolean(), IsBoolean()] + + const { required = true } = options + + if (!required) + decorators.push(IsOptional()) + + return applyDecorators(...decorators) +} + +export function DateField(options: IOptionalOptions = {}): PropertyDecorator { + const decorators = [ToDate(), IsDate()] + + const { required = true } = options + + if (!required) + decorators.push(IsOptional()) + + return applyDecorators(...decorators) +} diff --git a/src/common/decorators/http.decorator.ts b/src/common/decorators/http.decorator.ts new file mode 100644 index 0000000..85ed449 --- /dev/null +++ b/src/common/decorators/http.decorator.ts @@ -0,0 +1,22 @@ +import type { ExecutionContext } from '@nestjs/common' + +import { createParamDecorator } from '@nestjs/common' +import type { FastifyRequest } from 'fastify' + +import { getIp } from '~/utils/ip.util' + +/** + * 快速获取IP + */ +export const Ip = createParamDecorator((_, context: ExecutionContext) => { + const request = context.switchToHttp().getRequest() + return getIp(request) +}) + +/** + * 快速获取request path,并不包括url params + */ +export const Uri = createParamDecorator((_, context: ExecutionContext) => { + const request = context.switchToHttp().getRequest() + return request.routerPath +}) diff --git a/src/common/decorators/id-param.decorator.ts b/src/common/decorators/id-param.decorator.ts new file mode 100644 index 0000000..fade33c --- /dev/null +++ b/src/common/decorators/id-param.decorator.ts @@ -0,0 +1,7 @@ +import { HttpStatus, NotAcceptableException, Param, ParseIntPipe } from '@nestjs/common' + +export function IdParam() { + return Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE, exceptionFactory: (_error) => { + throw new NotAcceptableException('id 格式不正确') + } })) +} diff --git a/src/common/decorators/idempotence.decorator.ts b/src/common/decorators/idempotence.decorator.ts new file mode 100644 index 0000000..d1142b3 --- /dev/null +++ b/src/common/decorators/idempotence.decorator.ts @@ -0,0 +1,15 @@ +import { SetMetadata } from '@nestjs/common' + +import { IdempotenceOption } from '../interceptors/idempotence.interceptor' + +export const HTTP_IDEMPOTENCE_KEY = '__idempotence_key__' +export const HTTP_IDEMPOTENCE_OPTIONS = '__idempotence_options__' + +/** + * 幂等 + */ +export function Idempotence(options?: IdempotenceOption): MethodDecorator { + return function (target, key, descriptor: PropertyDescriptor) { + SetMetadata(HTTP_IDEMPOTENCE_OPTIONS, options || {})(descriptor.value) + } +} diff --git a/src/common/decorators/swagger.decorator.ts b/src/common/decorators/swagger.decorator.ts new file mode 100644 index 0000000..4809e5c --- /dev/null +++ b/src/common/decorators/swagger.decorator.ts @@ -0,0 +1,11 @@ +import { applyDecorators } from '@nestjs/common' +import { ApiSecurity } from '@nestjs/swagger' + +export const API_SECURITY_AUTH = 'auth' + +/** + * like to @ApiSecurity('auth') + */ +export function ApiSecurityAuth(): ClassDecorator & MethodDecorator { + return applyDecorators(ApiSecurity(API_SECURITY_AUTH)) +} diff --git a/src/common/decorators/transform.decorator.ts b/src/common/decorators/transform.decorator.ts new file mode 100644 index 0000000..e86cf75 --- /dev/null +++ b/src/common/decorators/transform.decorator.ts @@ -0,0 +1,146 @@ +import { Transform } from 'class-transformer' +import { castArray, isArray, isNil, trim } from 'lodash' + +/** + * convert string to number + */ +export function ToNumber(): PropertyDecorator { + return Transform( + (params) => { + const value = params.value as string[] | string + + if (isArray(value)) + return value.map(v => Number(v)) + + return Number(value) + }, + { toClassOnly: true }, + ) +} + +/** + * convert string to int + */ +export function ToInt(): PropertyDecorator { + return Transform( + (params) => { + const value = params.value as string[] | string + + if (isArray(value)) + return value.map(v => Number.parseInt(v)) + + return Number.parseInt(value) + }, + { toClassOnly: true }, + ) +} + +/** + * convert string to boolean + */ +export function ToBoolean(): PropertyDecorator { + return Transform( + (params) => { + switch (params.value) { + case 'true': + return true + case 'false': + return false + default: + return params.value + } + }, + { toClassOnly: true }, + ) +} + +/** + * convert string to Date + */ +export function ToDate(): PropertyDecorator { + return Transform( + (params) => { + const { value } = params + + if (!value) + return + + return new Date(value) + }, + { toClassOnly: true }, + ) +} + +/** + * transforms to array, specially for query params + */ +export function ToArray(): PropertyDecorator { + return Transform( + (params) => { + const { value } = params + + if (isNil(value)) + return [] + + return castArray(value) + }, + { toClassOnly: true }, + ) +} + +/** + * trim spaces from start and end, replace multiple spaces with one. + */ +export function ToTrim(): PropertyDecorator { + return Transform( + (params) => { + const value = params.value as string[] | string + + if (isArray(value)) + return value.map(v => trim(v)) + + return trim(value) + }, + { toClassOnly: true }, + ) +} + +/** + * lowercase value + */ +export function ToLowerCase(): PropertyDecorator { + return Transform( + (params) => { + const value = params.value as string[] | string + + if (!value) + return + + if (isArray(value)) + return value.map(v => v.toLowerCase()) + + return value.toLowerCase() + }, + { toClassOnly: true }, + ) +} + +/** + * uppercase value + */ +export function ToUpperCase(): PropertyDecorator { + return Transform( + (params) => { + const value = params.value as string[] | string + + if (!value) + return + + if (isArray(value)) + return value.map(v => v.toUpperCase()) + + return value.toUpperCase() + }, + { toClassOnly: true }, + ) +} diff --git a/src/common/dto/cursor.dto.ts b/src/common/dto/cursor.dto.ts new file mode 100644 index 0000000..50c816c --- /dev/null +++ b/src/common/dto/cursor.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Expose, Transform } from 'class-transformer' +import { IsInt, IsOptional, Max, Min } from 'class-validator' + +export class CursorDto { + @ApiProperty({ minimum: 0, default: 0 }) + @Min(0) + @IsInt() + @Expose() + @IsOptional({ always: true }) + @Transform(({ value: val }) => (val ? Number.parseInt(val) : 0), { + toClassOnly: true, + }) + cursor?: number + + @ApiProperty({ minimum: 1, maximum: 100, default: 10 }) + @Min(1) + @Max(100) + @IsInt() + @IsOptional({ always: true }) + @Expose() + @Transform(({ value: val }) => (val ? Number.parseInt(val) : 10), { + toClassOnly: true, + }) + limit?: number +} diff --git a/src/common/dto/delete.dto.ts b/src/common/dto/delete.dto.ts new file mode 100644 index 0000000..5157aae --- /dev/null +++ b/src/common/dto/delete.dto.ts @@ -0,0 +1,8 @@ +import { IsDefined, IsNotEmpty, IsNumber } from 'class-validator' + +export class BatchDeleteDto { + @IsDefined() + @IsNotEmpty() + @IsNumber({}, { each: true }) + ids: number[] +} diff --git a/src/common/dto/id.dto.ts b/src/common/dto/id.dto.ts new file mode 100644 index 0000000..65898b1 --- /dev/null +++ b/src/common/dto/id.dto.ts @@ -0,0 +1,6 @@ +import { IsNumber } from 'class-validator' + +export class IdDto { + @IsNumber() + id: number +} diff --git a/src/common/dto/pager.dto.ts b/src/common/dto/pager.dto.ts new file mode 100644 index 0000000..95abdaf --- /dev/null +++ b/src/common/dto/pager.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Expose, Transform } from 'class-transformer' +import { Allow, IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator' + +export enum Order { + ASC = 'ASC', + DESC = 'DESC', +} + +export class PagerDto { + @ApiProperty({ minimum: 1, default: 1 }) + @Min(1) + @IsInt() + @Expose() + @IsOptional({ always: true }) + @Transform(({ value: val }) => (val ? Number.parseInt(val) : 1), { + toClassOnly: true, + }) + page?: number + + @ApiProperty({ minimum: 1, maximum: 100, default: 10 }) + @Min(1) + @Max(100) + @IsInt() + @IsOptional({ always: true }) + @Expose() + @Transform(({ value: val }) => (val ? Number.parseInt(val) : 10), { + toClassOnly: true, + }) + pageSize?: number + + @ApiProperty() + @IsString() + @IsOptional() + field?: string // | keyof T + + @ApiProperty({ enum: Order }) + @IsEnum(Order) + @IsOptional() + @Transform(({ value }) => (value === 'asc' ? Order.ASC : Order.DESC)) + order?: Order + + @Allow() + _t?: number +} diff --git a/src/common/entity/common.entity.ts b/src/common/entity/common.entity.ts new file mode 100644 index 0000000..b6a9e32 --- /dev/null +++ b/src/common/entity/common.entity.ts @@ -0,0 +1,55 @@ +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' +import { Exclude } from 'class-transformer' +import { + BaseEntity, + Column, + CreateDateColumn, + PrimaryGeneratedColumn, + UpdateDateColumn, + VirtualColumn, +} from 'typeorm' + +// 如果觉得前端转换时间太麻烦,并且不考虑通用性的话,可以在服务端进行转换,eg: @UpdateDateColumn({ name: 'updated_at', transformer }) +// const transformer: ValueTransformer = { +// to(value) { +// return value +// }, +// from(value) { +// return dayjs(value).format('YYYY-MM-DD HH:mm:ss') +// }, +// } + +export abstract class CommonEntity extends BaseEntity { + @PrimaryGeneratedColumn() + id: number + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date +} + +export abstract class CompleteEntity extends CommonEntity { + @ApiHideProperty() + @Exclude() + @Column({ name: 'create_by', update: false, comment: '创建者' }) + createBy: number + + @ApiHideProperty() + @Exclude() + @Column({ name: 'update_by', comment: '更新者' }) + updateBy: number + + /** + * 不会保存到数据库中的虚拟列,数据量大时可能会有性能问题,有性能要求请考虑在 service 层手动实现 + * @see https://typeorm.io/decorator-reference#virtualcolumn + */ + @ApiProperty({ description: '创建者' }) + @VirtualColumn({ query: alias => `SELECT username FROM sys_user WHERE id = ${alias}.create_by` }) + creator: string + + @ApiProperty({ description: '更新者' }) + @VirtualColumn({ query: alias => `SELECT username FROM sys_user WHERE id = ${alias}.update_by` }) + updater: string +} diff --git a/src/common/exceptions/biz.exception.ts b/src/common/exceptions/biz.exception.ts new file mode 100644 index 0000000..f33ee3a --- /dev/null +++ b/src/common/exceptions/biz.exception.ts @@ -0,0 +1,40 @@ +import { HttpException, HttpStatus } from '@nestjs/common' + +import { ErrorEnum } from '~/constants/error-code.constant' +import { RESPONSE_SUCCESS_CODE } from '~/constants/response.constant' + +export class BusinessException extends HttpException { + private errorCode: number + + constructor(error: ErrorEnum | string) { + // 如果是非 ErrorEnum + if (!error.includes(':')) { + super( + HttpException.createBody({ + code: RESPONSE_SUCCESS_CODE, + message: error, + }), + HttpStatus.OK, + ) + this.errorCode = RESPONSE_SUCCESS_CODE + return + } + + const [code, message] = error.split(':') + super( + HttpException.createBody({ + code, + message, + }), + HttpStatus.OK, + ) + + this.errorCode = Number(code) + } + + getErrorCode(): number { + return this.errorCode + } +} + +export { BusinessException as BizException } diff --git a/src/common/exceptions/not-found.exception.ts b/src/common/exceptions/not-found.exception.ts new file mode 100644 index 0000000..c6cb536 --- /dev/null +++ b/src/common/exceptions/not-found.exception.ts @@ -0,0 +1,10 @@ +import { NotFoundException } from '@nestjs/common' +import { sample } from 'lodash' + +export const NotFoundMessage = ['404, Not Found'] + +export class CannotFindException extends NotFoundException { + constructor() { + super(sample(NotFoundMessage)) + } +} diff --git a/src/common/exceptions/socket.exception.ts b/src/common/exceptions/socket.exception.ts new file mode 100644 index 0000000..e790844 --- /dev/null +++ b/src/common/exceptions/socket.exception.ts @@ -0,0 +1,38 @@ +import { HttpException } from '@nestjs/common' +import { WsException } from '@nestjs/websockets' + +import { ErrorEnum } from '~/constants/error-code.constant' + +export class SocketException extends WsException { + private errorCode: number + + constructor(message: string) + constructor(error: ErrorEnum) + constructor(...args: any) { + const error = args[0] + if (typeof error === 'string') { + super( + HttpException.createBody({ + code: 0, + message: error, + }), + ) + this.errorCode = 0 + return + } + + const [code, message] = error.split(':') + super( + HttpException.createBody({ + code, + message, + }), + ) + + this.errorCode = Number(code) + } + + getErrorCode(): number { + return this.errorCode + } +} diff --git a/src/common/filters/any-exception.filter.ts b/src/common/filters/any-exception.filter.ts new file mode 100644 index 0000000..8bf04cd --- /dev/null +++ b/src/common/filters/any-exception.filter.ts @@ -0,0 +1,89 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus, + Logger, +} from '@nestjs/common' +import { FastifyReply, FastifyRequest } from 'fastify' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' + +import { isDev } from '~/global/env' + +interface myError { + readonly status: number + readonly statusCode?: number + + readonly message?: string +} + +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + private readonly logger = new Logger(AllExceptionsFilter.name) + + constructor() { + this.registerCatchAllExceptionsHook() + } + + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp() + const request = ctx.getRequest() + const response = ctx.getResponse() + + const url = request.raw.url! + + const status + = exception instanceof HttpException + ? exception.getStatus() + : (exception as myError)?.status + || (exception as myError)?.statusCode + || HttpStatus.INTERNAL_SERVER_ERROR + + let message + = (exception as any)?.response?.message + || (exception as myError)?.message + || `${exception}` + + // 系统内部错误时 + if ( + status === HttpStatus.INTERNAL_SERVER_ERROR + && !(exception instanceof BusinessException) + ) { + Logger.error(exception, undefined, 'Catch') + + // 生产环境下隐藏错误信息 + if (!isDev) + message = ErrorEnum.SERVER_ERROR?.split(':')[1] + } + else { + this.logger.warn( + `错误信息:(${status}) ${message} Path: ${decodeURI(url)}`, + ) + } + + const apiErrorCode: number + = exception instanceof BusinessException ? exception.getErrorCode() : status + + // 返回基础响应结果 + const resBody: IBaseResponse = { + code: apiErrorCode, + message, + data: null, + } + + response.status(status).send(resBody) + } + + registerCatchAllExceptionsHook() { + process.on('unhandledRejection', (reason) => { + console.error('unhandledRejection: ', reason) + }) + + process.on('uncaughtException', (err) => { + console.error('uncaughtException: ', err) + }) + } +} diff --git a/src/common/interceptors/idempotence.interceptor.ts b/src/common/interceptors/idempotence.interceptor.ts new file mode 100644 index 0000000..a1f11d3 --- /dev/null +++ b/src/common/interceptors/idempotence.interceptor.ts @@ -0,0 +1,148 @@ +import type { + CallHandler, + ExecutionContext, + NestInterceptor, +} from '@nestjs/common' + +import { + ConflictException, + Injectable, + SetMetadata, +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import type { FastifyRequest } from 'fastify' +import { catchError, tap } from 'rxjs' + +import { CacheService } from '~/shared/redis/cache.service' +import { hashString } from '~/utils' +import { getIp } from '~/utils/ip.util' +import { getRedisKey } from '~/utils/redis.util' + +import { HTTP_IDEMPOTENCE_KEY, HTTP_IDEMPOTENCE_OPTIONS } from '../decorators/idempotence.decorator' + +const IdempotenceHeaderKey = 'x-idempotence' + +export interface IdempotenceOption { + errorMessage?: string + pendingMessage?: string + + /** + * 如果重复请求的话,手动处理异常 + */ + handler?: (req: FastifyRequest) => any + + /** + * 记录重复请求的时间 + * @default 60 + */ + expired?: number + + /** + * 如果 header 没有幂等 key,根据 request 生成 key,如何生成这个 key 的方法 + */ + generateKey?: (req: FastifyRequest) => string + + /** + * 仅读取 header 的 key,不自动生成 + * @default false + */ + disableGenerateKey?: boolean +} + +@Injectable() +export class IdempotenceInterceptor implements NestInterceptor { + constructor( + private readonly reflector: Reflector, + private readonly cacheService: CacheService, + ) {} + + async intercept(context: ExecutionContext, next: CallHandler) { + const request = context.switchToHttp().getRequest() + + // skip Get 请求 + if (request.method.toUpperCase() === 'GET') + return next.handle() + + const handler = context.getHandler() + const options: IdempotenceOption | undefined = this.reflector.get( + HTTP_IDEMPOTENCE_OPTIONS, + handler, + ) + + if (!options) + return next.handle() + + const { + errorMessage = '相同请求成功后在 60 秒内只能发送一次', + pendingMessage = '相同请求正在处理中...', + handler: errorHandler, + expired = 60, + disableGenerateKey = false, + } = options + const redis = this.cacheService.getClient() + + const idempotence = request.headers[IdempotenceHeaderKey] as string + const key = disableGenerateKey + ? undefined + : options.generateKey + ? options.generateKey(request) + : this.generateKey(request) + + const idempotenceKey + = !!(idempotence || key) && getRedisKey(`idempotence:${idempotence || key}`) + + SetMetadata(HTTP_IDEMPOTENCE_KEY, idempotenceKey)(handler) + + if (idempotenceKey) { + const resultValue: '0' | '1' | null = (await redis.get( + idempotenceKey, + )) as any + if (resultValue !== null) { + if (errorHandler) + return await errorHandler(request) + + const message = { + 1: errorMessage, + 0: pendingMessage, + }[resultValue] + throw new ConflictException(message) + } + else { + await redis.set(idempotenceKey, '0', 'EX', expired) + } + } + return next.handle().pipe( + tap(async () => { + idempotenceKey && (await redis.set(idempotenceKey, '1', 'KEEPTTL')) + }), + catchError(async (err) => { + if (idempotenceKey) + await redis.del(idempotenceKey) + + throw err + }), + ) + } + + private generateKey(req: FastifyRequest) { + const { body, params, query = {}, headers, url } = req + + const obj = { body, url, params, query } as any + + const uuid = headers['x-uuid'] + if (uuid) { + obj.uuid = uuid + } + else { + const ua = headers['user-agent'] + const ip = getIp(req) + + if (!ua && !ip) + return undefined + + Object.assign(obj, { ua, ip }) + } + + return hashString(JSON.stringify(obj)) + } +} diff --git a/src/common/interceptors/logging.interceptor.ts b/src/common/interceptors/logging.interceptor.ts new file mode 100644 index 0000000..de54623 --- /dev/null +++ b/src/common/interceptors/logging.interceptor.ts @@ -0,0 +1,35 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + Logger, + NestInterceptor, +} from '@nestjs/common' +import { Observable, tap } from 'rxjs' + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + private logger = new Logger(LoggingInterceptor.name, { timestamp: false }) + + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable { + const call$ = next.handle() + const request = context.switchToHttp().getRequest() + const content = `${request.method} -> ${request.url}` + const isSse = request.headers.accept === 'text/event-stream' + this.logger.debug(`+++ 请求:${content}`) + const now = Date.now() + + return call$.pipe( + tap(() => { + if (isSse) + return + + this.logger.debug(`--- 响应:${content}${` +${Date.now() - now}ms`}`) + }, + ), + ) + } +} diff --git a/src/common/interceptors/timeout.interceptor.ts b/src/common/interceptors/timeout.interceptor.ts new file mode 100644 index 0000000..8508c58 --- /dev/null +++ b/src/common/interceptors/timeout.interceptor.ts @@ -0,0 +1,26 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, + RequestTimeoutException, +} from '@nestjs/common' +import { Observable, TimeoutError, throwError } from 'rxjs' +import { catchError, timeout } from 'rxjs/operators' + +@Injectable() +export class TimeoutInterceptor implements NestInterceptor { + constructor(private readonly time: number = 10000) {} + + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + timeout(this.time), + catchError((err) => { + if (err instanceof TimeoutError) + return throwError(new RequestTimeoutException('请求超时')) + + return throwError(err) + }), + ) + } +} diff --git a/src/common/interceptors/transform.interceptor.ts b/src/common/interceptors/transform.interceptor.ts new file mode 100644 index 0000000..4b59fbe --- /dev/null +++ b/src/common/interceptors/transform.interceptor.ts @@ -0,0 +1,46 @@ +import { + CallHandler, + ExecutionContext, + HttpStatus, + Injectable, + NestInterceptor, +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { Observable } from 'rxjs' +import { map } from 'rxjs/operators' + +import { ResOp } from '~/common/model/response.model' + +import { BYPASS_KEY } from '../decorators/bypass.decorator' + +/** + * 统一处理返回接口结果,如果不需要则添加 @Bypass 装饰器 + */ +@Injectable() +export class TransformInterceptor implements NestInterceptor { + constructor(private readonly reflector: Reflector) {} + + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable { + const bypass = this.reflector.get( + BYPASS_KEY, + context.getHandler(), + ) + + if (bypass) + return next.handle() + + return next.handle().pipe( + map((data) => { + // if (typeof data === 'undefined') { + // context.switchToHttp().getResponse().status(HttpStatus.NO_CONTENT); + // return data; + // } + + return new ResOp(HttpStatus.OK, data ?? null) + }), + ) + } +} diff --git a/src/common/model/response.model.ts b/src/common/model/response.model.ts new file mode 100644 index 0000000..0336456 --- /dev/null +++ b/src/common/model/response.model.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger' + +import { + RESPONSE_SUCCESS_CODE, + RESPONSE_SUCCESS_MSG, +} from '~/constants/response.constant' + +export class ResOp { + @ApiProperty({ type: 'object' }) + data?: T + + @ApiProperty({ type: 'number', default: RESPONSE_SUCCESS_CODE }) + code: number + + @ApiProperty({ type: 'string', default: RESPONSE_SUCCESS_MSG }) + message: string + + constructor(code: number, data: T, message = RESPONSE_SUCCESS_MSG) { + this.code = code + this.data = data + this.message = message + } + + static success(data?: T, message?: string) { + return new ResOp(RESPONSE_SUCCESS_CODE, data, message) + } + + static error(code: number, message) { + return new ResOp(code, {}, message) + } +} + +export class TreeResult { + @ApiProperty() + id: number + + @ApiProperty() + parentId: number + + @ApiProperty() + children?: TreeResult[] +} diff --git a/src/common/pipes/parse-int.pipe.ts b/src/common/pipes/parse-int.pipe.ts new file mode 100644 index 0000000..514d67c --- /dev/null +++ b/src/common/pipes/parse-int.pipe.ts @@ -0,0 +1,18 @@ +import { + ArgumentMetadata, + BadRequestException, + Injectable, + PipeTransform, +} from '@nestjs/common' + +@Injectable() +export class ParseIntPipe implements PipeTransform { + transform(value: string, metadata: ArgumentMetadata): number { + const val = Number.parseInt(value, 10) + + if (Number.isNaN(val)) + throw new BadRequestException('id validation failed') + + return val + } +} diff --git a/src/config/app.config.ts b/src/config/app.config.ts new file mode 100644 index 0000000..25f2a14 --- /dev/null +++ b/src/config/app.config.ts @@ -0,0 +1,20 @@ +import { ConfigType, registerAs } from '@nestjs/config' + +import { env, envNumber } from '~/global/env' + +export const appRegToken = 'app' + +export const AppConfig = registerAs(appRegToken, () => ({ + name: env('APP_NAME'), + port: envNumber('APP_PORT', 3000), + baseUrl: env('APP_BASE_URL'), + globalPrefix: env('GLOBAL_PREFIX', 'api'), + locale: env('APP_LOCALE', 'zh-CN'), + + logger: { + level: env('LOGGER_LEVEL'), + maxFiles: envNumber('LOGGER_MAX_FILES'), + }, +})) + +export type IAppConfig = ConfigType diff --git a/src/config/database.config.ts b/src/config/database.config.ts new file mode 100644 index 0000000..2d1b34f --- /dev/null +++ b/src/config/database.config.ts @@ -0,0 +1,40 @@ +import { ConfigType, registerAs } from '@nestjs/config' + +import { DataSource, DataSourceOptions } from 'typeorm' + +import { env, envBoolean, envNumber } from '~/global/env' + +// eslint-disable-next-line import/order +import dotenv from 'dotenv' + +dotenv.config({ path: `.env.${process.env.NODE_ENV}` }) + +// 当前通过 npm scripts 执行的命令 +const currentScript = process.env.npm_lifecycle_event + +const dataSourceOptions: DataSourceOptions = { + type: 'mysql', + host: env('DB_HOST', '127.0.0.1'), + port: envNumber('DB_PORT', 3306), + username: env('DB_USERNAME'), + password: env('DB_PASSWORD'), + database: env('DB_DATABASE'), + synchronize: envBoolean('DB_SYNCHRONIZE', false), + // 解决通过 pnpm migration:run 初始化数据时,遇到的 SET FOREIGN_KEY_CHECKS = 0; 等语句报错问题, 仅在执行数据迁移操作时设为 true + multipleStatements: currentScript === 'typeorm', + entities: ['dist/modules/**/*.entity{.ts,.js}'], + migrations: ['dist/migrations/*{.ts,.js}'], + subscribers: ['dist/modules/**/*.subscriber{.ts,.js}'], +} +export const dbRegToken = 'database' + +export const DatabaseConfig = registerAs( + dbRegToken, + (): DataSourceOptions => dataSourceOptions, +) + +export type IDatabaseConfig = ConfigType + +const dataSource = new DataSource(dataSourceOptions) + +export default dataSource diff --git a/src/config/index.ts b/src/config/index.ts new file mode 100644 index 0000000..7cca557 --- /dev/null +++ b/src/config/index.ts @@ -0,0 +1,37 @@ +import { AppConfig, IAppConfig, appRegToken } from './app.config' +import { DatabaseConfig, IDatabaseConfig, dbRegToken } from './database.config' +import { IMailerConfig, MailerConfig, mailerRegToken } from './mailer.config' +import { IOssConfig, OssConfig, ossRegToken } from './oss.config' +import { IRedisConfig, RedisConfig, redisRegToken } from './redis.config' +import { ISecurityConfig, SecurityConfig, securityRegToken } from './security.config' +import { ISwaggerConfig, SwaggerConfig, swaggerRegToken } from './swagger.config' + +export * from './app.config' +export * from './redis.config' +export * from './database.config' +export * from './swagger.config' +export * from './security.config' +export * from './mailer.config' +export * from './oss.config' + +export interface AllConfigType { + [appRegToken]: IAppConfig + [dbRegToken]: IDatabaseConfig + [mailerRegToken]: IMailerConfig + [redisRegToken]: IRedisConfig + [securityRegToken]: ISecurityConfig + [swaggerRegToken]: ISwaggerConfig + [ossRegToken]: IOssConfig +} + +export type ConfigKeyPaths = RecordNamePaths + +export default { + AppConfig, + DatabaseConfig, + MailerConfig, + OssConfig, + RedisConfig, + SecurityConfig, + SwaggerConfig, +} diff --git a/src/config/mailer.config.ts b/src/config/mailer.config.ts new file mode 100644 index 0000000..27763d1 --- /dev/null +++ b/src/config/mailer.config.ts @@ -0,0 +1,18 @@ +import { ConfigType, registerAs } from '@nestjs/config' + +import { env, envNumber } from '~/global/env' + +export const mailerRegToken = 'mailer' + +export const MailerConfig = registerAs(mailerRegToken, () => ({ + host: env('SMTP_HOST'), + port: envNumber('SMTP_PORT'), + ignoreTLS: true, + secure: true, + auth: { + user: env('SMTP_USER'), + pass: env('SMTP_PASS'), + }, +})) + +export type IMailerConfig = ConfigType diff --git a/src/config/oss.config.ts b/src/config/oss.config.ts new file mode 100644 index 0000000..ae1f563 --- /dev/null +++ b/src/config/oss.config.ts @@ -0,0 +1,32 @@ +import { ConfigType, registerAs } from '@nestjs/config' +import * as qiniu from 'qiniu' + +import { env } from '~/global/env' + +function parseZone(zone: string) { + switch (zone) { + case 'Zone_as0': + return qiniu.zone.Zone_as0 + case 'Zone_na0': + return qiniu.zone.Zone_na0 + case 'Zone_z0': + return qiniu.zone.Zone_z0 + case 'Zone_z1': + return qiniu.zone.Zone_z1 + case 'Zone_z2': + return qiniu.zone.Zone_z2 + } +} + +export const ossRegToken = 'oss' + +export const OssConfig = registerAs(ossRegToken, () => ({ + accessKey: env('OSS_ACCESSKEY'), + secretKey: env('OSS_SECRETKEY'), + domain: env('OSS_DOMAIN'), + bucket: env('OSS_BUCKET'), + zone: parseZone(env('OSS_ZONE') || 'Zone_z2'), + access: (env('OSS_ACCESS_TYPE') as any) || 'public', +})) + +export type IOssConfig = ConfigType diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts new file mode 100644 index 0000000..749c772 --- /dev/null +++ b/src/config/redis.config.ts @@ -0,0 +1,14 @@ +import { ConfigType, registerAs } from '@nestjs/config' + +import { env, envNumber } from '~/global/env' + +export const redisRegToken = 'redis' + +export const RedisConfig = registerAs(redisRegToken, () => ({ + host: env('REDIS_HOST', '127.0.0.1'), + port: envNumber('REDIS_PORT', 6379), + password: env('REDIS_PASSWORD'), + db: envNumber('REDIS_DB'), +})) + +export type IRedisConfig = ConfigType diff --git a/src/config/security.config.ts b/src/config/security.config.ts new file mode 100644 index 0000000..024cccb --- /dev/null +++ b/src/config/security.config.ts @@ -0,0 +1,14 @@ +import { ConfigType, registerAs } from '@nestjs/config' + +import { env, envNumber } from '~/global/env' + +export const securityRegToken = 'security' + +export const SecurityConfig = registerAs(securityRegToken, () => ({ + jwtSecret: env('JWT_SECRET'), + jwtExprire: envNumber('JWT_EXPIRE'), + refreshSecret: env('REFRESH_TOKEN_SECRET'), + refreshExpire: envNumber('REFRESH_TOKEN_EXPIRE'), +})) + +export type ISecurityConfig = ConfigType diff --git a/src/config/swagger.config.ts b/src/config/swagger.config.ts new file mode 100644 index 0000000..0424fcf --- /dev/null +++ b/src/config/swagger.config.ts @@ -0,0 +1,12 @@ +import { ConfigType, registerAs } from '@nestjs/config' + +import { env, envBoolean } from '~/global/env' + +export const swaggerRegToken = 'swagger' + +export const SwaggerConfig = registerAs(swaggerRegToken, () => ({ + enable: envBoolean('SWAGGER_ENABLE'), + path: env('SWAGGER_PATH'), +})) + +export type ISwaggerConfig = ConfigType diff --git a/src/constants/cache.constant.ts b/src/constants/cache.constant.ts new file mode 100644 index 0000000..c4852b8 --- /dev/null +++ b/src/constants/cache.constant.ts @@ -0,0 +1,8 @@ +export enum RedisKeys { + AccessIp = 'access_ip', + CAPTCHA_IMG_PREFIX = 'captcha:img:', + AUTH_TOKEN_PREFIX = 'auth:token:', + AUTH_PERM_PREFIX = 'auth:permission:', + AUTH_PASSWORD_V_PREFIX = 'auth:passwordVersion:', +} +export const API_CACHE_PREFIX = 'api-cache:' diff --git a/src/constants/error-code.constant.ts b/src/constants/error-code.constant.ts new file mode 100644 index 0000000..4046758 --- /dev/null +++ b/src/constants/error-code.constant.ts @@ -0,0 +1,49 @@ +export enum ErrorEnum { + DEFAULT = '0:未知错误', + SERVER_ERROR = '500:服务繁忙,请稍后再试', + + SYSTEM_USER_EXISTS = '1001:系统用户已存在', + INVALID_VERIFICATION_CODE = '1002:验证码填写有误', + INVALID_USERNAME_PASSWORD = '1003:用户名密码有误', + NODE_ROUTE_EXISTS = '1004:节点路由已存在', + PERMISSION_REQUIRES_PARENT = '1005:权限必须包含父节点', + ILLEGAL_OPERATION_DIRECTORY_PARENT = '1006:非法操作:该节点仅支持目录类型父节点', + ILLEGAL_OPERATION_CANNOT_CONVERT_NODE_TYPE = '1007:非法操作:节点类型无法直接转换', + ROLE_HAS_ASSOCIATED_USERS = '1008:该角色存在关联用户,请先删除关联用户', + DEPARTMENT_HAS_ASSOCIATED_USERS = '1009:该部门存在关联用户,请先删除关联用户', + DEPARTMENT_HAS_ASSOCIATED_ROLES = '1010:该部门存在关联角色,请先删除关联角色', + PASSWORD_MISMATCH = '1011:旧密码与原密码不一致', + LOGOUT_OWN_SESSION = '1012:如想下线自身可右上角退出', + NOT_ALLOWED_TO_LOGOUT_USER = '1013:不允许下线该用户', + PARENT_MENU_NOT_FOUND = '1014:父级菜单不存在', + DEPARTMENT_HAS_CHILD_DEPARTMENTS = '1015:该部门存在子部门,请先删除子部门', + SYSTEM_BUILTIN_FUNCTION_NOT_ALLOWED = '1016:系统内置功能不允许操作', + USER_NOT_FOUND = '1017:用户不存在', + UNABLE_TO_FIND_DEPARTMENT_FOR_USER = '1018:无法查找当前用户所属部门', + DEPARTMENT_NOT_FOUND = '1019:部门不存在', + DICT_NAME_EXISTS = '1020: 已存在相同名称的字典', + PARAMETER_CONFIG_KEY_EXISTS = '1021:参数配置键值对已存在', + DEFAULT_ROLE_NOT_FOUND = '1022:所分配的默认角色不存在', + + INVALID_LOGIN = '1101:登录无效,请重新登录', + NO_PERMISSION = '1102:无权限访问', + ONLY_ADMIN_CAN_LOGIN = '1103:不是管理员,无法登录', + REQUEST_INVALIDATED = '1104:当前请求已失效', + ACCOUNT_LOGGED_IN_ELSEWHERE = '1105:您的账号已在其他地方登录', + GUEST_ACCOUNT_RESTRICTED_OPERATION = '1106:游客账号不允许操作', + REQUESTED_RESOURCE_NOT_FOUND = '1107:所请求的资源不存在', + + TOO_MANY_REQUESTS = '1201:请求频率过快,请一分钟后再试', + MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY = '1202:一天最多发送5条验证码', + VERIFICATION_CODE_SEND_FAILED = '1203:验证码发送失败', + + INSECURE_MISSION = '1301:不安全的任务,确保执行的加入@Mission注解', + EXECUTED_MISSION_NOT_FOUND = '1302:所执行的任务不存在', + MISSION_EXECUTION_FAILED = '1303:任务执行失败', + MISSION_NOT_FOUND = '1304:任务不存在', + + // OSS相关 + OSS_FILE_OR_DIR_EXIST = '1401:当前创建的文件或目录已存在', + OSS_NO_OPERATION_REQUIRED = '1402:无需操作', + OSS_EXCEE_MAXIMUM_QUANTITY = '1403:已超出支持的最大处理数量', +} diff --git a/src/constants/event-bus.constant.ts b/src/constants/event-bus.constant.ts new file mode 100644 index 0000000..7d35e8a --- /dev/null +++ b/src/constants/event-bus.constant.ts @@ -0,0 +1,4 @@ +export enum EventBusEvents { + TokenExpired = 'token.expired', + SystemException = 'system.exception', +} diff --git a/src/constants/oss.constant.ts b/src/constants/oss.constant.ts new file mode 100644 index 0000000..a56336e --- /dev/null +++ b/src/constants/oss.constant.ts @@ -0,0 +1,8 @@ +export const OSS_CONFIG = 'admin_module:qiniu_config' +export const OSS_API = 'http://api.qiniu.com' + +// 目录分隔符 +export const NETDISK_DELIMITER = '/' +export const NETDISK_LIMIT = 100 +export const NETDISK_HANDLE_MAX_ITEM = 1000 +export const NETDISK_COPY_SUFFIX = '的副本' diff --git a/src/constants/response.constant.ts b/src/constants/response.constant.ts new file mode 100644 index 0000000..229f70f --- /dev/null +++ b/src/constants/response.constant.ts @@ -0,0 +1,15 @@ +export const RESPONSE_SUCCESS_CODE = 200 + +export const RESPONSE_SUCCESS_MSG = 'success' + +/** + * @description: contentType + */ +export enum ContentTypeEnum { + // json + JSON = 'application/json;charset=UTF-8', + // form-data qs + FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8', + // form-data upload + FORM_DATA = 'multipart/form-data;charset=UTF-8', +} diff --git a/src/constants/system.constant.ts b/src/constants/system.constant.ts new file mode 100644 index 0000000..eef03a5 --- /dev/null +++ b/src/constants/system.constant.ts @@ -0,0 +1,6 @@ +export const SYS_USER_INITPASSWORD = 'sys_user_initPassword' +export const SYS_API_TOKEN = 'sys_api_token' +/** 超级管理员用户 id */ +export const ROOT_USER_ID = 1 +/** 超级管理员角色 id */ +export const ROOT_ROLE_ID = 1 diff --git a/src/global/env.ts b/src/global/env.ts new file mode 100644 index 0000000..8f7b82b --- /dev/null +++ b/src/global/env.ts @@ -0,0 +1,62 @@ +import cluster from 'node:cluster' + +export const isMainCluster + = process.env.NODE_APP_INSTANCE && Number.parseInt(process.env.NODE_APP_INSTANCE) === 0 +export const isMainProcess = cluster.isPrimary || isMainCluster + +export const isDev = process.env.NODE_ENV === 'development' + +export const isTest = !!process.env.TEST +export const cwd = process.cwd() + +/** + * 基础类型接口 + */ +export type BaseType = boolean | number | string | undefined | null + +/** + * 格式化环境变量 + * @param key 环境变量的键值 + * @param defaultValue 默认值 + * @param callback 格式化函数 + */ +function fromatValue(key: string, defaultValue: T, callback?: (value: string) => T): T { + const value: string | undefined = process.env[key] + if (typeof value === 'undefined') + return defaultValue + + if (!callback) + return value as unknown as T + + return callback(value) +} + +export function env(key: string, defaultValue: string = '') { + return fromatValue(key, defaultValue) +} + +export function envString(key: string, defaultValue: string = '') { + return fromatValue(key, defaultValue) +} + +export function envNumber(key: string, defaultValue: number = 0) { + return fromatValue(key, defaultValue, (value) => { + try { + return Number(value) + } + catch { + throw new Error(`${key} environment variable is not a number`) + } + }) +} + +export function envBoolean(key: string, defaultValue: boolean = false) { + return fromatValue(key, defaultValue, (value) => { + try { + return Boolean(JSON.parse(value)) + } + catch { + throw new Error(`${key} environment variable is not a boolean`) + } + }) +} diff --git a/src/helper/catchError.ts b/src/helper/catchError.ts new file mode 100644 index 0000000..d21789c --- /dev/null +++ b/src/helper/catchError.ts @@ -0,0 +1,5 @@ +export function catchError() { + process.on('unhandledRejection', (reason, p) => { + console.log('Promise: ', p, 'Reason: ', reason) + }) +} diff --git a/src/helper/crud/base.service.ts b/src/helper/crud/base.service.ts new file mode 100644 index 0000000..daa67a4 --- /dev/null +++ b/src/helper/crud/base.service.ts @@ -0,0 +1,40 @@ +import { NotFoundException } from '@nestjs/common' +import { ObjectLiteral, Repository } from 'typeorm' + +import { PagerDto } from '~/common/dto/pager.dto' + +import { paginate } from '../paginate' +import { Pagination } from '../paginate/pagination' + +export class BaseService = Repository> { + constructor(private repository: R) { + } + + async list({ + page, + pageSize, + }: PagerDto): Promise> { + return paginate(this.repository, { page, pageSize }) + } + + async findOne(id: number): Promise { + const item = await this.repository.createQueryBuilder().where({ id }).getOne() + if (!item) + throw new NotFoundException('未找到该记录') + + return item + } + + async create(dto: any): Promise { + return await this.repository.save(dto) + } + + async update(id: number, dto: any): Promise { + await this.repository.update(id, dto) + } + + async delete(id: number): Promise { + const item = await this.findOne(id) + await this.repository.remove(item) + } +} diff --git a/src/helper/crud/crud.factory.ts b/src/helper/crud/crud.factory.ts new file mode 100644 index 0000000..7ae1fc8 --- /dev/null +++ b/src/helper/crud/crud.factory.ts @@ -0,0 +1,81 @@ +import type { Type } from '@nestjs/common' + +import { + Body, + Controller, + Delete, + Get, + Patch, + Post, + Put, + Query, +} from '@nestjs/common' +import { ApiBody, IntersectionType, PartialType } from '@nestjs/swagger' +import pluralize from 'pluralize' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { PagerDto } from '~/common/dto/pager.dto' + +import { BaseService } from './base.service' + +export function BaseCrudFactory< + E extends new (...args: any[]) => any, +>({ entity, dto, permissions }: { entity: E, dto?: Type, permissions?: Record }): Type { + const prefix = entity.name.toLowerCase().replace(/entity$/, '') + const pluralizeName = pluralize(prefix) as string + + dto = dto ?? class extends entity {} + + class Dto extends dto {} + class UpdateDto extends PartialType(Dto) {} + class QueryDto extends IntersectionType(PagerDto, PartialType(Dto)) {} + + permissions = permissions ?? { + LIST: `${prefix}:list`, + CREATE: `${prefix}:create`, + READ: `${prefix}:read`, + UPDATE: `${prefix}:update`, + DELETE: `${prefix}:delete`, + } as const + + @Controller(pluralizeName) + class BaseController> { + constructor(private service: S) { } + + @Get() + @ApiResult({ type: [entity], isPage: true }) + async list(@Query() pager: QueryDto) { + return await this.service.list(pager) + } + + @Get(':id') + @ApiResult({ type: entity }) + async get(@IdParam() id: number) { + return await this.service.findOne(id) + } + + @Post() + @ApiBody({ type: dto }) + async create(@Body() dto: Dto) { + return await this.service.create(dto) + } + + @Put(':id') + async update(@IdParam() id: number, @Body() dto: UpdateDto) { + return await this.service.update(id, dto) + } + + @Patch(':id') + async patch(@IdParam() id: number, @Body() dto: UpdateDto) { + await this.service.update(id, dto) + } + + @Delete(':id') + async delete(@IdParam() id: number) { + await this.service.delete(id) + } + } + + return BaseController +} diff --git a/src/helper/genRedisKey.ts b/src/helper/genRedisKey.ts new file mode 100644 index 0000000..1dab9a3 --- /dev/null +++ b/src/helper/genRedisKey.ts @@ -0,0 +1,19 @@ +import { RedisKeys } from '~/constants/cache.constant' + +/** 生成验证码 redis key */ +export function genCaptchaImgKey(val: string | number) { + return `${RedisKeys.CAPTCHA_IMG_PREFIX}${String(val)}` as const +} + +/** 生成 auth token redis key */ +export function genAuthTokenKey(val: string | number) { + return `${RedisKeys.AUTH_TOKEN_PREFIX}${String(val)}` as const +} +/** 生成 auth permission redis key */ +export function genAuthPermKey(val: string | number) { + return `${RedisKeys.AUTH_PERM_PREFIX}${String(val)}` as const +} +/** 生成 auth passwordVersion redis key */ +export function genAuthPVKey(val: string | number) { + return `${RedisKeys.AUTH_PASSWORD_V_PREFIX}${String(val)}` as const +} diff --git a/src/helper/paginate/create-pagination.ts b/src/helper/paginate/create-pagination.ts new file mode 100644 index 0000000..5c1182f --- /dev/null +++ b/src/helper/paginate/create-pagination.ts @@ -0,0 +1,27 @@ +import { IPaginationMeta } from './interface' +import { Pagination } from './pagination' + +export function createPaginationObject({ + items, + totalItems, + currentPage, + limit, +}: { + items: T[] + totalItems?: number + currentPage: number + limit: number +}): Pagination { + const totalPages + = totalItems !== undefined ? Math.ceil(totalItems / limit) : undefined + + const meta: IPaginationMeta = { + totalItems, + itemCount: items.length, + itemsPerPage: limit, + totalPages, + currentPage, + } + + return new Pagination(items, meta) +} diff --git a/src/helper/paginate/index.ts b/src/helper/paginate/index.ts new file mode 100644 index 0000000..fa8b487 --- /dev/null +++ b/src/helper/paginate/index.ts @@ -0,0 +1,147 @@ +import { + FindManyOptions, + FindOptionsWhere, + ObjectLiteral, + Repository, + SelectQueryBuilder, +} from 'typeorm' + +import { createPaginationObject } from './create-pagination' +import { IPaginationOptions, PaginationTypeEnum } from './interface' +import { Pagination } from './pagination' + +const DEFAULT_LIMIT = 10 +const DEFAULT_PAGE = 1 + +function resolveOptions( + options: IPaginationOptions, +): [number, number, PaginationTypeEnum] { + const { page, pageSize, paginationType } = options + + return [ + page || DEFAULT_PAGE, + pageSize || DEFAULT_LIMIT, + paginationType || PaginationTypeEnum.TAKE_AND_SKIP, + ] +} + +async function paginateRepository( + repository: Repository, + options: IPaginationOptions, + searchOptions?: FindOptionsWhere | FindManyOptions, +): Promise> { + const [page, limit] = resolveOptions(options) + + const promises: [Promise, Promise | undefined] = [ + repository.find({ + skip: limit * (page - 1), + take: limit, + ...searchOptions, + }), + undefined, + ] + + const [items, total] = await Promise.all(promises) + + return createPaginationObject({ + items, + totalItems: total, + currentPage: page, + limit, + }) +} + +async function paginateQueryBuilder( + queryBuilder: SelectQueryBuilder, + options: IPaginationOptions, +): Promise> { + const [page, limit, paginationType] = resolveOptions(options) + + if (paginationType === PaginationTypeEnum.TAKE_AND_SKIP) + queryBuilder.take(limit).skip((page - 1) * limit) + else + queryBuilder.limit(limit).offset((page - 1) * limit) + + const [items, total] = await queryBuilder.getManyAndCount() + + return createPaginationObject({ + items, + totalItems: total, + currentPage: page, + limit, + }) +} + +export async function paginateRaw( + queryBuilder: SelectQueryBuilder, + options: IPaginationOptions, +): Promise> { + const [page, limit, paginationType] = resolveOptions(options) + + const promises: [Promise, Promise | undefined] = [ + (paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET + ? queryBuilder.limit(limit).offset((page - 1) * limit) + : queryBuilder.take(limit).skip((page - 1) * limit) + ).getRawMany(), + queryBuilder.getCount(), + ] + + const [items, total] = await Promise.all(promises) + + return createPaginationObject({ + items, + totalItems: total, + currentPage: page, + limit, + }) +} + +export async function paginateRawAndEntities( + queryBuilder: SelectQueryBuilder, + options: IPaginationOptions, +): Promise<[Pagination, Partial[]]> { + const [page, limit, paginationType] = resolveOptions(options) + + const promises: [ + Promise<{ entities: T[], raw: T[] }>, + Promise | undefined, + ] = [ + (paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET + ? queryBuilder.limit(limit).offset((page - 1) * limit) + : queryBuilder.take(limit).skip((page - 1) * limit) + ).getRawAndEntities(), + queryBuilder.getCount(), + ] + + const [itemObject, total] = await Promise.all(promises) + + return [ + createPaginationObject({ + items: itemObject.entities, + totalItems: total, + currentPage: page, + limit, + }), + itemObject.raw, + ] +} + +export async function paginate( + repository: Repository, + options: IPaginationOptions, + searchOptions?: FindOptionsWhere | FindManyOptions, +): Promise> +export async function paginate( + queryBuilder: SelectQueryBuilder, + options: IPaginationOptions, +): Promise> + +export async function paginate( + repositoryOrQueryBuilder: Repository | SelectQueryBuilder, + options: IPaginationOptions, + searchOptions?: FindOptionsWhere | FindManyOptions, +) { + return repositoryOrQueryBuilder instanceof Repository + ? paginateRepository(repositoryOrQueryBuilder, options, searchOptions) + : paginateQueryBuilder(repositoryOrQueryBuilder, options) +} diff --git a/src/helper/paginate/interface.ts b/src/helper/paginate/interface.ts new file mode 100644 index 0000000..e7c6ee2 --- /dev/null +++ b/src/helper/paginate/interface.ts @@ -0,0 +1,27 @@ +import { ObjectLiteral } from 'typeorm' + +export enum PaginationTypeEnum { + LIMIT_AND_OFFSET = 'limit', + TAKE_AND_SKIP = 'take', +} + +export interface IPaginationOptions { + page: number + pageSize: number + paginationType?: PaginationTypeEnum +} + +export interface IPaginationMeta extends ObjectLiteral { + itemCount: number + totalItems?: number + itemsPerPage: number + totalPages?: number + currentPage: number +} + +export interface IPaginationLinks { + first?: string + previous?: string + next?: string + last?: string +} diff --git a/src/helper/paginate/pagination.ts b/src/helper/paginate/pagination.ts new file mode 100644 index 0000000..5bcf90f --- /dev/null +++ b/src/helper/paginate/pagination.ts @@ -0,0 +1,14 @@ +import { ObjectLiteral } from 'typeorm' + +import { IPaginationMeta } from './interface' + +export class Pagination< + PaginationObject, + T extends ObjectLiteral = IPaginationMeta, +> { + constructor( + public readonly items: PaginationObject[], + + public readonly meta: T, + ) {} +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..4fd1732 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,101 @@ +import cluster from 'node:cluster' +import path from 'node:path' + +import { + HttpStatus, + Logger, + UnprocessableEntityException, + ValidationPipe, +} from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { NestFactory } from '@nestjs/core' +import { NestFastifyApplication } from '@nestjs/platform-fastify' + +import { useContainer } from 'class-validator' + +import { AppModule } from './app.module' + +import { fastifyApp } from './common/adapters/fastify.adapter' +import { RedisIoAdapter } from './common/adapters/socket.adapter' +import { LoggingInterceptor } from './common/interceptors/logging.interceptor' +import type { ConfigKeyPaths } from './config' +import { isDev, isMainProcess } from './global/env' +import { setupSwagger } from './setup-swagger' +import { LoggerService } from './shared/logger/logger.service' + +declare const module: any + +async function bootstrap() { + const app = await NestFactory.create( + AppModule, + fastifyApp, + { + bufferLogs: true, + snapshot: true, + // forceCloseConnections: true, + }, + ) + + const configService = app.get(ConfigService) + + const { port, globalPrefix } = configService.get('app', { infer: true }) + + // class-validator 的 DTO 类中注入 nest 容器的依赖 (用于自定义验证器) + useContainer(app.select(AppModule), { fallbackOnErrors: true }) + + app.enableCors({ origin: '*', credentials: true }) + app.setGlobalPrefix(globalPrefix) + app.useStaticAssets({ root: path.join(__dirname, '..', 'public') }) + // Starts listening for shutdown hooks + !isDev && app.enableShutdownHooks() + + if (isDev) + app.useGlobalInterceptors(new LoggingInterceptor()) + + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + whitelist: true, + transformOptions: { enableImplicitConversion: true }, + // forbidNonWhitelisted: true, // 禁止 无装饰器验证的数据通过 + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + stopAtFirstError: true, + exceptionFactory: errors => + new UnprocessableEntityException( + errors.map((e) => { + const rule = Object.keys(e.constraints!)[0] + const msg = e.constraints![rule] + return msg + })[0], + ), + }), + ) + + app.useWebSocketAdapter(new RedisIoAdapter(app)) + + setupSwagger(app, configService) + + await app.listen(port, '0.0.0.0', async () => { + app.useLogger(app.get(LoggerService)) + const url = await app.getUrl() + const { pid } = process + const env = cluster.isPrimary + const prefix = env ? 'P' : 'W' + + if (!isMainProcess) + return + + const logger = new Logger('NestApplication') + logger.log(`[${prefix + pid}] Server running on ${url}`) + + if (isDev) + logger.log(`[${prefix + pid}] OpenAPI: ${url}/api-docs`) + }) + + if (module.hot) { + module.hot.accept() + module.hot.dispose(() => app.close()) + } +} + +bootstrap() diff --git a/src/migrations/1707996695540-initData.ts b/src/migrations/1707996695540-initData.ts new file mode 100644 index 0000000..c4c3e6e --- /dev/null +++ b/src/migrations/1707996695540-initData.ts @@ -0,0 +1,15 @@ +import fs from 'node:fs' +import path from 'node:path' + +import { MigrationInterface, QueryRunner } from 'typeorm' + +const sql = fs.readFileSync(path.join(__dirname, '../../deploy/sql/nest_admin.sql'), 'utf8') + +export class InitData1707996695540 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(sql) + } + + public async down(queryRunner: QueryRunner): Promise { + } +} diff --git a/src/modules/auth/auth.constant.ts b/src/modules/auth/auth.constant.ts new file mode 100644 index 0000000..08d48e6 --- /dev/null +++ b/src/modules/auth/auth.constant.ts @@ -0,0 +1,26 @@ +export const PUBLIC_KEY = '__public_key__' + +export const PERMISSION_KEY = '__permission_key__' + +export const RESOURCE_KEY = '__resource_key__' + +export const ALLOW_ANON_KEY = '__allow_anon_permission_key__' + +export const AuthStrategy = { + LOCAL: 'local', + LOCAL_EMAIL: 'local_email', + LOCAL_PHONE: 'local_phone', + + JWT: 'jwt', + + GITHUB: 'github', + GOOGLE: 'google', +} as const + +export const Roles = { + ADMIN: 'admin', + USER: 'user', + // GUEST: 'guest', +} as const + +export type Role = (typeof Roles)[keyof typeof Roles] diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000..cb1bed7 --- /dev/null +++ b/src/modules/auth/auth.controller.ts @@ -0,0 +1,47 @@ +import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { Ip } from '~/common/decorators/http.decorator' + +import { UserService } from '../user/user.service' + +import { AuthService } from './auth.service' +import { Public } from './decorators/public.decorator' +import { LoginDto, RegisterDto } from './dto/auth.dto' +import { LocalGuard } from './guards/local.guard' +import { LoginToken } from './models/auth.model' +import { CaptchaService } from './services/captcha.service' + +@ApiTags('Auth - 认证模块') +@UseGuards(LocalGuard) +@Public() +@Controller('auth') +export class AuthController { + constructor( + private authService: AuthService, + private userService: UserService, + private captchaService: CaptchaService, + ) {} + + @Post('login') + @ApiOperation({ summary: '登录' }) + @ApiResult({ type: LoginToken }) + async login( + @Body() dto: LoginDto, @Ip() ip: string, @Headers('user-agent') ua: string): Promise { + await this.captchaService.checkImgCaptcha(dto.captchaId, dto.verifyCode) + const token = await this.authService.login( + dto.username, + dto.password, + ip, + ua, + ) + return { token } + } + + @Post('register') + @ApiOperation({ summary: '注册' }) + async register(@Body() dto: RegisterDto): Promise { + await this.userService.register(dto) + } +} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..edaf919 --- /dev/null +++ b/src/modules/auth/auth.module.ts @@ -0,0 +1,64 @@ +import { Module } from '@nestjs/common' + +import { ConfigModule, ConfigService } from '@nestjs/config' +import { JwtModule } from '@nestjs/jwt' +import { PassportModule } from '@nestjs/passport' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { ConfigKeyPaths, ISecurityConfig } from '~/config' +import { isDev } from '~/global/env' + +import { LogModule } from '../system/log/log.module' +import { MenuModule } from '../system/menu/menu.module' +import { RoleModule } from '../system/role/role.module' +import { UserModule } from '../user/user.module' + +import { AuthController } from './auth.controller' +import { AuthService } from './auth.service' +import { AccountController } from './controllers/account.controller' +import { CaptchaController } from './controllers/captcha.controller' +import { EmailController } from './controllers/email.controller' +import { AccessTokenEntity } from './entities/access-token.entity' +import { RefreshTokenEntity } from './entities/refresh-token.entity' +import { CaptchaService } from './services/captcha.service' +import { TokenService } from './services/token.service' +import { JwtStrategy } from './strategies/jwt.strategy' +import { LocalStrategy } from './strategies/local.strategy' + +const controllers = [ + AuthController, + AccountController, + CaptchaController, + EmailController, +] +const providers = [AuthService, TokenService, CaptchaService] +const strategies = [LocalStrategy, JwtStrategy] + +@Module({ + imports: [ + TypeOrmModule.forFeature([AccessTokenEntity, RefreshTokenEntity]), + PassportModule, + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + const { jwtSecret, jwtExprire } + = configService.get('security') + + return { + secret: jwtSecret, + expires: jwtExprire, + ignoreExpiration: isDev, + } + }, + inject: [ConfigService], + }), + UserModule, + RoleModule, + MenuModule, + LogModule, + ], + controllers: [...controllers], + providers: [...providers, ...strategies], + exports: [TypeOrmModule, JwtModule, ...providers], +}) +export class AuthModule {} diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts new file mode 100644 index 0000000..b734e27 --- /dev/null +++ b/src/modules/auth/auth.service.ts @@ -0,0 +1,156 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' + +import Redis from 'ioredis' +import { isEmpty } from 'lodash' + +import { BusinessException } from '~/common/exceptions/biz.exception' + +import { ErrorEnum } from '~/constants/error-code.constant' +import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey' + +import { UserService } from '~/modules/user/user.service' + +import { md5 } from '~/utils' + +import { LoginLogService } from '../system/log/services/login-log.service' +import { MenuService } from '../system/menu/menu.service' +import { RoleService } from '../system/role/role.service' + +import { TokenService } from './services/token.service' + +@Injectable() +export class AuthService { + constructor( + @InjectRedis() private readonly redis: Redis, + private menuService: MenuService, + private roleService: RoleService, + private userService: UserService, + private loginLogService: LoginLogService, + private tokenService: TokenService, + ) {} + + async validateUser(credential: string, password: string): Promise { + const user = await this.userService.findUserByUserName(credential) + + if (isEmpty(user)) + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) + + const comparePassword = md5(`${password}${user.psalt}`) + if (user.password !== comparePassword) + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) + + if (user) { + const { password, ...result } = user + return result + } + + return null + } + + /** + * 获取登录JWT + * 返回null则账号密码有误,不存在该用户 + */ + async login( + username: string, + password: string, + ip: string, + ua: string, + ): Promise { + const user = await this.userService.findUserByUserName(username) + if (isEmpty(user)) + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) + + const comparePassword = md5(`${password}${user.psalt}`) + if (user.password !== comparePassword) + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) + + const roleIds = await this.roleService.getRoleIdsByUser(user.id) + + const roles = await this.roleService.getRoleValues(roleIds) + + // 包含access_token和refresh_token + const token = await this.tokenService.generateAccessToken(user.id, roles) + + await this.redis.set(genAuthTokenKey(user.id), token.accessToken) + + // 设置密码版本号 当密码修改时,版本号+1 + await this.redis.set(genAuthPVKey(user.id), 1) + + // 设置菜单权限 + const permissions = await this.menuService.getPermissions(user.id) + await this.setPermissionsCache(user.id, permissions) + + await this.loginLogService.create(user.id, ip, ua) + + return token.accessToken + } + + /** + * 效验账号密码 + */ + async checkPassword(username: string, password: string) { + const user = await this.userService.findUserByUserName(username) + + const comparePassword = md5(`${password}${user.psalt}`) + if (user.password !== comparePassword) + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) + } + + async loginLog(uid: number, ip: string, ua: string) { + await this.loginLogService.create(uid, ip, ua) + } + + async logout(uid: number) { + // 删除token + await this.userService.forbidden(uid) + } + + /** + * 重置密码 + */ + async resetPassword(username: string, password: string) { + const user = await this.userService.findUserByUserName(username) + + await this.userService.forceUpdatePassword(user.id, password) + } + + /** + * 清除登录状态信息 + */ + async clearLoginStatus(uid: number): Promise { + await this.userService.forbidden(uid) + } + + /** + * 获取菜单列表 + */ + async getMenus(uid: number): Promise { + return this.menuService.getMenus(uid) + } + + /** + * 获取权限列表 + */ + async getPermissions(uid: number): Promise { + return this.menuService.getPermissions(uid) + } + + async getPermissionsCache(uid: number): Promise { + const permissionString = await this.redis.get(genAuthPermKey(uid)) + return permissionString ? JSON.parse(permissionString) : [] + } + + async setPermissionsCache(uid: number, permissions: string[]): Promise { + await this.redis.set(genAuthPermKey(uid), JSON.stringify(permissions)) + } + + async getPasswordVersionByUid(uid: number): Promise { + return this.redis.get(genAuthPVKey(uid)) + } + + async getTokenByUid(uid: number): Promise { + return this.redis.get(genAuthTokenKey(uid)) + } +} diff --git a/src/modules/auth/controllers/account.controller.ts b/src/modules/auth/controllers/account.controller.ts new file mode 100644 index 0000000..d6072a1 --- /dev/null +++ b/src/modules/auth/controllers/account.controller.ts @@ -0,0 +1,75 @@ +import { Body, Controller, Get, Post, Put, UseGuards } from '@nestjs/common' +import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' + +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { AllowAnon } from '~/modules/auth/decorators/allow-anon.decorator' +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' + +import { PasswordUpdateDto } from '~/modules/user/dto/password.dto' + +import { AccountInfo } from '../../user/user.model' +import { UserService } from '../../user/user.service' +import { AuthService } from '../auth.service' +import { AccountMenus, AccountUpdateDto } from '../dto/account.dto' +import { JwtAuthGuard } from '../guards/jwt-auth.guard' + +@ApiTags('Account - 账户模块') +@ApiSecurityAuth() +@ApiExtraModels(AccountInfo) +@UseGuards(JwtAuthGuard) +@Controller('account') +export class AccountController { + constructor( + private userService: UserService, + private authService: AuthService, + ) {} + + @Get('profile') + @ApiOperation({ summary: '获取账户资料' }) + @ApiResult({ type: AccountInfo }) + @AllowAnon() + async profile(@AuthUser() user: IAuthUser): Promise { + return this.userService.getAccountInfo(user.uid) + } + + @Get('logout') + @ApiOperation({ summary: '账户登出' }) + @AllowAnon() + async logout(@AuthUser() user: IAuthUser): Promise { + await this.authService.clearLoginStatus(user.uid) + } + + @Get('menus') + @ApiOperation({ summary: '获取菜单列表' }) + @ApiResult({ type: [AccountMenus] }) + @AllowAnon() + async menu(@AuthUser() user: IAuthUser): Promise { + return this.authService.getMenus(user.uid) + } + + @Get('permissions') + @ApiOperation({ summary: '获取权限列表' }) + @ApiResult({ type: [String] }) + @AllowAnon() + async permissions(@AuthUser() user: IAuthUser): Promise { + return this.authService.getPermissions(user.uid) + } + + @Put('update') + @ApiOperation({ summary: '更改账户资料' }) + @AllowAnon() + async update( + @AuthUser() user: IAuthUser, @Body() dto: AccountUpdateDto): Promise { + await this.userService.updateAccountInfo(user.uid, dto) + } + + @Post('password') + @ApiOperation({ summary: '更改账户密码' }) + @AllowAnon() + async password( + @AuthUser() user: IAuthUser, @Body() dto: PasswordUpdateDto): Promise { + await this.userService.updatePassword(user.uid, dto) + } +} diff --git a/src/modules/auth/controllers/captcha.controller.ts b/src/modules/auth/controllers/captcha.controller.ts new file mode 100644 index 0000000..154236c --- /dev/null +++ b/src/modules/auth/controllers/captcha.controller.ts @@ -0,0 +1,50 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Controller, Get, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import Redis from 'ioredis' +import { isEmpty } from 'lodash' +import * as svgCaptcha from 'svg-captcha' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { genCaptchaImgKey } from '~/helper/genRedisKey' +import { generateUUID } from '~/utils' + +import { Public } from '../decorators/public.decorator' + +import { ImageCaptchaDto } from '../dto/captcha.dto' +import { ImageCaptcha } from '../models/auth.model' + +@ApiTags('Captcha - 验证码模块') +// @UseGuards(ThrottlerGuard) +@Controller('auth/captcha') +export class CaptchaController { + constructor(@InjectRedis() private redis: Redis) {} + + @Get('img') + @ApiOperation({ summary: '获取登录图片验证码' }) + @ApiResult({ type: ImageCaptcha }) + @Public() + // @Throttle({ default: { limit: 2, ttl: 600000 } }) + async captchaByImg(@Query() dto: ImageCaptchaDto): Promise { + const { width, height } = dto + + const svg = svgCaptcha.create({ + size: 4, + color: true, + noise: 4, + width: isEmpty(width) ? 100 : width, + height: isEmpty(height) ? 50 : height, + charPreset: '1234567890', + }) + const result = { + img: `data:image/svg+xml;base64,${Buffer.from(svg.data).toString( + 'base64', + )}`, + id: generateUUID(), + } + // 5分钟过期时间 + await this.redis.set(genCaptchaImgKey(result.id), svg.text, 'EX', 60 * 5) + return result + } +} diff --git a/src/modules/auth/controllers/email.controller.ts b/src/modules/auth/controllers/email.controller.ts new file mode 100644 index 0000000..99ce81a --- /dev/null +++ b/src/modules/auth/controllers/email.controller.ts @@ -0,0 +1,41 @@ +import { Body, Controller, Post, UseGuards } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { Throttle, ThrottlerGuard } from '@nestjs/throttler' + +import { Ip } from '~/common/decorators/http.decorator' + +import { MailerService } from '~/shared/mailer/mailer.service' + +import { Public } from '../decorators/public.decorator' + +import { SendEmailCodeDto } from '../dto/captcha.dto' + +@ApiTags('Auth - 认证模块') +@UseGuards(ThrottlerGuard) +@Controller('auth/email') +export class EmailController { + constructor(private mailerService: MailerService) {} + + @Post('send') + @ApiOperation({ summary: '发送邮箱验证码' }) + @Public() + @Throttle({ default: { limit: 2, ttl: 600000 } }) + async sendEmailCode( + @Body() dto: SendEmailCodeDto, + @Ip() ip: string, + ): Promise { + // await this.authService.checkImgCaptcha(dto.captchaId, dto.verifyCode); + const { email } = dto + + await this.mailerService.checkLimit(email, ip) + const { code } = await this.mailerService.sendVerificationCode(email) + + await this.mailerService.log(email, code, ip) + } + + // @Post() + // async authWithEmail(@AuthUser() user: IAuthUser) { + // // TODO: + // } +} diff --git a/src/modules/auth/decorators/allow-anon.decorator.ts b/src/modules/auth/decorators/allow-anon.decorator.ts new file mode 100644 index 0000000..21954f6 --- /dev/null +++ b/src/modules/auth/decorators/allow-anon.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common' + +import { ALLOW_ANON_KEY } from '../auth.constant' + +/** + * 当接口不需要检测用户是否具有操作权限时添加该装饰器 + */ +export const AllowAnon = () => SetMetadata(ALLOW_ANON_KEY, true) diff --git a/src/modules/auth/decorators/auth-user.decorator.ts b/src/modules/auth/decorators/auth-user.decorator.ts new file mode 100644 index 0000000..ef0733d --- /dev/null +++ b/src/modules/auth/decorators/auth-user.decorator.ts @@ -0,0 +1,17 @@ +import { ExecutionContext, createParamDecorator } from '@nestjs/common' +import { FastifyRequest } from 'fastify' + +type Payload = keyof IAuthUser + +/** + * @description 获取当前登录用户信息, 并挂载到request上 + */ +export const AuthUser = createParamDecorator( + (data: Payload, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest() + // auth guard will mount this + const user = request.user as IAuthUser + + return data ? user?.[data] : user + }, +) diff --git a/src/modules/auth/decorators/permission.decorator.ts b/src/modules/auth/decorators/permission.decorator.ts new file mode 100644 index 0000000..943679f --- /dev/null +++ b/src/modules/auth/decorators/permission.decorator.ts @@ -0,0 +1,58 @@ +import { SetMetadata, applyDecorators } from '@nestjs/common' + +import { isPlainObject } from 'lodash' + +import { PERMISSION_KEY } from '../auth.constant' + + type TupleToObject> = { + [K in Uppercase]: `${T}:${Lowercase}` + } + type AddPrefixToObjectValue> = { + [K in keyof P]: K extends string ? `${T}:${P[K]}` : never + } + +/** 资源操作需要特定的权限 */ +export function Perm(permission: string | string[]) { + return applyDecorators(SetMetadata(PERMISSION_KEY, permission)) +} + +/** (此举非必需)保存通过 definePermission 定义的所有权限,可用于前端开发人员开发阶段的 ts 类型提示,避免前端权限定义与后端定义不匹配 */ +let permissions: string[] = [] +/** + * 定义权限,同时收集所有被定义的权限 + * + * - 通过对象形式定义, eg: + * ```ts + * definePermission('app:health', { + * NETWORK: 'network' + * }; + * ``` + * + * - 通过字符串数组形式定义, eg: + * ```ts + * definePermission('app:health', ['network']); + * ``` + */ +export function definePermission>(modulePrefix: T, actionMap: U): AddPrefixToObjectValue +export function definePermission>(modulePrefix: T, actions: U): TupleToObject +export function definePermission(modulePrefix: string, actions) { + if (isPlainObject(actions)) { + Object.entries(actions).forEach(([key, action]) => { + actions[key] = `${modulePrefix}:${action}` + }) + permissions = [...new Set([...permissions, ...Object.values(actions)])] + return actions + } + else if (Array.isArray(actions)) { + const permissionFormats = actions.map(action => `${modulePrefix}:${action}`) + permissions = [...new Set([...permissions, ...permissionFormats])] + + return actions.reduce((prev, action) => { + prev[action.toUpperCase()] = `${modulePrefix}:${action}` + return prev + }, {}) + } +} + +/** 获取所有通过 definePermission 定义的权限 */ +export const getDefinePermissions = () => permissions diff --git a/src/modules/auth/decorators/public.decorator.ts b/src/modules/auth/decorators/public.decorator.ts new file mode 100644 index 0000000..c3409ca --- /dev/null +++ b/src/modules/auth/decorators/public.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common' + +import { PUBLIC_KEY } from '../auth.constant' + +/** + * 当接口不需要检测用户登录时添加该装饰器 + */ +export const Public = () => SetMetadata(PUBLIC_KEY, true) diff --git a/src/modules/auth/decorators/resource.decorator.ts b/src/modules/auth/decorators/resource.decorator.ts new file mode 100644 index 0000000..73143b0 --- /dev/null +++ b/src/modules/auth/decorators/resource.decorator.ts @@ -0,0 +1,12 @@ +import { SetMetadata, applyDecorators } from '@nestjs/common' + +import { ObjectLiteral, ObjectType, Repository } from 'typeorm' + +import { RESOURCE_KEY } from '../auth.constant' + +export type Condition = (Repository: Repository, items: number[], user: IAuthUser) => Promise + +export interface ResourceObject { entity: ObjectType, condition: Condition } +export function Resource(entity: ObjectType, condition?: Condition) { + return applyDecorators(SetMetadata(RESOURCE_KEY, { entity, condition })) +} diff --git a/src/modules/auth/dto/account.dto.ts b/src/modules/auth/dto/account.dto.ts new file mode 100644 index 0000000..8b3a5ee --- /dev/null +++ b/src/modules/auth/dto/account.dto.ts @@ -0,0 +1,64 @@ +import { ApiProperty, OmitType, PartialType, PickType } from '@nestjs/swagger' +import { + IsEmail, + IsOptional, + IsString, + Matches, + MaxLength, + MinLength, +} from 'class-validator' + +import { MenuEntity } from '~/modules/system/menu/menu.entity' + +export class AccountUpdateDto { + @ApiProperty({ description: '用户呢称' }) + @IsString() + @IsOptional() + nickname: string + + @ApiProperty({ description: '用户邮箱' }) + @IsEmail() + email: string + + @ApiProperty({ description: '用户QQ' }) + @IsOptional() + @IsString() + @Matches(/^[0-9]+$/) + @MinLength(5) + @MaxLength(11) + qq: string + + @ApiProperty({ description: '用户手机号' }) + @IsOptional() + @IsString() + phone: string + + @ApiProperty({ description: '用户头像' }) + @IsOptional() + @IsString() + avatar: string + + @ApiProperty({ description: '用户备注' }) + @IsOptional() + @IsString() + remark: string +} + +export class ResetPasswordDto { + @ApiProperty({ description: '临时token', example: 'uuid' }) + @IsString() + accessToken: string + + @ApiProperty({ description: '密码', example: 'a123456' }) + @IsString() + @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/) + @MinLength(6) + password: string +} + +export class MenuMeta extends PartialType(OmitType(MenuEntity, ['parentId', 'createdAt', 'updatedAt', 'id', 'roles', 'path', 'name'] as const)) { + title: string +} +export class AccountMenus extends PickType(MenuEntity, ['id', 'path', 'name', 'component'] as const) { + meta: MenuMeta +} diff --git a/src/modules/auth/dto/auth.dto.ts b/src/modules/auth/dto/auth.dto.ts new file mode 100644 index 0000000..abaf379 --- /dev/null +++ b/src/modules/auth/dto/auth.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty } from '@nestjs/swagger' + +import { IsString, Matches, MaxLength, MinLength } from 'class-validator' + +export class LoginDto { + @ApiProperty({ description: '手机号/邮箱' }) + @IsString() + @MinLength(4) + username: string + + @ApiProperty({ description: '密码', example: 'a123456' }) + @IsString() + @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/) + @MinLength(6) + password: string + + @ApiProperty({ description: '验证码标识' }) + @IsString() + captchaId: string + + @ApiProperty({ description: '用户输入的验证码' }) + @IsString() + @MinLength(4) + @MaxLength(4) + verifyCode: string +} + +export class RegisterDto { + @ApiProperty({ description: '账号' }) + @IsString() + username: string + + @ApiProperty({ description: '密码' }) + @IsString() + @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/) + @MinLength(6) + @MaxLength(16) + password: string + + @ApiProperty({ description: '语言', examples: ['EN', 'ZH'] }) + @IsString() + lang: string +} diff --git a/src/modules/auth/dto/captcha.dto.ts b/src/modules/auth/dto/captcha.dto.ts new file mode 100644 index 0000000..c24d3d8 --- /dev/null +++ b/src/modules/auth/dto/captcha.dto.ts @@ -0,0 +1,53 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { + IsEmail, + IsInt, + IsMobilePhone, + IsOptional, + IsString, +} from 'class-validator' + +export class ImageCaptchaDto { + @ApiProperty({ + required: false, + default: 100, + description: '验证码宽度', + }) + @Type(() => Number) + @IsInt() + @IsOptional() + readonly width: number = 100 + + @ApiProperty({ + required: false, + default: 50, + description: '验证码宽度', + }) + @Type(() => Number) + @IsInt() + @IsOptional() + readonly height: number = 50 +} + +export class SendEmailCodeDto { + @ApiProperty({ description: '邮箱' }) + @IsEmail({}, { message: '邮箱格式不正确' }) + email: string +} + +export class SendSmsCodeDto { + @ApiProperty({ description: '手机号' }) + @IsMobilePhone('zh-CN', {}, { message: '手机号格式不正确' }) + phone: string +} + +export class CheckCodeDto { + @ApiProperty({ description: '手机号/邮箱' }) + @IsString() + account: string + + @ApiProperty({ description: '验证码' }) + @IsString() + code: string +} diff --git a/src/modules/auth/entities/access-token.entity.ts b/src/modules/auth/entities/access-token.entity.ts new file mode 100644 index 0000000..d56afc8 --- /dev/null +++ b/src/modules/auth/entities/access-token.entity.ts @@ -0,0 +1,40 @@ +import { + BaseEntity, + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm' + +import { UserEntity } from '~/modules/user/user.entity' + +import { RefreshTokenEntity } from './refresh-token.entity' + +@Entity('user_access_tokens') +export class AccessTokenEntity extends BaseEntity { + @PrimaryGeneratedColumn('uuid') + id!: string + + @Column({ length: 500 }) + value!: string + + @Column({ comment: '令牌过期时间' }) + expired_at!: Date + + @CreateDateColumn({ comment: '令牌创建时间' }) + created_at!: Date + + @OneToOne(() => RefreshTokenEntity, refreshToken => refreshToken.accessToken, { + cascade: true, + }) + refreshToken!: RefreshTokenEntity + + @ManyToOne(() => UserEntity, user => user.accessTokens, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'user_id' }) + user!: UserEntity +} diff --git a/src/modules/auth/entities/refresh-token.entity.ts b/src/modules/auth/entities/refresh-token.entity.ts new file mode 100644 index 0000000..cec190b --- /dev/null +++ b/src/modules/auth/entities/refresh-token.entity.ts @@ -0,0 +1,32 @@ +import { + BaseEntity, + Column, + CreateDateColumn, + Entity, + JoinColumn, + OneToOne, + PrimaryGeneratedColumn, +} from 'typeorm' + +import { AccessTokenEntity } from './access-token.entity' + +@Entity('user_refresh_tokens') +export class RefreshTokenEntity extends BaseEntity { + @PrimaryGeneratedColumn('uuid') + id!: string + + @Column({ length: 500 }) + value!: string + + @Column({ comment: '令牌过期时间' }) + expired_at!: Date + + @CreateDateColumn({ comment: '令牌创建时间' }) + created_at!: Date + + @OneToOne(() => AccessTokenEntity, accessToken => accessToken.refreshToken, { + onDelete: 'CASCADE', + }) + @JoinColumn() + accessToken!: AccessTokenEntity +} diff --git a/src/modules/auth/guards/jwt-auth.guard.ts b/src/modules/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..0cbe88c --- /dev/null +++ b/src/modules/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,105 @@ +import { + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { AuthGuard } from '@nestjs/passport' +import { FastifyRequest } from 'fastify' +import { isEmpty, isNil } from 'lodash' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { AuthService } from '~/modules/auth/auth.service' + +import { checkIsDemoMode } from '~/utils' + +import { AuthStrategy, PUBLIC_KEY } from '../auth.constant' +import { TokenService } from '../services/token.service' + +// https://docs.nestjs.com/recipes/passport#implement-protected-route-and-jwt-strategy-guards +@Injectable() +export class JwtAuthGuard extends AuthGuard(AuthStrategy.JWT) { + constructor( + private reflector: Reflector, + private authService: AuthService, + private tokenService: TokenService, + ) { + super() + } + + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride(PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]) + const request = context.switchToHttp().getRequest() + // const response = context.switchToHttp().getResponse() + + // TODO 此处代码的作用是判断如果在演示环境下,则拒绝用户的增删改操作,去掉此代码不影响正常的业务逻辑 + if (request.method !== 'GET' && !request.url.includes('/auth/login')) + checkIsDemoMode() + + const isSse = request.headers.accept === 'text/event-stream' + + if (isSse && !request.headers.authorization?.startsWith('Bearer')) { + const { token } = request.query as Record + if (token) + request.headers.authorization = `Bearer ${token}` + } + + const Authorization = request.headers.authorization + + let result: any = false + try { + result = await super.canActivate(context) + } + catch (e) { + // 需要后置判断 这样携带了 token 的用户就能够解析到 request.user + if (isPublic) + return true + + if (isEmpty(Authorization)) + throw new UnauthorizedException('未登录') + + // 判断 token 是否存在, 如果不存在则认证失败 + const accessToken = isNil(Authorization) + ? undefined + : await this.tokenService.checkAccessToken(Authorization!) + + if (!accessToken) + throw new UnauthorizedException('令牌无效') + } + + // SSE 请求 + if (isSse) { + const { uid } = request.params as Record + + if (Number(uid) !== request.user.uid) + throw new UnauthorizedException('路径参数 uid 与当前 token 登录的用户 uid 不一致') + } + + const pv = await this.authService.getPasswordVersionByUid(request.user.uid) + if (pv !== `${request.user.pv}`) { + // 密码版本不一致,登录期间已更改过密码 + throw new BusinessException(ErrorEnum.INVALID_LOGIN) + } + + // 不允许多端登录 + // const cacheToken = await this.authService.getTokenByUid(request.user.uid); + // if (Authorization !== cacheToken) { + // // 与redis保存不一致 即二次登录 + // throw new ApiException(ErrorEnum.CODE_1106); + // } + + return result + } + + handleRequest(err, user, info) { + // You can throw an exception based on either "info" or "err" arguments + if (err || !user) + throw err || new UnauthorizedException() + + return user + } +} diff --git a/src/modules/auth/guards/local.guard.ts b/src/modules/auth/guards/local.guard.ts new file mode 100644 index 0000000..c2de171 --- /dev/null +++ b/src/modules/auth/guards/local.guard.ts @@ -0,0 +1,11 @@ +import { ExecutionContext, Injectable } from '@nestjs/common' +import { AuthGuard } from '@nestjs/passport' + +import { AuthStrategy } from '../auth.constant' + +@Injectable() +export class LocalGuard extends AuthGuard(AuthStrategy.LOCAL) { + async canActivate(context: ExecutionContext) { + return true + } +} diff --git a/src/modules/auth/guards/rbac.guard.ts b/src/modules/auth/guards/rbac.guard.ts new file mode 100644 index 0000000..8545adb --- /dev/null +++ b/src/modules/auth/guards/rbac.guard.ts @@ -0,0 +1,76 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { FastifyRequest } from 'fastify' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { AuthService } from '~/modules/auth/auth.service' + +import { ALLOW_ANON_KEY, PERMISSION_KEY, PUBLIC_KEY, Roles } from '../auth.constant' + +@Injectable() +export class RbacGuard implements CanActivate { + constructor( + private reflector: Reflector, + private authService: AuthService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride(PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]) + + if (isPublic) + return true + + const request = context.switchToHttp().getRequest() + + const { user } = request + if (!user) + throw new UnauthorizedException('登录无效') + + // allowAnon 是需要登录后可访问(无需权限), Public 则是无需登录也可访问. + const allowAnon = this.reflector.get( + ALLOW_ANON_KEY, + context.getHandler(), + ) + if (allowAnon) + return true + + const payloadPermission = this.reflector.getAllAndOverride< + string | string[] + >(PERMISSION_KEY, [context.getHandler(), context.getClass()]) + + // 控制器没有设置接口权限,则默认通过 + if (!payloadPermission) + return true + + // 管理员放开所有权限 + if (user.roles.includes(Roles.ADMIN)) + return true + + const allPermissions = await this.authService.getPermissionsCache(user.uid) ?? await this.authService.getPermissions(user.uid) + // console.log(allPermissions) + let canNext = false + + // handle permission strings + if (Array.isArray(payloadPermission)) { + // 只要有一个权限满足即可 + canNext = payloadPermission.every(i => allPermissions.includes(i)) + } + + if (typeof payloadPermission === 'string') + canNext = allPermissions.includes(payloadPermission) + + if (!canNext) + throw new BusinessException(ErrorEnum.NO_PERMISSION) + + return true + } +} diff --git a/src/modules/auth/guards/resource.guard.ts b/src/modules/auth/guards/resource.guard.ts new file mode 100644 index 0000000..b78da0c --- /dev/null +++ b/src/modules/auth/guards/resource.guard.ts @@ -0,0 +1,87 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { FastifyRequest } from 'fastify' + +import { isArray, isEmpty, isNil } from 'lodash' + +import { DataSource, In, Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' + +import { ErrorEnum } from '~/constants/error-code.constant' + +import { PUBLIC_KEY, RESOURCE_KEY, Roles } from '../auth.constant' +import { ResourceObject } from '../decorators/resource.decorator' + +@Injectable() +export class ResourceGuard implements CanActivate { + constructor( + private reflector: Reflector, + private dataSource: DataSource, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride(PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]) + + const request = context.switchToHttp().getRequest() + const isSse = request.headers.accept === 'text/event-stream' + // 忽略 sse 请求 + if (isPublic || isSse) + return true + + const { user } = request + + if (!user) + return false + + // 如果是检查资源所属,且不是超级管理员,还需要进一步判断是否是自己的数据 + const { entity, condition } = this.reflector.get( + RESOURCE_KEY, + context.getHandler(), + ) ?? { entity: null, condition: null } + + if (entity && !user.roles.includes(Roles.ADMIN)) { + const repo: Repository = this.dataSource.getRepository(entity) + + /** + * 获取请求中的 items (ids) 验证数据拥有者 + * @param request + */ + const getRequestItems = (request?: FastifyRequest): number[] => { + const { params = {}, body = {}, query = {} } = (request ?? {}) as any + const id = params.id ?? body.id ?? query.id + + if (id) + return [id] + + const { items } = body + return !isNil(items) && isArray(items) ? items : [] + } + + const items = getRequestItems(request) + if (isEmpty(items)) + throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) + + if (condition) + return condition(repo, items, user) + + const recordQuery = { + where: { + id: In(items), + user: { id: user.uid }, + }, + relations: ['user'], + } + + const records = await repo.find(recordQuery) + + if (isEmpty(records)) + throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) + } + + return true + } +} diff --git a/src/modules/auth/models/auth.model.ts b/src/modules/auth/models/auth.model.ts new file mode 100644 index 0000000..faeada1 --- /dev/null +++ b/src/modules/auth/models/auth.model.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class ImageCaptcha { + @ApiProperty({ description: 'base64格式的svg图片' }) + img: string + + @ApiProperty({ description: '验证码对应的唯一ID' }) + id: string +} + +export class LoginToken { + @ApiProperty({ description: 'JWT身份Token' }) + token: string +} diff --git a/src/modules/auth/services/captcha.service.ts b/src/modules/auth/services/captcha.service.ts new file mode 100644 index 0000000..612a6b7 --- /dev/null +++ b/src/modules/auth/services/captcha.service.ts @@ -0,0 +1,40 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' + +import Redis from 'ioredis' +import { isEmpty } from 'lodash' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { genCaptchaImgKey } from '~/helper/genRedisKey' +import { CaptchaLogService } from '~/modules/system/log/services/captcha-log.service' + +@Injectable() +export class CaptchaService { + constructor( + @InjectRedis() private redis: Redis, + + private captchaLogService: CaptchaLogService, + ) {} + + /** + * 校验图片验证码 + */ + async checkImgCaptcha(id: string, code: string): Promise { + const result = await this.redis.get(genCaptchaImgKey(id)) + if (isEmpty(result) || code.toLowerCase() !== result.toLowerCase()) + throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE) + + // 校验成功后移除验证码 + await this.redis.del(genCaptchaImgKey(id)) + } + + async log( + account: string, + code: string, + provider: 'sms' | 'email', + uid?: number, + ): Promise { + await this.captchaLogService.create(account, code, provider, uid) + } +} diff --git a/src/modules/auth/services/token.service.ts b/src/modules/auth/services/token.service.ts new file mode 100644 index 0000000..e680ecc --- /dev/null +++ b/src/modules/auth/services/token.service.ts @@ -0,0 +1,160 @@ +import { Inject, Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import dayjs from 'dayjs' + +import { ISecurityConfig, SecurityConfig } from '~/config' +import { RoleService } from '~/modules/system/role/role.service' +import { UserEntity } from '~/modules/user/user.entity' +import { generateUUID } from '~/utils' + +import { AccessTokenEntity } from '../entities/access-token.entity' +import { RefreshTokenEntity } from '../entities/refresh-token.entity' + +/** + * 令牌服务 + */ +@Injectable() +export class TokenService { + constructor( + private jwtService: JwtService, + private roleService: RoleService, + @Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig, + ) {} + + /** + * 根据accessToken刷新AccessToken与RefreshToken + * @param accessTokenSign + * @param response + */ + async refreshToken(accessToken: AccessTokenEntity) { + const { user, refreshToken } = accessToken + + if (refreshToken) { + const now = dayjs() + // 判断refreshToken是否过期 + if (now.isAfter(refreshToken.expired_at)) + return null + + const roleIds = await this.roleService.getRoleIdsByUser(user.id) + const roleValues = await this.roleService.getRoleValues(roleIds) + + // 如果没过期则生成新的access_token和refresh_token + const token = await this.generateAccessToken(user.id, roleValues) + + await accessToken.remove() + return token + } + return null + } + + generateJwtSign(payload: any) { + const jwtSign = this.jwtService.sign(payload) + + return jwtSign + } + + async generateAccessToken(uid: number, roles: string[] = []) { + const payload: IAuthUser = { + uid, + pv: 1, + roles, + } + + const jwtSign = this.jwtService.sign(payload) + + // 生成accessToken + const accessToken = new AccessTokenEntity() + accessToken.value = jwtSign + accessToken.user = { id: uid } as UserEntity + accessToken.expired_at = dayjs() + .add(this.securityConfig.jwtExprire, 'second') + .toDate() + + await accessToken.save() + + // 生成refreshToken + const refreshToken = await this.generateRefreshToken(accessToken, dayjs()) + + return { + accessToken: jwtSign, + refreshToken, + } + } + + /** + * 生成新的RefreshToken并存入数据库 + * @param accessToken + * @param now + */ + async generateRefreshToken( + accessToken: AccessTokenEntity, + now: dayjs.Dayjs, + ): Promise { + const refreshTokenPayload = { + uuid: generateUUID(), + } + + const refreshTokenSign = this.jwtService.sign(refreshTokenPayload, { + secret: this.securityConfig.refreshSecret, + }) + + const refreshToken = new RefreshTokenEntity() + refreshToken.value = refreshTokenSign + refreshToken.expired_at = now + .add(this.securityConfig.refreshExpire, 'second') + .toDate() + refreshToken.accessToken = accessToken + + await refreshToken.save() + + return refreshTokenSign + } + + /** + * 检查accessToken是否存在 + * @param value + */ + async checkAccessToken(value: string) { + return AccessTokenEntity.findOne({ + where: { value }, + relations: ['user', 'refreshToken'], + cache: true, + }) + } + + /** + * 移除AccessToken且自动移除关联的RefreshToken + * @param value + */ + async removeAccessToken(value: string) { + const accessToken = await AccessTokenEntity.findOne({ + where: { value }, + }) + if (accessToken) + await accessToken.remove() + } + + /** + * 移除RefreshToken + * @param value + */ + async removeRefreshToken(value: string) { + const refreshToken = await RefreshTokenEntity.findOne({ + where: { value }, + relations: ['accessToken'], + }) + if (refreshToken) { + if (refreshToken.accessToken) + await refreshToken.accessToken.remove() + await refreshToken.remove() + } + } + + /** + * 验证Token是否正确,如果正确则返回所属用户对象 + * @param token + */ + async verifyAccessToken(token: string): Promise { + return this.jwtService.verify(token) + } +} diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..1fc00fd --- /dev/null +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -0,0 +1,24 @@ +import { Inject, Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' +import { ExtractJwt, Strategy } from 'passport-jwt' + +import { ISecurityConfig, SecurityConfig } from '~/config' + +import { AuthStrategy } from '../auth.constant' + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy, AuthStrategy.JWT) { + constructor( + @Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: securityConfig.jwtSecret, + }) + } + + async validate(payload: IAuthUser) { + return payload + } +} diff --git a/src/modules/auth/strategies/local.strategy.ts b/src/modules/auth/strategies/local.strategy.ts new file mode 100644 index 0000000..69c59a9 --- /dev/null +++ b/src/modules/auth/strategies/local.strategy.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' +import { Strategy } from 'passport-local' + +import { AuthStrategy } from '../auth.constant' +import { AuthService } from '../auth.service' + +@Injectable() +export class LocalStrategy extends PassportStrategy( + Strategy, + AuthStrategy.LOCAL, +) { + constructor(private authService: AuthService) { + super({ + usernameField: 'credential', + passwordField: 'password', + }) + } + + async validate(username: string, password: string): Promise { + const user = await this.authService.validateUser(username, password) + return user + } +} diff --git a/src/modules/health/health.controller.ts b/src/modules/health/health.controller.ts new file mode 100644 index 0000000..ad81caa --- /dev/null +++ b/src/modules/health/health.controller.ts @@ -0,0 +1,71 @@ +import { Controller, Get } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' +import { + DiskHealthIndicator, + HealthCheck, + HttpHealthIndicator, + MemoryHealthIndicator, + TypeOrmHealthIndicator, +} from '@nestjs/terminus' + +import { Perm, definePermission } from '../auth/decorators/permission.decorator' + +export const PermissionHealth = definePermission('app:health', { + NETWORK: 'network', + DB: 'database', + MH: 'memory-heap', + MR: 'memory-rss', + DISK: 'disk', +} as const) + +@ApiTags('Health - 健康检查') +@Controller('health') +export class HealthController { + constructor( + private http: HttpHealthIndicator, + private db: TypeOrmHealthIndicator, + private memory: MemoryHealthIndicator, + private disk: DiskHealthIndicator, + ) {} + + @Get('network') + @HealthCheck() + @Perm(PermissionHealth.NETWORK) + async checkNetwork() { + return this.http.pingCheck('buqiyuan', 'https://buqiyuan.gitee.io/') + } + + @Get('database') + @HealthCheck() + @Perm(PermissionHealth.DB) + async checkDatabase() { + return this.db.pingCheck('database') + } + + @Get('memory-heap') + @HealthCheck() + @Perm(PermissionHealth.MH) + async checkMemoryHeap() { + // the process should not use more than 200MB memory + return this.memory.checkHeap('memory-heap', 200 * 1024 * 1024) + } + + @Get('memory-rss') + @HealthCheck() + @Perm(PermissionHealth.MR) + async checkMemoryRSS() { + // the process should not have more than 200MB RSS memory allocated + return this.memory.checkRSS('memory-rss', 200 * 1024 * 1024) + } + + @Get('disk') + @HealthCheck() + @Perm(PermissionHealth.DISK) + async checkDisk() { + return this.disk.checkStorage('disk', { + // The used disk storage should not exceed 75% of the full disk size + thresholdPercent: 0.75, + path: '/', + }) + } +} diff --git a/src/modules/health/health.module.ts b/src/modules/health/health.module.ts new file mode 100644 index 0000000..141fadc --- /dev/null +++ b/src/modules/health/health.module.ts @@ -0,0 +1,11 @@ +import { HttpModule } from '@nestjs/axios' +import { Module } from '@nestjs/common' +import { TerminusModule } from '@nestjs/terminus' + +import { HealthController } from './health.controller' + +@Module({ + imports: [TerminusModule, HttpModule], + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/src/modules/netdisk/manager/manage.class.ts b/src/modules/netdisk/manager/manage.class.ts new file mode 100644 index 0000000..ef774c1 --- /dev/null +++ b/src/modules/netdisk/manager/manage.class.ts @@ -0,0 +1,68 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export type FileType = 'file' | 'dir'; + +export class SFileInfo { + @ApiProperty({ description: '文件id' }) + id: string; + + @ApiProperty({ description: '文件类型', enum: ['file', 'dir'] }) + type: FileType; + + @ApiProperty({ description: '文件名称' }) + name: string; + + @ApiProperty({ description: '存入时间', type: Date }) + putTime?: Date; + + @ApiProperty({ description: '文件大小, byte单位' }) + fsize?: string; + + @ApiProperty({ description: '文件的mime-type' }) + mimeType?: string; + + @ApiProperty({ description: '所属目录' }) + belongTo?: string; +} + +export class SFileList { + @ApiProperty({ description: '文件列表', type: [SFileInfo] }) + list: SFileInfo[]; + + @ApiProperty({ description: '分页标志,空则代表加载完毕' }) + marker?: string; +} + +export class UploadToken { + @ApiProperty({ description: '上传token' }) + token: string; +} + +export class SFileInfoDetail { + @ApiProperty({ description: '文件大小,int64类型,单位为字节(Byte)' }) + fsize: number; + + @ApiProperty({ description: '文件HASH值' }) + hash: string; + + @ApiProperty({ description: '文件MIME类型,string类型' }) + mimeType: string; + + @ApiProperty({ + description: + '文件存储类型,2 表示归档存储,1 表示低频存储,0表示普通存储。', + }) + type: number; + + @ApiProperty({ description: '文件上传时间', type: Date }) + putTime: Date; + + @ApiProperty({ description: '文件md5值' }) + md5: string; + + @ApiProperty({ description: '上传人' }) + uploader: string; + + @ApiProperty({ description: '文件备注' }) + mark?: string; +} diff --git a/src/modules/netdisk/manager/manage.controller.ts b/src/modules/netdisk/manager/manage.controller.ts new file mode 100644 index 0000000..652d6aa --- /dev/null +++ b/src/modules/netdisk/manager/manage.controller.ts @@ -0,0 +1,153 @@ +import { Body, Controller, Get, Post, Query } from '@nestjs/common' +import { + ApiOkResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' + +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' + +import { checkIsDemoMode } from '~/utils' + +import { SFileInfoDetail, SFileList, UploadToken } from './manage.class' +import { + DeleteDto, + FileInfoDto, + FileOpDto, + GetFileListDto, + MKDirDto, + MarkFileDto, + RenameDto, +} from './manage.dto' +import { NetDiskManageService } from './manage.service' + +export const permissions = definePermission('netdisk:manage', { + LIST: 'list', + CREATE: 'create', + INFO: 'info', + UPDATE: 'update', + DELETE: 'delete', + MKDIR: 'mkdir', + TOKEN: 'token', + MARK: 'mark', + DOWNLOAD: 'download', + RENAME: 'rename', + CUT: 'cut', + COPY: 'copy', +} as const) + +@ApiTags('NetDiskManage - 网盘管理模块') +@Controller('manage') +export class NetDiskManageController { + constructor(private manageService: NetDiskManageService) {} + + @Get('list') + @ApiOperation({ summary: '获取文件列表' }) + @ApiOkResponse({ type: SFileList }) + @Perm(permissions.LIST) + async list(@Query() dto: GetFileListDto): Promise { + return await this.manageService.getFileList(dto.path, dto.marker, dto.key) + } + + @Post('mkdir') + @ApiOperation({ summary: '创建文件夹,支持多级' }) + @Perm(permissions.MKDIR) + async mkdir(@Body() dto: MKDirDto): Promise { + const result = await this.manageService.checkFileExist( + `${dto.path}${dto.dirName}/`, + ) + if (result) + throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST) + + await this.manageService.createDir(`${dto.path}${dto.dirName}`) + } + + @Get('token') + @ApiOperation({ summary: '获取上传Token,无Token前端无法上传' }) + @ApiOkResponse({ type: UploadToken }) + @Perm(permissions.TOKEN) + async token(@AuthUser() user: IAuthUser): Promise { + checkIsDemoMode() + + return { + token: this.manageService.createUploadToken(`${user.uid}`), + } + } + + @Get('info') + @ApiOperation({ summary: '获取文件详细信息' }) + @ApiOkResponse({ type: SFileInfoDetail }) + @Perm(permissions.INFO) + async info(@Query() dto: FileInfoDto): Promise { + return await this.manageService.getFileInfo(dto.name, dto.path) + } + + @Post('mark') + @ApiOperation({ summary: '添加文件备注' }) + @Perm(permissions.MARK) + async mark(@Body() dto: MarkFileDto): Promise { + await this.manageService.changeFileHeaders(dto.name, dto.path, { + mark: dto.mark, + }) + } + + @Get('download') + @ApiOperation({ summary: '获取下载链接,不支持下载文件夹' }) + @ApiOkResponse({ type: String }) + @Perm(permissions.DOWNLOAD) + async download(@Query() dto: FileInfoDto): Promise { + return this.manageService.getDownloadLink(`${dto.path}${dto.name}`) + } + + @Post('rename') + @ApiOperation({ summary: '重命名文件或文件夹' }) + @Perm(permissions.RENAME) + async rename(@Body() dto: RenameDto): Promise { + const result = await this.manageService.checkFileExist( + `${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}`, + ) + if (result) + throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST) + + if (dto.type === 'file') + await this.manageService.renameFile(dto.path, dto.name, dto.toName) + else + await this.manageService.renameDir(dto.path, dto.name, dto.toName) + } + + @Post('delete') + @ApiOperation({ summary: '删除文件或文件夹' }) + @Perm(permissions.DELETE) + async delete(@Body() dto: DeleteDto): Promise { + await this.manageService.deleteMultiFileOrDir(dto.files, dto.path) + } + + @Post('cut') + @ApiOperation({ summary: '剪切文件或文件夹,支持批量' }) + @Perm(permissions.CUT) + async cut(@Body() dto: FileOpDto): Promise { + if (dto.originPath === dto.toPath) + throw new BusinessException(ErrorEnum.OSS_NO_OPERATION_REQUIRED) + + await this.manageService.moveMultiFileOrDir( + dto.files, + dto.originPath, + dto.toPath, + ) + } + + @Post('copy') + @ApiOperation({ summary: '复制文件或文件夹,支持批量' }) + @Perm(permissions.COPY) + async copy(@Body() dto: FileOpDto): Promise { + await this.manageService.copyMultiFileOrDir( + dto.files, + dto.originPath, + dto.toPath, + ) + } +} diff --git a/src/modules/netdisk/manager/manage.dto.ts b/src/modules/netdisk/manager/manage.dto.ts new file mode 100644 index 0000000..984ef9b --- /dev/null +++ b/src/modules/netdisk/manager/manage.dto.ts @@ -0,0 +1,162 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { + ArrayMaxSize, + IsNotEmpty, + IsOptional, + IsString, + Matches, + Validate, + ValidateIf, + ValidateNested, + ValidationArguments, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator' +import { isEmpty } from 'lodash' + +import { NETDISK_HANDLE_MAX_ITEM } from '~/constants/oss.constant' + +@ValidatorConstraint({ name: 'IsLegalNameExpression', async: false }) +export class IsLegalNameExpression implements ValidatorConstraintInterface { + validate(value: string, args: ValidationArguments) { + try { + if (isEmpty(value)) + throw new Error('dir name is empty') + + if (value.includes('/')) + throw new Error('dir name not allow /') + + return true + } + catch (e) { + return false + } + } + + defaultMessage(_args: ValidationArguments) { + // here you can provide default error message if validation failed + return 'file or dir name invalid' + } +} + +export class FileOpItem { + @ApiProperty({ description: '文件类型', enum: ['file', 'dir'] }) + @IsString() + @Matches(/(^file$)|(^dir$)/) + type: string + + @ApiProperty({ description: '文件名称' }) + @IsString() + @IsNotEmpty() + @Validate(IsLegalNameExpression) + name: string +} + +export class GetFileListDto { + @ApiProperty({ description: '分页标识' }) + @IsOptional() + @IsString() + marker: string + + @ApiProperty({ description: '当前路径' }) + @IsString() + path: string + + @ApiPropertyOptional({ description: '搜索关键字' }) + @Validate(IsLegalNameExpression) + @ValidateIf(o => !isEmpty(o.key)) + @IsString() + key: string +} + +export class MKDirDto { + @ApiProperty({ description: '文件夹名称' }) + @IsNotEmpty() + @IsString() + @Validate(IsLegalNameExpression) + dirName: string + + @ApiProperty({ description: '所属路径' }) + @IsString() + path: string +} + +export class RenameDto { + @ApiProperty({ description: '文件类型' }) + @IsString() + @Matches(/(^file$)|(^dir$)/) + type: string + + @ApiProperty({ description: '更改的名称' }) + @IsString() + @IsNotEmpty() + @Validate(IsLegalNameExpression) + toName: string + + @ApiProperty({ description: '原来的名称' }) + @IsString() + @IsNotEmpty() + @Validate(IsLegalNameExpression) + name: string + + @ApiProperty({ description: '路径' }) + @IsString() + path: string +} + +export class FileInfoDto { + @ApiProperty({ description: '文件名' }) + @IsString() + @IsNotEmpty() + @Validate(IsLegalNameExpression) + name: string + + @ApiProperty({ description: '文件所在路径' }) + @IsString() + path: string +} + +export class DeleteDto { + @ApiProperty({ description: '需要操作的文件或文件夹', type: [FileOpItem] }) + @Type(() => FileOpItem) + @ArrayMaxSize(NETDISK_HANDLE_MAX_ITEM) + @ValidateNested({ each: true }) + files: FileOpItem[] + + @ApiProperty({ description: '所在目录' }) + @IsString() + path: string +} + +export class MarkFileDto { + @ApiProperty({ description: '文件名' }) + @IsString() + @IsNotEmpty() + @Validate(IsLegalNameExpression) + name: string + + @ApiProperty({ description: '文件所在路径' }) + @IsString() + path: string + + @ApiProperty({ description: '备注信息' }) + @IsString() + mark: string +} + +export class FileOpDto { + @ApiProperty({ description: '需要操作的文件或文件夹', type: [FileOpItem] }) + @Type(() => FileOpItem) + @ArrayMaxSize(NETDISK_HANDLE_MAX_ITEM) + @ValidateNested({ each: true }) + files: FileOpItem[] + + @ApiProperty({ description: '操作前的目录' }) + @IsString() + originPath: string + + @ApiProperty({ description: '操作后的目录' }) + @IsString() + toPath: string +} diff --git a/src/modules/netdisk/manager/manage.service.ts b/src/modules/netdisk/manager/manage.service.ts new file mode 100644 index 0000000..3574a65 --- /dev/null +++ b/src/modules/netdisk/manager/manage.service.ts @@ -0,0 +1,930 @@ +import { basename, extname } from 'node:path' + +import { Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { isEmpty } from 'lodash' +import * as qiniu from 'qiniu' +import { auth, conf, rs } from 'qiniu' + +import { ConfigKeyPaths } from '~/config' +import { NETDISK_COPY_SUFFIX, NETDISK_DELIMITER, NETDISK_HANDLE_MAX_ITEM, NETDISK_LIMIT } from '~/constants/oss.constant' + +import { AccountInfo } from '~/modules/user/user.model' +import { UserService } from '~/modules/user/user.service' + +import { generateRandomValue } from '~/utils' + +import { SFileInfo, SFileInfoDetail, SFileList } from './manage.class' +import { FileOpItem } from './manage.dto' + +@Injectable() +export class NetDiskManageService { + private config: conf.ConfigOptions + private mac: auth.digest.Mac + private bucketManager: rs.BucketManager + + private get qiniuConfig() { + return this.configService.get('oss', { infer: true }) + } + + constructor( + private configService: ConfigService, + private userService: UserService, + ) { + this.mac = new qiniu.auth.digest.Mac( + this.qiniuConfig.accessKey, + this.qiniuConfig.secretKey, + ) + this.config = new qiniu.conf.Config({ + zone: this.qiniuConfig.zone, + }) + // bucket manager + this.bucketManager = new qiniu.rs.BucketManager(this.mac, this.config) + } + + /** + * 获取文件列表 + * @param prefix 当前文件夹路径,搜索模式下会被忽略 + * @param marker 下一页标识 + * @returns iFileListResult + */ + async getFileList(prefix = '', marker = '', skey = ''): Promise { + // 是否需要搜索 + const searching = !isEmpty(skey) + return new Promise((resolve, reject) => { + this.bucketManager.listPrefix( + this.qiniuConfig.bucket, + { + prefix: searching ? '' : prefix, + limit: NETDISK_LIMIT, + delimiter: searching ? '' : NETDISK_DELIMITER, + marker, + }, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + // 如果这个nextMarker不为空,那么还有未列举完毕的文件列表,下次调用listPrefix的时候, + // 指定options里面的marker为这个值 + const fileList: SFileInfo[] = [] + // 处理目录,但只有非搜索模式下可用 + if (!searching && !isEmpty(respBody.commonPrefixes)) { + // dir + for (const dirPath of respBody.commonPrefixes) { + const name = (dirPath as string) + .substr(0, dirPath.length - 1) + .replace(prefix, '') + if (isEmpty(skey) || name.includes(skey)) { + fileList.push({ + name: (dirPath as string) + .substr(0, dirPath.length - 1) + .replace(prefix, ''), + type: 'dir', + id: generateRandomValue(10), + }) + } + } + } + // handle items + if (!isEmpty(respBody.items)) { + // file + for (const item of respBody.items) { + // 搜索模式下处理 + if (searching) { + const pathList: string[] = item.key.split(NETDISK_DELIMITER) + // dir is empty stirng, file is key string + const name = pathList.pop() + if ( + item.key.endsWith(NETDISK_DELIMITER) + && pathList[pathList.length - 1].includes(skey) + ) { + // 结果是目录 + const ditName = pathList.pop() + fileList.push({ + id: generateRandomValue(10), + name: ditName, + type: 'dir', + belongTo: pathList.join(NETDISK_DELIMITER), + }) + } + else if (name.includes(skey)) { + // 文件 + fileList.push({ + id: generateRandomValue(10), + name, + type: 'file', + fsize: item.fsize, + mimeType: item.mimeType, + putTime: new Date(Number.parseInt(item.putTime) / 10000), + belongTo: pathList.join(NETDISK_DELIMITER), + }) + } + } + else { + // 正常获取列表 + const fileKey = item.key.replace(prefix, '') as string + if (!isEmpty(fileKey)) { + fileList.push({ + id: generateRandomValue(10), + name: fileKey, + type: 'file', + fsize: item.fsize, + mimeType: item.mimeType, + putTime: new Date(Number.parseInt(item.putTime) / 10000), + }) + } + } + } + } + resolve({ + list: fileList, + marker: respBody.marker || null, + }) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + + /** + * 获取文件信息 + */ + async getFileInfo(name: string, path: string): Promise { + return new Promise((resolve, reject) => { + this.bucketManager.stat( + this.qiniuConfig.bucket, + `${path}${name}`, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + const detailInfo: SFileInfoDetail = { + fsize: respBody.fsize, + hash: respBody.hash, + md5: respBody.md5, + mimeType: respBody.mimeType.split('/x-qn-meta')[0], + putTime: new Date(Number.parseInt(respBody.putTime) / 10000), + type: respBody.type, + uploader: '', + mark: respBody?.['x-qn-meta']?.['!mark'] ?? '', + } + if (!respBody.endUser) { + resolve(detailInfo) + } + else { + this.userService + .getAccountInfo(Number.parseInt(respBody.endUser)) + .then((user: AccountInfo) => { + if (isEmpty(user)) { + resolve(detailInfo) + } + else { + detailInfo.uploader = user.username + resolve(detailInfo) + } + }) + } + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + + /** + * 修改文件MimeType + */ + async changeFileHeaders( + name: string, + path: string, + headers: { [k: string]: string }, + ): Promise { + return new Promise((resolve, reject) => { + this.bucketManager.changeHeaders( + this.qiniuConfig.bucket, + `${path}${name}`, + headers, + (err, _, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + + /** + * 创建文件夹 + * @returns true创建成功 + */ + async createDir(dirName: string): Promise { + const safeDirName = dirName.endsWith('/') ? dirName : `${dirName}/` + return new Promise((resolve, reject) => { + // 上传一个空文件以用于显示文件夹效果 + const formUploader = new qiniu.form_up.FormUploader(this.config) + const putExtra = new qiniu.form_up.PutExtra() + formUploader.put( + this.createUploadToken(''), + safeDirName, + ' ', + putExtra, + (respErr, respBody, respInfo) => { + if (respErr) { + reject(respErr) + return + } + if (respInfo.statusCode === 200) { + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + + /** + * 检查文件是否存在,同可检查目录 + */ + async checkFileExist(filePath: string): Promise { + return new Promise((resolve, reject) => { + // fix path end must a / + + // 检测文件夹是否存在 + this.bucketManager.stat( + this.qiniuConfig.bucket, + filePath, + (respErr, respBody, respInfo) => { + if (respErr) { + reject(respErr) + return + } + if (respInfo.statusCode === 200) { + // 文件夹存在 + resolve(true) + } + else if (respInfo.statusCode === 612) { + // 文件夹不存在 + resolve(false) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + + /** + * 创建Upload Token, 默认过期时间一小时 + * @returns upload token + */ + createUploadToken(endUser: string): string { + const policy = new qiniu.rs.PutPolicy({ + scope: this.qiniuConfig.bucket, + insertOnly: 1, + fsizeLimit: 1024 ** 2 * 10, + endUser, + }) + const uploadToken = policy.uploadToken(this.mac) + return uploadToken + } + + /** + * 重命名文件 + * @param dir 文件路径 + * @param name 文件名称 + */ + async renameFile(dir: string, name: string, toName: string): Promise { + const fileName = `${dir}${name}` + const toFileName = `${dir}${toName}` + const op = { + force: true, + } + return new Promise((resolve, reject) => { + this.bucketManager.move( + this.qiniuConfig.bucket, + fileName, + this.qiniuConfig.bucket, + toFileName, + op, + (err, respBody, respInfo) => { + if (err) { + reject(err) + } + else { + if (respInfo.statusCode === 200) { + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + } + }, + ) + }) + } + + /** + * 移动文件 + */ + async moveFile(dir: string, toDir: string, name: string): Promise { + const fileName = `${dir}${name}` + const toFileName = `${toDir}${name}` + const op = { + force: true, + } + return new Promise((resolve, reject) => { + this.bucketManager.move( + this.qiniuConfig.bucket, + fileName, + this.qiniuConfig.bucket, + toFileName, + op, + (err, respBody, respInfo) => { + if (err) { + reject(err) + } + else { + if (respInfo.statusCode === 200) { + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + } + }, + ) + }) + } + + /** + * 复制文件 + */ + async copyFile(dir: string, toDir: string, name: string): Promise { + const fileName = `${dir}${name}` + // 拼接文件名 + const ext = extname(name) + const bn = basename(name, ext) + const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}` + const op = { + force: true, + } + return new Promise((resolve, reject) => { + this.bucketManager.copy( + this.qiniuConfig.bucket, + fileName, + this.qiniuConfig.bucket, + toFileName, + op, + (err, respBody, respInfo) => { + if (err) { + reject(err) + } + else { + if (respInfo.statusCode === 200) { + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + } + }, + ) + }) + } + + /** + * 重命名文件夹 + */ + async renameDir(path: string, name: string, toName: string): Promise { + const dirName = `${path}${name}` + const toDirName = `${path}${toName}` + let hasFile = true + let marker = '' + const op = { + force: true, + } + const bucketName = this.qiniuConfig.bucket + while (hasFile) { + await new Promise((resolve, reject) => { + // 列举当前目录下的所有文件 + this.bucketManager.listPrefix( + this.qiniuConfig.bucket, + { + prefix: dirName, + limit: NETDISK_HANDLE_MAX_ITEM, + marker, + }, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + const moveOperations = respBody.items.map((item) => { + const { key } = item + const destKey = key.replace(dirName, toDirName) + return qiniu.rs.moveOp( + bucketName, + key, + bucketName, + destKey, + op, + ) + }) + this.bucketManager.batch( + moveOperations, + (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2) + return + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) + hasFile = false + else + marker = respBody.marker + + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, + ), + ) + } + }, + ) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + } + + /** + * 获取七牛下载的文件url链接 + * @param key 文件路径 + * @returns 连接 + */ + getDownloadLink(key: string): string { + if (this.qiniuConfig.access === 'public') { + return this.bucketManager.publicDownloadUrl(this.qiniuConfig.domain, key) + } + else if (this.qiniuConfig.access === 'private') { + return this.bucketManager.privateDownloadUrl( + this.qiniuConfig.domain, + key, + Date.now() / 1000 + 36000, + ) + } + throw new Error('qiniu config access type not support') + } + + /** + * 删除文件 + * @param dir 删除的文件夹目录 + * @param name 文件名 + */ + async deleteFile(dir: string, name: string): Promise { + return new Promise((resolve, reject) => { + this.bucketManager.delete( + this.qiniuConfig.bucket, + `${dir}${name}`, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + + /** + * 删除文件夹 + * @param dir 文件夹所在的上级目录 + * @param name 文件目录名称 + */ + async deleteMultiFileOrDir( + fileList: FileOpItem[], + dir: string, + ): Promise { + const files = fileList.filter(item => item.type === 'file') + if (files.length > 0) { + // 批处理文件 + const copyOperations = files.map((item) => { + const fileName = `${dir}${item.name}` + return qiniu.rs.deleteOp(this.qiniuConfig.bucket, fileName) + }) + await new Promise((resolve, reject) => { + this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + resolve() + } + else if (respInfo.statusCode === 298) { + reject(new Error('操作异常,但部分文件夹删除成功')) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }) + }) + } + // 处理文件夹 + const dirs = fileList.filter(item => item.type === 'dir') + if (dirs.length > 0) { + // 处理文件夹的复制 + for (let i = 0; i < dirs.length; i++) { + const dirName = `${dir}${dirs[i].name}/` + let hasFile = true + let marker = '' + while (hasFile) { + await new Promise((resolve, reject) => { + // 列举当前目录下的所有文件 + this.bucketManager.listPrefix( + this.qiniuConfig.bucket, + { + prefix: dirName, + limit: NETDISK_HANDLE_MAX_ITEM, + marker, + }, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + const moveOperations = respBody.items.map((item) => { + const { key } = item + return qiniu.rs.deleteOp(this.qiniuConfig.bucket, key) + }) + this.bucketManager.batch( + moveOperations, + (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2) + return + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) + hasFile = false + else + marker = respBody.marker + + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, + ), + ) + } + }, + ) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + } + } + } + + /** + * 复制文件,含文件夹 + */ + async copyMultiFileOrDir( + fileList: FileOpItem[], + dir: string, + toDir: string, + ): Promise { + const files = fileList.filter(item => item.type === 'file') + const op = { + force: true, + } + if (files.length > 0) { + // 批处理文件 + const copyOperations = files.map((item) => { + const fileName = `${dir}${item.name}` + // 拼接文件名 + const ext = extname(item.name) + const bn = basename(item.name, ext) + const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}` + return qiniu.rs.copyOp( + this.qiniuConfig.bucket, + fileName, + this.qiniuConfig.bucket, + toFileName, + op, + ) + }) + await new Promise((resolve, reject) => { + this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + resolve() + } + else if (respInfo.statusCode === 298) { + reject(new Error('操作异常,但部分文件夹删除成功')) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }) + }) + } + // 处理文件夹 + const dirs = fileList.filter(item => item.type === 'dir') + if (dirs.length > 0) { + // 处理文件夹的复制 + for (let i = 0; i < dirs.length; i++) { + const dirName = `${dir}${dirs[i].name}/` + const copyDirName = `${toDir}${dirs[i].name}${NETDISK_COPY_SUFFIX}/` + let hasFile = true + let marker = '' + while (hasFile) { + await new Promise((resolve, reject) => { + // 列举当前目录下的所有文件 + this.bucketManager.listPrefix( + this.qiniuConfig.bucket, + { + prefix: dirName, + limit: NETDISK_HANDLE_MAX_ITEM, + marker, + }, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + const moveOperations = respBody.items.map((item) => { + const { key } = item + const destKey = key.replace(dirName, copyDirName) + return qiniu.rs.copyOp( + this.qiniuConfig.bucket, + key, + this.qiniuConfig.bucket, + destKey, + op, + ) + }) + this.bucketManager.batch( + moveOperations, + (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2) + return + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) + hasFile = false + else + marker = respBody.marker + + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, + ), + ) + } + }, + ) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + } + } + } + + /** + * 移动文件,含文件夹 + */ + async moveMultiFileOrDir( + fileList: FileOpItem[], + dir: string, + toDir: string, + ): Promise { + const files = fileList.filter(item => item.type === 'file') + const op = { + force: true, + } + if (files.length > 0) { + // 批处理文件 + const copyOperations = files.map((item) => { + const fileName = `${dir}${item.name}` + const toFileName = `${toDir}${item.name}` + return qiniu.rs.moveOp( + this.qiniuConfig.bucket, + fileName, + this.qiniuConfig.bucket, + toFileName, + op, + ) + }) + await new Promise((resolve, reject) => { + this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + resolve() + } + else if (respInfo.statusCode === 298) { + reject(new Error('操作异常,但部分文件夹删除成功')) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }) + }) + } + // 处理文件夹 + const dirs = fileList.filter(item => item.type === 'dir') + if (dirs.length > 0) { + // 处理文件夹的复制 + for (let i = 0; i < dirs.length; i++) { + const dirName = `${dir}${dirs[i].name}/` + const toDirName = `${toDir}${dirs[i].name}/` + // 移动的目录不是是自己 + if (toDirName.startsWith(dirName)) + continue + + let hasFile = true + let marker = '' + while (hasFile) { + await new Promise((resolve, reject) => { + // 列举当前目录下的所有文件 + this.bucketManager.listPrefix( + this.qiniuConfig.bucket, + { + prefix: dirName, + limit: NETDISK_HANDLE_MAX_ITEM, + marker, + }, + (err, respBody, respInfo) => { + if (err) { + reject(err) + return + } + if (respInfo.statusCode === 200) { + const moveOperations = respBody.items.map((item) => { + const { key } = item + const destKey = key.replace(dirName, toDirName) + return qiniu.rs.moveOp( + this.qiniuConfig.bucket, + key, + this.qiniuConfig.bucket, + destKey, + op, + ) + }) + this.bucketManager.batch( + moveOperations, + (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2) + return + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) + hasFile = false + else + marker = respBody.marker + + resolve() + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, + ), + ) + } + }, + ) + } + else { + reject( + new Error( + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, + ), + ) + } + }, + ) + }) + } + } + } + } +} diff --git a/src/modules/netdisk/netdisk.module.ts b/src/modules/netdisk/netdisk.module.ts new file mode 100644 index 0000000..aa5a3f7 --- /dev/null +++ b/src/modules/netdisk/netdisk.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common' + +import { RouterModule } from '@nestjs/core' + +import { UserModule } from '../user/user.module' + +import { NetDiskManageController } from './manager/manage.controller' +import { NetDiskManageService } from './manager/manage.service' +import { NetDiskOverviewController } from './overview/overview.controller' +import { NetDiskOverviewService } from './overview/overview.service' + +@Module({ + imports: [UserModule, RouterModule.register([ + { + path: 'netdisk', + module: NetdiskModule, + }, + ])], + controllers: [NetDiskManageController, NetDiskOverviewController], + providers: [NetDiskManageService, NetDiskOverviewService], +}) +export class NetdiskModule {} diff --git a/src/modules/netdisk/overview/overview.controller.ts b/src/modules/netdisk/overview/overview.controller.ts new file mode 100644 index 0000000..a7b7df9 --- /dev/null +++ b/src/modules/netdisk/overview/overview.controller.ts @@ -0,0 +1,49 @@ +import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager' +import { + Controller, + Get, + UseInterceptors, +} from '@nestjs/common' +import { + ApiOkResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger' + +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' + +import { OverviewSpaceInfo } from './overview.dto' +import { NetDiskOverviewService } from './overview.service' + +export const permissions = definePermission('netdisk:overview', { + DESC: 'desc', +} as const) + +@ApiTags('NetDiskOverview - 网盘概览模块') +@Controller('overview') +export class NetDiskOverviewController { + constructor(private overviewService: NetDiskOverviewService) {} + + @Get('desc') + @CacheKey('netdisk_overview_desc') + @CacheTTL(3600) + @UseInterceptors(CacheInterceptor) + @ApiOperation({ summary: '获取网盘空间数据统计' }) + @ApiOkResponse({ type: OverviewSpaceInfo }) + @Perm(permissions.DESC) + async space(): Promise { + const date = this.overviewService.getZeroHourAnd1Day(new Date()) + const hit = await this.overviewService.getHit(date) + const flow = await this.overviewService.getFlow(date) + const space = await this.overviewService.getSpace(date) + const count = await this.overviewService.getCount(date) + return { + fileSize: count.datas[count.datas.length - 1], + flowSize: flow.datas[flow.datas.length - 1], + hitSize: hit.datas[hit.datas.length - 1], + spaceSize: space.datas[space.datas.length - 1], + flowTrend: flow, + sizeTrend: space, + } + } +} diff --git a/src/modules/netdisk/overview/overview.dto.ts b/src/modules/netdisk/overview/overview.dto.ts new file mode 100644 index 0000000..9d77aff --- /dev/null +++ b/src/modules/netdisk/overview/overview.dto.ts @@ -0,0 +1,53 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class SpaceInfo { + @ApiProperty({ description: '当月的X号', type: [Number] }) + times: number[]; + + @ApiProperty({ description: '对应天数的容量, byte单位', type: [Number] }) + datas: number[]; +} + +export class CountInfo { + @ApiProperty({ description: '当月的X号', type: [Number] }) + times: number[]; + + @ApiProperty({ description: '对应天数的文件数量', type: [Number] }) + datas: number[]; +} + +export class FlowInfo { + @ApiProperty({ description: '当月的X号', type: [Number] }) + times: number[]; + + @ApiProperty({ description: '对应天数的耗费流量', type: [Number] }) + datas: number[]; +} + +export class HitInfo { + @ApiProperty({ description: '当月的X号', type: [Number] }) + times: number[]; + + @ApiProperty({ description: '对应天数的Get请求次数', type: [Number] }) + datas: number[]; +} + +export class OverviewSpaceInfo { + @ApiProperty({ description: '当前使用容量' }) + spaceSize: number; + + @ApiProperty({ description: '当前文件数量' }) + fileSize: number; + + @ApiProperty({ description: '当天使用流量' }) + flowSize: number; + + @ApiProperty({ description: '当天请求次数' }) + hitSize: number; + + @ApiProperty({ description: '流量趋势,从当月1号开始计算', type: FlowInfo }) + flowTrend: FlowInfo; + + @ApiProperty({ description: '容量趋势,从当月1号开始计算', type: SpaceInfo }) + sizeTrend: SpaceInfo; +} diff --git a/src/modules/netdisk/overview/overview.service.ts b/src/modules/netdisk/overview/overview.service.ts new file mode 100644 index 0000000..40ff410 --- /dev/null +++ b/src/modules/netdisk/overview/overview.service.ts @@ -0,0 +1,156 @@ +import { HttpService } from '@nestjs/axios' +import { Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import dayjs from 'dayjs' +import * as qiniu from 'qiniu' + +import { ConfigKeyPaths } from '~/config' +import { OSS_API } from '~/constants/oss.constant' + +import { CountInfo, FlowInfo, HitInfo, SpaceInfo } from './overview.dto' + +@Injectable() +export class NetDiskOverviewService { + private mac: qiniu.auth.digest.Mac + private readonly FORMAT = 'YYYYMMDDHHmmss' + private get qiniuConfig() { + return this.configService.get('oss', { infer: true }) + } + + constructor( + private configService: ConfigService, + private readonly httpService: HttpService, + ) { + this.mac = new qiniu.auth.digest.Mac( + this.qiniuConfig.accessKey, + this.qiniuConfig.secretKey, + ) + } + + /** 获取格式化后的起始和结束时间 */ + getStartAndEndDate(start: Date, end = new Date()) { + return [dayjs(start).format(this.FORMAT), dayjs(end).format(this.FORMAT)] + } + + /** + * 获取数据统计接口路径 + * @see: https://developer.qiniu.com/kodo/3906/statistic-interface + */ + getStatisticUrl(type: string, queryParams = {}) { + const defaultParams = { + $bucket: this.qiniuConfig.bucket, + g: 'day', + } + const searchParams = new URLSearchParams({ ...defaultParams, ...queryParams }) + return decodeURIComponent(`${OSS_API}/v6/${type}?${searchParams}`) + } + + /** 获取统计数据 */ + getStatisticData(url: string) { + const accessToken = qiniu.util.generateAccessTokenV2( + this.mac, + url, + 'GET', + 'application/x-www-form-urlencoded', + ) + return this.httpService.axiosRef.get(url, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': `${accessToken}`, + }, + }) + } + + /** + * 获取当天零时 + */ + getZeroHourToDay(current: Date): Date { + const year = dayjs(current).year() + const month = dayjs(current).month() + const date = dayjs(current).date() + return new Date(year, month, date, 0) + } + + /** + * 获取当月1号零时 + */ + getZeroHourAnd1Day(current: Date): Date { + const year = dayjs(current).year() + const month = dayjs(current).month() + return new Date(year, month, 1, 0) + } + + /** + * 该接口可以获取标准存储的当前存储量。可查询当天计量,统计延迟大概 5 分钟。 + * https://developer.qiniu.com/kodo/3908/statistic-space + */ + async getSpace(beginDate: Date, endDate = new Date()): Promise { + const [begin, end] = this.getStartAndEndDate(beginDate, endDate) + const url = this.getStatisticUrl('space', { begin, end }) + const { data } = await this.getStatisticData(url) + return { + datas: data.datas, + times: data.times.map((e) => { + return dayjs.unix(e).date() + }), + } + } + + /** + * 该接口可以获取标准存储的文件数量。可查询当天计量,统计延迟大概 5 分钟。 + * https://developer.qiniu.com/kodo/3914/count + */ + async getCount(beginDate: Date, endDate = new Date()): Promise { + const [begin, end] = this.getStartAndEndDate(beginDate, endDate) + const url = this.getStatisticUrl('count', { begin, end }) + const { data } = await this.getStatisticData(url) + return { + times: data.times.map((e) => { + return dayjs.unix(e).date() + }), + datas: data.datas, + } + } + + /** + * 外网流出流量统计 + * 该接口可以获取外网流出流量、CDN回源流量统计和 GET 请求次数。可查询当天计量,统计延迟大概 5 分钟。 + * https://developer.qiniu.com/kodo/3820/blob-io + */ + async getFlow(beginDate: Date, endDate = new Date()): Promise { + const [begin, end] = this.getStartAndEndDate(beginDate, endDate) + const url = this.getStatisticUrl('blob_io', { begin, end, $ftype: 0, $src: 'origin', select: 'flow' }) + const { data } = await this.getStatisticData(url) + const times = [] + const datas = [] + data.forEach((e) => { + times.push(dayjs(e.time).date()) + datas.push(e.values.flow) + }) + return { + times, + datas, + } + } + + /** + * GET 请求次数统计 + * 该接口可以获取外网流出流量、CDN回源流量统计和 GET 请求次数。可查询当天计量,统计延迟大概 5 分钟。 + * https://developer.qiniu.com/kodo/3820/blob-io + */ + async getHit(beginDate: Date, endDate = new Date()): Promise { + const [begin, end] = this.getStartAndEndDate(beginDate, endDate) + const url = this.getStatisticUrl('blob_io', { begin, end, $ftype: 0, $src: 'inner', select: 'hit' }) + const { data } = await this.getStatisticData(url) + const times = [] + const datas = [] + data.forEach((e) => { + times.push(dayjs(e.time).date()) + datas.push(e.values.hit) + }) + return { + times, + datas, + } + } +} diff --git a/src/modules/sse/sse.controller.ts b/src/modules/sse/sse.controller.ts new file mode 100644 index 0000000..1e475b7 --- /dev/null +++ b/src/modules/sse/sse.controller.ts @@ -0,0 +1,54 @@ +import { BeforeApplicationShutdown, Controller, Param, ParseIntPipe, Req, Res, Sse } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' +import { FastifyReply, FastifyRequest } from 'fastify' +import { Observable, interval } from 'rxjs' + +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' + +import { MessageEvent, SseService } from './sse.service' + +@ApiTags('System - sse模块') +@ApiSecurityAuth() +@Controller('sse') +export class SseController implements BeforeApplicationShutdown { + private replyMap: Map = new Map() + + constructor(private readonly sseService: SseService) {} + + private closeAllConnect() { + this.sseService.sendToAll({ + type: 'close', + data: 'bye~', + }) + this.replyMap.forEach((reply) => { + reply.raw.end().destroy() + }) + } + + // 通过控制台关闭程序时触发 + beforeApplicationShutdown() { + // console.log('beforeApplicationShutdown') + this.closeAllConnect() + } + + @Sse(':uid') + sse(@Param('uid', ParseIntPipe) uid: number, @Req() req: FastifyRequest, @Res() res: FastifyReply): Observable { + this.replyMap.set(uid, res) + + const subscription = interval(10000).subscribe(() => { + this.sseService.sendToClient(uid, { type: 'ping' }) + }) + + // 当客户端断开连接时 + req.raw.on('close', () => { + subscription.unsubscribe() + this.sseService.removeClient(uid) + this.replyMap.delete(uid) + // console.log(`user-${uid}已关闭`) + }) + + return new Observable((subscriber) => { + this.sseService.addClient(uid, subscriber) + }) + } +} diff --git a/src/modules/sse/sse.module.ts b/src/modules/sse/sse.module.ts new file mode 100644 index 0000000..e6af2b1 --- /dev/null +++ b/src/modules/sse/sse.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common' + +import { SseController } from './sse.controller' +import { SseService } from './sse.service' + +@Module({ + imports: [], + controllers: [SseController], + providers: [SseService], + exports: [SseService], +}) +export class SseModule {} diff --git a/src/modules/sse/sse.service.ts b/src/modules/sse/sse.service.ts new file mode 100644 index 0000000..076e415 --- /dev/null +++ b/src/modules/sse/sse.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common' +import { Subscriber } from 'rxjs' +import { In } from 'typeorm' + +import { ROOT_ROLE_ID } from '~/constants/system.constant' + +import { RoleEntity } from '~/modules/system/role/role.entity' +import { UserEntity } from '~/modules/user/user.entity' + +export interface MessageEvent { + data?: string | object + id?: string + type?: 'ping' | 'close' | 'updatePermsAndMenus' + retry?: number +} + +const clientMap: Map> = new Map() + +@Injectable() +export class SseService { + addClient(uid: number, subscriber: Subscriber) { + clientMap.set(uid, subscriber) + } + + removeClient(uid: number): void { + const client = clientMap.get(uid) + client?.complete() + clientMap.delete(uid) + } + + sendToClient(uid: number, data: MessageEvent): void { + const client = clientMap.get(uid) + client?.next?.(data) + } + + sendToAll(data: MessageEvent): void { + clientMap.forEach((client) => { + client.next(data) + }) + } + + /** + * 通知前端重新获取权限菜单 + * @param uid + * @constructor + */ + async noticeClientToUpdateMenusByUserIds(uid: number | number[]) { + const userIds = [].concat(uid) as number[] + userIds.forEach((uid) => { + this.sendToClient(uid, { type: 'updatePermsAndMenus' }) + }) + } + + /** + * 通过menuIds通知用户更新权限菜单 + */ + async noticeClientToUpdateMenusByMenuIds(menuIds: number[]): Promise { + const roleMenus = await RoleEntity.find({ + where: { + menus: { + id: In(menuIds), + }, + }, + }) + const roleIds = roleMenus.map(n => n.id).concat(ROOT_ROLE_ID) + await this.noticeClientToUpdateMenusByRoleIds(roleIds) + } + + /** + * 通过roleIds通知用户更新权限菜单 + */ + async noticeClientToUpdateMenusByRoleIds(roleIds: number[]): Promise { + const users = await UserEntity.find({ + where: { + roles: { + id: In(roleIds), + }, + }, + }) + if (users) { + const userIds = users.map(n => n.id) + await this.noticeClientToUpdateMenusByUserIds(userIds) + } + } +} diff --git a/src/modules/system/dept/dept.controller.ts b/src/modules/system/dept/dept.controller.ts new file mode 100644 index 0000000..81b6ef9 --- /dev/null +++ b/src/modules/system/dept/dept.controller.ts @@ -0,0 +1,83 @@ +import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { DeptEntity } from '~/modules/system/dept/dept.entity' + +import { DeptDto, DeptQueryDto } from './dept.dto' +import { DeptService } from './dept.service' + +export const permissions = definePermission('system:dept', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiSecurityAuth() +@ApiTags('System - 部门模块') +@Controller('depts') +export class DeptController { + constructor(private deptService: DeptService) {} + + @Get() + @ApiOperation({ summary: '获取部门列表' }) + @ApiResult({ type: [DeptEntity] }) + @Perm(permissions.LIST) + async list( + @Query() dto: DeptQueryDto, @AuthUser('uid') uid: number): Promise { + return this.deptService.getDeptTree(uid, dto) + } + + @Post() + @ApiOperation({ summary: '创建部门' }) + @Perm(permissions.CREATE) + async create(@Body() dto: DeptDto): Promise { + await this.deptService.create(dto) + } + + @Get(':id') + @ApiOperation({ summary: '查询部门信息' }) + @Perm(permissions.READ) + async info(@IdParam() id: number) { + return this.deptService.info(id) + } + + @Put(':id') + @ApiOperation({ summary: '更新部门' }) + @Perm(permissions.UPDATE) + async update( + @IdParam() id: number, @Body() updateDeptDto: DeptDto): Promise { + await this.deptService.update(id, updateDeptDto) + } + + @Delete(':id') + @ApiOperation({ summary: '删除部门' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + // 查询是否有关联用户或者部门,如果含有则无法删除 + const count = await this.deptService.countUserByDeptId(id) + if (count > 0) + throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_ASSOCIATED_USERS) + + const count2 = await this.deptService.countChildDept(id) + console.log('count2', count2) + if (count2 > 0) + throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_CHILD_DEPARTMENTS) + + await this.deptService.delete(id) + } + + // @Post('move') + // @ApiOperation({ summary: '部门移动排序' }) + // async move(@Body() dto: MoveDeptDto): Promise { + // await this.deptService.move(dto.depts); + // } +} diff --git a/src/modules/system/dept/dept.dto.ts b/src/modules/system/dept/dept.dto.ts new file mode 100644 index 0000000..5d79782 --- /dev/null +++ b/src/modules/system/dept/dept.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { + ArrayNotEmpty, + IsArray, + IsInt, + IsOptional, + IsString, + Min, + MinLength, + ValidateNested, +} from 'class-validator' + +export class DeptDto { + @ApiProperty({ description: '部门名称' }) + @IsString() + @MinLength(1) + name: string + + @ApiProperty({ description: '父级部门id' }) + @Type(() => Number) + @IsInt() + @IsOptional() + parentId: number + + @ApiProperty({ description: '排序编号', required: false }) + @IsInt() + @Min(0) + @IsOptional() + orderNo: number +} + +export class TransferDeptDto { + @ApiProperty({ description: '需要转移的管理员列表编号', type: [Number] }) + @IsArray() + @ArrayNotEmpty() + userIds: number[] + + @ApiProperty({ description: '需要转移过去的系统部门ID' }) + @IsInt() + @Min(0) + deptId: number +} + +export class MoveDept { + @ApiProperty({ description: '当前部门ID' }) + @IsInt() + @Min(0) + id: number + + @ApiProperty({ description: '移动到指定父级部门的ID' }) + @IsInt() + @Min(0) + @IsOptional() + parentId: number +} + +export class MoveDeptDto { + @ApiProperty({ description: '部门列表', type: [MoveDept] }) + @ValidateNested({ each: true }) + @Type(() => MoveDept) + depts: MoveDept[] +} + +export class DeptQueryDto { + @ApiProperty({ description: '部门名称' }) + @IsString() + @IsOptional() + name?: string +} diff --git a/src/modules/system/dept/dept.entity.ts b/src/modules/system/dept/dept.entity.ts new file mode 100644 index 0000000..3a69396 --- /dev/null +++ b/src/modules/system/dept/dept.entity.ts @@ -0,0 +1,36 @@ +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' +import { + Column, + Entity, + OneToMany, + Relation, + Tree, + TreeChildren, + TreeParent, +} from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +import { UserEntity } from '../../user/user.entity' + +@Entity({ name: 'sys_dept' }) +@Tree('materialized-path') +export class DeptEntity extends CommonEntity { + @Column() + @ApiProperty({ description: '部门名称' }) + name: string + + @Column({ nullable: true, default: 0 }) + @ApiProperty({ description: '排序' }) + orderNo: number + + @TreeChildren({ cascade: true }) + children: DeptEntity[] + + @TreeParent({ onDelete: 'SET NULL' }) + parent?: DeptEntity + + @ApiHideProperty() + @OneToMany(() => UserEntity, user => user.dept) + users: Relation +} diff --git a/src/modules/system/dept/dept.module.ts b/src/modules/system/dept/dept.module.ts new file mode 100644 index 0000000..d936ed9 --- /dev/null +++ b/src/modules/system/dept/dept.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { UserModule } from '../../user/user.module' +import { RoleModule } from '../role/role.module' + +import { DeptController } from './dept.controller' +import { DeptEntity } from './dept.entity' +import { DeptService } from './dept.service' + +const services = [DeptService] + +@Module({ + imports: [TypeOrmModule.forFeature([DeptEntity]), UserModule, RoleModule], + controllers: [DeptController], + providers: [...services], + exports: [TypeOrmModule, ...services], +}) +export class DeptModule {} diff --git a/src/modules/system/dept/dept.service.ts b/src/modules/system/dept/dept.service.ts new file mode 100644 index 0000000..1eae39b --- /dev/null +++ b/src/modules/system/dept/dept.service.ts @@ -0,0 +1,134 @@ +import { Injectable } from '@nestjs/common' +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' +import { isEmpty } from 'lodash' +import { EntityManager, Repository, TreeRepository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { DeptEntity } from '~/modules/system/dept/dept.entity' +import { UserEntity } from '~/modules/user/user.entity' + +import { deleteEmptyChildren } from '~/utils/list2tree.util' + +import { DeptDto, DeptQueryDto, MoveDept } from './dept.dto' + +@Injectable() +export class DeptService { + constructor( + @InjectRepository(UserEntity) + private userRepository: Repository, + @InjectRepository(DeptEntity) + private deptRepository: TreeRepository, + @InjectEntityManager() private entityManager: EntityManager, + ) {} + + async list(): Promise { + return this.deptRepository.find({ order: { orderNo: 'DESC' } }) + } + + async info(id: number): Promise { + const dept = await this.deptRepository + .createQueryBuilder('dept') + .leftJoinAndSelect('dept.parent', 'parent') + .where({ id }) + .getOne() + + if (isEmpty(dept)) + throw new BusinessException(ErrorEnum.DEPARTMENT_NOT_FOUND) + + return dept + } + + async create({ parentId, ...data }: DeptDto): Promise { + const parent = await this.deptRepository + .createQueryBuilder('dept') + .where({ id: parentId }) + .getOne() + + await this.deptRepository.save({ + ...data, + parent, + }) + } + + async update(id: number, { parentId, ...data }: DeptDto): Promise { + const item = await this.deptRepository + .createQueryBuilder('dept') + .where({ id }) + .getOne() + + const parent = await this.deptRepository + .createQueryBuilder('dept') + .where({ id: parentId }) + .getOne() + + await this.deptRepository.save({ + ...item, + ...data, + parent, + }) + } + + async delete(id: number): Promise { + await this.deptRepository.delete(id) + } + + /** + * 移动排序 + */ + async move(depts: MoveDept[]): Promise { + await this.entityManager.transaction(async (manager) => { + await manager.save(depts) + }) + } + + /** + * 根据部门查询关联的用户数量 + */ + async countUserByDeptId(id: number): Promise { + return this.userRepository.countBy({ dept: { id } }) + } + + /** + * 查找当前部门下的子部门数量 + */ + async countChildDept(id: number): Promise { + const item = await this.deptRepository.findOneBy({ id }) + return (await this.deptRepository.countDescendants(item)) - 1 + } + + /** + * 获取部门列表树结构 + */ + async getDeptTree( + uid: number, + { name }: DeptQueryDto, + ): Promise { + const tree: DeptEntity[] = [] + + if (name) { + const deptList = await this.deptRepository + .createQueryBuilder('dept') + .where('dept.name like :name', { name: `%${name}%` }) + .getMany() + + for (const dept of deptList) { + const deptTree = await this.deptRepository.findDescendantsTree(dept) + tree.push(deptTree) + } + + deleteEmptyChildren(tree) + + return tree + } + + const deptTree = await this.deptRepository.findTrees({ + depth: 2, + relations: ['parent'], + }) + + deleteEmptyChildren(deptTree) + + return deptTree + } +} diff --git a/src/modules/system/dict-item/dict-item.controller.ts b/src/modules/system/dict-item/dict-item.controller.ts new file mode 100644 index 0000000..6906aa6 --- /dev/null +++ b/src/modules/system/dict-item/dict-item.controller.ts @@ -0,0 +1,68 @@ +import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { Pagination } from '~/helper/paginate/pagination' +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { DictItemEntity } from '~/modules/system/dict-item/dict-item.entity' + +import { DictItemDto, DictItemQueryDto } from './dict-item.dto' +import { DictItemService } from './dict-item.service' + +export const permissions = definePermission('system:dict-item', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiTags('System - 字典项模块') +@ApiSecurityAuth() +@Controller('dict-item') +export class DictItemController { + constructor(private dictItemService: DictItemService) {} + + @Get() + @ApiOperation({ summary: '获取字典项列表' }) + @ApiResult({ type: [DictItemEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: DictItemQueryDto): Promise> { + return this.dictItemService.page(dto) + } + + @Post() + @ApiOperation({ summary: '新增字典项' }) + @Perm(permissions.CREATE) + async create(@Body() dto: DictItemDto, @AuthUser() user: IAuthUser): Promise { + await this.dictItemService.isExistKey(dto) + dto.createBy = dto.updateBy = user.uid + await this.dictItemService.create(dto) + } + + @Get(':id') + @ApiOperation({ summary: '查询字典项信息' }) + @ApiResult({ type: DictItemEntity }) + @Perm(permissions.READ) + async info(@IdParam() id: number): Promise { + return this.dictItemService.findOne(id) + } + + @Post(':id') + @ApiOperation({ summary: '更新字典项' }) + @Perm(permissions.UPDATE) + async update(@IdParam() id: number, @Body() dto: DictItemDto, @AuthUser() user: IAuthUser): Promise { + dto.updateBy = user.uid + await this.dictItemService.update(id, dto) + } + + @Delete(':id') + @ApiOperation({ summary: '删除指定的字典项' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + await this.dictItemService.delete(id) + } +} diff --git a/src/modules/system/dict-item/dict-item.dto.ts b/src/modules/system/dict-item/dict-item.dto.ts new file mode 100644 index 0000000..fa5cf65 --- /dev/null +++ b/src/modules/system/dict-item/dict-item.dto.ts @@ -0,0 +1,48 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { IsInt, IsOptional, IsString, MinLength } from 'class-validator' + +import { PagerDto } from '~/common/dto/pager.dto' + +import { DictItemEntity } from './dict-item.entity' + +export class DictItemDto extends PartialType(DictItemEntity) { + @ApiProperty({ description: '字典类型 ID' }) + @IsInt() + typeId: number + + @ApiProperty({ description: '字典项键名' }) + @IsString() + @MinLength(1) + label: string + + @ApiProperty({ description: '字典项值' }) + @IsString() + @MinLength(1) + value: string + + @ApiProperty({ description: '状态' }) + @IsOptional() + @IsInt() + status?: number + + @ApiProperty({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string +} + +export class DictItemQueryDto extends PagerDto { + @ApiProperty({ description: '字典类型 ID', required: true }) + @IsInt() + typeId: number + + @ApiProperty({ description: '字典项键名' }) + @IsString() + @IsOptional() + label?: string + + @ApiProperty({ description: '字典项值' }) + @IsString() + @IsOptional() + value?: string +} diff --git a/src/modules/system/dict-item/dict-item.entity.ts b/src/modules/system/dict-item/dict-item.entity.ts new file mode 100644 index 0000000..d4885a8 --- /dev/null +++ b/src/modules/system/dict-item/dict-item.entity.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm' + +import { CompleteEntity } from '~/common/entity/common.entity' + +import { DictTypeEntity } from '../dict-type/dict-type.entity' + +@Entity({ name: 'sys_dict_item' }) +export class DictItemEntity extends CompleteEntity { + @ManyToOne(() => DictTypeEntity, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'type_id' }) + type: DictTypeEntity + + @Column({ type: 'varchar', length: 50 }) + @ApiProperty({ description: '字典项键名' }) + label: string + + @Column({ type: 'varchar', length: 50 }) + @ApiProperty({ description: '字典项值' }) + value: string + + @Column({ nullable: true, comment: '字典项排序' }) + orderNo: number + + @Column({ type: 'tinyint', default: 1 }) + @ApiProperty({ description: ' 状态' }) + status: number + + @Column({ type: 'varchar', nullable: true }) + @ApiProperty({ description: '备注' }) + remark: string +} diff --git a/src/modules/system/dict-item/dict-item.module.ts b/src/modules/system/dict-item/dict-item.module.ts new file mode 100644 index 0000000..e5ee832 --- /dev/null +++ b/src/modules/system/dict-item/dict-item.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { DictItemController } from './dict-item.controller' +import { DictItemEntity } from './dict-item.entity' +import { DictItemService } from './dict-item.service' + +const services = [DictItemService] + +@Module({ + imports: [TypeOrmModule.forFeature([DictItemEntity])], + controllers: [DictItemController], + providers: [...services], + exports: [TypeOrmModule, ...services], +}) +export class DictItemModule {} diff --git a/src/modules/system/dict-item/dict-item.service.ts b/src/modules/system/dict-item/dict-item.service.ts new file mode 100644 index 0000000..ae5f0a0 --- /dev/null +++ b/src/modules/system/dict-item/dict-item.service.ts @@ -0,0 +1,97 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' + +import { Like, Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' +import { DictItemEntity } from '~/modules/system/dict-item/dict-item.entity' + +import { DictItemDto, DictItemQueryDto } from './dict-item.dto' + +@Injectable() +export class DictItemService { + constructor( + @InjectRepository(DictItemEntity) + private dictItemRepository: Repository, + ) {} + + /** + * 罗列所有配置 + */ + async page({ + page, + pageSize, + label, + value, + typeId, + }: DictItemQueryDto): Promise> { + const queryBuilder = this.dictItemRepository.createQueryBuilder('dict_item') + .orderBy({ orderNo: 'ASC' }) + .where({ + ...(label && { label: Like(`%${label}%`) }), + ...(value && { value: Like(`%${value}%`) }), + type: { + id: typeId, + }, + }) + + return paginate(queryBuilder, { page, pageSize }) + } + + /** + * 获取参数总数 + */ + async countConfigList(): Promise { + return this.dictItemRepository.count() + } + + /** + * 新增 + */ + async create(dto: DictItemDto): Promise { + const { typeId, ...rest } = dto + await this.dictItemRepository.insert({ + ...rest, + type: { + id: typeId, + }, + }) + } + + /** + * 更新 + */ + async update(id: number, dto: Partial): Promise { + const { typeId, ...rest } = dto + await this.dictItemRepository.update(id, { + ...rest, + type: { + id: typeId, + }, + }) + } + + /** + * 删除 + */ + async delete(id: number): Promise { + await this.dictItemRepository.delete(id) + } + + /** + * 查询单个 + */ + async findOne(id: number): Promise { + return this.dictItemRepository.findOneBy({ id }) + } + + async isExistKey(dto: DictItemDto): Promise { + const { value, typeId } = dto + const result = await this.dictItemRepository.findOneBy({ value, type: { id: typeId } }) + if (result) + throw new BusinessException(ErrorEnum.DICT_NAME_EXISTS) + } +} diff --git a/src/modules/system/dict-type/dict-type.controller.ts b/src/modules/system/dict-type/dict-type.controller.ts new file mode 100644 index 0000000..652a94b --- /dev/null +++ b/src/modules/system/dict-type/dict-type.controller.ts @@ -0,0 +1,76 @@ +import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { Pagination } from '~/helper/paginate/pagination' +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { DictTypeEntity } from '~/modules/system/dict-type/dict-type.entity' + +import { DictTypeDto, DictTypeQueryDto } from './dict-type.dto' +import { DictTypeService } from './dict-type.service' + +export const permissions = definePermission('system:dict-type', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiTags('System - 字典类型模块') +@ApiSecurityAuth() +@Controller('dict-type') +export class DictTypeController { + constructor(private dictTypeService: DictTypeService) {} + + @Get() + @ApiOperation({ summary: '获取字典类型列表' }) + @ApiResult({ type: [DictTypeEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: DictTypeQueryDto): Promise> { + return this.dictTypeService.page(dto) + } + + @Get('select-options') + @ApiOperation({ summary: '一次性获取所有的字典类型(不分页)' }) + @ApiResult({ type: [DictTypeEntity] }) + @Perm(permissions.LIST) + async getAll(): Promise { + return this.dictTypeService.getAll() + } + + @Post() + @ApiOperation({ summary: '新增字典类型' }) + @Perm(permissions.CREATE) + async create(@Body() dto: DictTypeDto, @AuthUser() user: IAuthUser): Promise { + await this.dictTypeService.isExistKey(dto.name) + dto.createBy = dto.updateBy = user.uid + await this.dictTypeService.create(dto) + } + + @Get(':id') + @ApiOperation({ summary: '查询字典类型信息' }) + @ApiResult({ type: DictTypeEntity }) + @Perm(permissions.READ) + async info(@IdParam() id: number): Promise { + return this.dictTypeService.findOne(id) + } + + @Post(':id') + @ApiOperation({ summary: '更新字典类型' }) + @Perm(permissions.UPDATE) + async update(@IdParam() id: number, @Body() dto: DictTypeDto, @AuthUser() user: IAuthUser): Promise { + dto.updateBy = user.uid + await this.dictTypeService.update(id, dto) + } + + @Delete(':id') + @ApiOperation({ summary: '删除指定的字典类型' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + await this.dictTypeService.delete(id) + } +} diff --git a/src/modules/system/dict-type/dict-type.dto.ts b/src/modules/system/dict-type/dict-type.dto.ts new file mode 100644 index 0000000..c3809b4 --- /dev/null +++ b/src/modules/system/dict-type/dict-type.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { IsInt, IsOptional, IsString, MinLength } from 'class-validator' + +import { PagerDto } from '~/common/dto/pager.dto' + +import { DictTypeEntity } from './dict-type.entity' + +export class DictTypeDto extends PartialType(DictTypeEntity) { + @ApiProperty({ description: '字典类型名称' }) + @IsString() + @MinLength(1) + name: string + + @ApiProperty({ description: '字典类型code' }) + @IsString() + @MinLength(3) + code: string + + @ApiProperty({ description: '状态' }) + @IsOptional() + @IsInt() + status?: number + + @ApiProperty({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string +} + +export class DictTypeQueryDto extends PagerDto { + @ApiProperty({ description: '字典类型名称' }) + @IsString() + @IsOptional() + name: string + + @ApiProperty({ description: '字典类型code' }) + @IsString() + @IsOptional() + code: string +} diff --git a/src/modules/system/dict-type/dict-type.entity.ts b/src/modules/system/dict-type/dict-type.entity.ts new file mode 100644 index 0000000..8d2608f --- /dev/null +++ b/src/modules/system/dict-type/dict-type.entity.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity } from 'typeorm' + +import { CompleteEntity } from '~/common/entity/common.entity' + +@Entity({ name: 'sys_dict_type' }) +export class DictTypeEntity extends CompleteEntity { + @Column({ type: 'varchar', length: 50 }) + @ApiProperty({ description: '字典名称' }) + name: string + + @Column({ type: 'varchar', length: 50, unique: true }) + @ApiProperty({ description: '字典类型' }) + code: string + + @Column({ type: 'tinyint', default: 1 }) + @ApiProperty({ description: ' 状态' }) + status: number + + @Column({ type: 'varchar', nullable: true }) + @ApiProperty({ description: '备注' }) + remark: string +} diff --git a/src/modules/system/dict-type/dict-type.module.ts b/src/modules/system/dict-type/dict-type.module.ts new file mode 100644 index 0000000..5f8f238 --- /dev/null +++ b/src/modules/system/dict-type/dict-type.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { DictTypeController } from './dict-type.controller' +import { DictTypeEntity } from './dict-type.entity' +import { DictTypeService } from './dict-type.service' + +const services = [DictTypeService] + +@Module({ + imports: [TypeOrmModule.forFeature([DictTypeEntity])], + controllers: [DictTypeController], + providers: [...services], + exports: [TypeOrmModule, ...services], +}) +export class DictTypeModule {} diff --git a/src/modules/system/dict-type/dict-type.service.ts b/src/modules/system/dict-type/dict-type.service.ts new file mode 100644 index 0000000..04e6c47 --- /dev/null +++ b/src/modules/system/dict-type/dict-type.service.ts @@ -0,0 +1,84 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' + +import { Like, Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' +import { DictTypeEntity } from '~/modules/system/dict-type/dict-type.entity' + +import { DictTypeDto, DictTypeQueryDto } from './dict-type.dto' + +@Injectable() +export class DictTypeService { + constructor( + @InjectRepository(DictTypeEntity) + private dictTypeRepository: Repository, + ) {} + + /** + * 罗列所有配置 + */ + async page({ + page, + pageSize, + name, + code, + }: DictTypeQueryDto): Promise> { + const queryBuilder = this.dictTypeRepository.createQueryBuilder('dict_type') + .where({ + ...(name && { name: Like(`%${name}%`) }), + ...(code && { code: Like(`%${code}%`) }), + }) + + return paginate(queryBuilder, { page, pageSize }) + } + + /** 一次性获取所有的字典类型 */ + async getAll() { + return this.dictTypeRepository.find() + } + + /** + * 获取参数总数 + */ + async countConfigList(): Promise { + return this.dictTypeRepository.count() + } + + /** + * 新增 + */ + async create(dto: DictTypeDto): Promise { + await this.dictTypeRepository.insert(dto) + } + + /** + * 更新 + */ + async update(id: number, dto: Partial): Promise { + await this.dictTypeRepository.update(id, dto) + } + + /** + * 删除 + */ + async delete(id: number): Promise { + await this.dictTypeRepository.delete(id) + } + + /** + * 查询单个 + */ + async findOne(id: number): Promise { + return this.dictTypeRepository.findOneBy({ id }) + } + + async isExistKey(name: string): Promise { + const result = await this.dictTypeRepository.findOneBy({ name }) + if (result) + throw new BusinessException(ErrorEnum.DICT_NAME_EXISTS) + } +} diff --git a/src/modules/system/log/dto/log.dto.ts b/src/modules/system/log/dto/log.dto.ts new file mode 100644 index 0000000..5cf0ff3 --- /dev/null +++ b/src/modules/system/log/dto/log.dto.ts @@ -0,0 +1,57 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsOptional, IsString } from 'class-validator' + +import { PagerDto } from '~/common/dto/pager.dto' + +export class LoginLogQueryDto extends PagerDto { + @ApiProperty({ description: '用户名' }) + @IsString() + @IsOptional() + username: string + + @ApiProperty({ description: '登录IP' }) + @IsOptional() + @IsString() + ip?: string + + @ApiProperty({ description: '登录地点' }) + @IsOptional() + @IsString() + address?: string + + @ApiProperty({ description: '登录时间' }) + @IsOptional() + time?: string[] +} + +export class TaskLogQueryDto extends PagerDto { + @ApiProperty({ description: '用户名' }) + @IsOptional() + @IsString() + username: string + + @ApiProperty({ description: '登录IP' }) + @IsString() + @IsOptional() + ip?: string + + @ApiProperty({ description: '登录时间' }) + @IsOptional() + time?: string[] +} + +export class CaptchaLogQueryDto extends PagerDto { + @ApiProperty({ description: '用户名' }) + @IsOptional() + @IsString() + username: string + + @ApiProperty({ description: '验证码' }) + @IsString() + @IsOptional() + code?: string + + @ApiProperty({ description: '发送时间' }) + @IsOptional() + time?: string[] +} diff --git a/src/modules/system/log/entities/captcha-log.entity.ts b/src/modules/system/log/entities/captcha-log.entity.ts new file mode 100644 index 0000000..048bec8 --- /dev/null +++ b/src/modules/system/log/entities/captcha-log.entity.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +@Entity({ name: 'sys_captcha_log' }) +export class CaptchaLogEntity extends CommonEntity { + @Column({ name: 'user_id', nullable: true }) + @ApiProperty({ description: '用户ID' }) + userId: number + + @Column({ nullable: true }) + @ApiProperty({ description: '账号' }) + account: string + + @Column({ nullable: true }) + @ApiProperty({ description: '验证码' }) + code: string + + @Column({ nullable: true }) + @ApiProperty({ description: '验证码提供方' }) + provider: 'sms' | 'email' +} diff --git a/src/modules/system/log/entities/index.ts b/src/modules/system/log/entities/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/system/log/entities/login-log.entity.ts b/src/modules/system/log/entities/login-log.entity.ts new file mode 100644 index 0000000..7a56568 --- /dev/null +++ b/src/modules/system/log/entities/login-log.entity.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +import { UserEntity } from '../../../user/user.entity' + +@Entity({ name: 'sys_login_log' }) +export class LoginLogEntity extends CommonEntity { + @Column({ nullable: true }) + @ApiProperty({ description: 'IP' }) + ip: string + + @Column({ nullable: true }) + @ApiProperty({ description: '地址' }) + address: string + + @Column({ nullable: true }) + @ApiProperty({ description: '登录方式' }) + provider: string + + @Column({ length: 500, nullable: true }) + @ApiProperty({ description: '浏览器ua' }) + ua: string + + @ManyToOne(() => UserEntity, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'user_id' }) + user: Relation +} diff --git a/src/modules/system/log/entities/task-log.entity.ts b/src/modules/system/log/entities/task-log.entity.ts new file mode 100644 index 0000000..1721d68 --- /dev/null +++ b/src/modules/system/log/entities/task-log.entity.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +import { TaskEntity } from '../../task/task.entity' + +@Entity({ name: 'sys_task_log' }) +export class TaskLogEntity extends CommonEntity { + @Column({ type: 'tinyint', default: 0 }) + @ApiProperty({ description: '任务状态:0失败,1成功' }) + status: number + + @Column({ type: 'text', nullable: true }) + @ApiProperty({ description: '任务日志信息' }) + detail: string + + @Column({ type: 'int', nullable: true, name: 'consume_time', default: 0 }) + @ApiProperty({ description: '任务耗时' }) + consumeTime: number + + @ManyToOne(() => TaskEntity) + @JoinColumn({ name: 'task_id' }) + task: Relation +} diff --git a/src/modules/system/log/log.controller.ts b/src/modules/system/log/log.controller.ts new file mode 100644 index 0000000..489f3cb --- /dev/null +++ b/src/modules/system/log/log.controller.ts @@ -0,0 +1,64 @@ +import { Controller, Get, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { Pagination } from '~/helper/paginate/pagination' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' + +import { + CaptchaLogQueryDto, + LoginLogQueryDto, + TaskLogQueryDto, +} from './dto/log.dto' +import { CaptchaLogEntity } from './entities/captcha-log.entity' +import { TaskLogEntity } from './entities/task-log.entity' +import { LoginLogInfo } from './models/log.model' +import { CaptchaLogService } from './services/captcha-log.service' +import { LoginLogService } from './services/login-log.service' +import { TaskLogService } from './services/task-log.service' + +export const permissions = definePermission('system:log', { + TaskList: 'task:list', + LogList: 'login:list', + CaptchaList: 'captcha:list', +} as const) + +@ApiSecurityAuth() +@ApiTags('System - 日志模块') +@Controller('log') +export class LogController { + constructor( + private loginLogService: LoginLogService, + private taskService: TaskLogService, + private captchaLogService: CaptchaLogService, + ) {} + + @Get('login/list') + @ApiOperation({ summary: '查询登录日志列表' }) + @ApiResult({ type: [LoginLogInfo], isPage: true }) + @Perm(permissions.TaskList) + async loginLogPage( + @Query() dto: LoginLogQueryDto, + ): Promise> { + return this.loginLogService.list(dto) + } + + @Get('task/list') + @ApiOperation({ summary: '查询任务日志列表' }) + @ApiResult({ type: [TaskLogEntity], isPage: true }) + @Perm(permissions.LogList) + async taskList(@Query() dto: TaskLogQueryDto) { + return this.taskService.list(dto) + } + + @Get('captcha/list') + @ApiOperation({ summary: '查询验证码日志列表' }) + @ApiResult({ type: [CaptchaLogEntity], isPage: true }) + @Perm(permissions.CaptchaList) + async captchaList( + @Query() dto: CaptchaLogQueryDto, + ): Promise> { + return this.captchaLogService.paginate(dto) + } +} diff --git a/src/modules/system/log/log.module.ts b/src/modules/system/log/log.module.ts new file mode 100644 index 0000000..27f9f04 --- /dev/null +++ b/src/modules/system/log/log.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { UserModule } from '../../user/user.module' + +import { CaptchaLogEntity } from './entities/captcha-log.entity' +import { LoginLogEntity } from './entities/login-log.entity' +import { TaskLogEntity } from './entities/task-log.entity' +import { LogController } from './log.controller' +import { CaptchaLogService } from './services/captcha-log.service' +import { LoginLogService } from './services/login-log.service' +import { TaskLogService } from './services/task-log.service' + +const providers = [LoginLogService, TaskLogService, CaptchaLogService] + +@Module({ + imports: [ + TypeOrmModule.forFeature([LoginLogEntity, CaptchaLogEntity, TaskLogEntity]), + UserModule, + ], + controllers: [LogController], + providers: [...providers], + exports: [TypeOrmModule, ...providers], +}) +export class LogModule {} diff --git a/src/modules/system/log/models/log.model.ts b/src/modules/system/log/models/log.model.ts new file mode 100644 index 0000000..f128be0 --- /dev/null +++ b/src/modules/system/log/models/log.model.ts @@ -0,0 +1,47 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class LoginLogInfo { + @ApiProperty({ description: '日志编号' }) + id: number + + @ApiProperty({ description: '登录ip', example: '1.1.1.1' }) + ip: string + + @ApiProperty({ description: '登录地址' }) + address: string + + @ApiProperty({ description: '系统', example: 'Windows 10' }) + os: string + + @ApiProperty({ description: '浏览器', example: 'Chrome' }) + browser: string + + @ApiProperty({ description: '登录用户名', example: 'admin' }) + username: string + + @ApiProperty({ description: '登录时间', example: '2023-12-22 16:46:20.333843' }) + time: string +} + +export class TaskLogInfo { + @ApiProperty({ description: '日志编号' }) + id: number + + @ApiProperty({ description: '任务编号' }) + taskId: number + + @ApiProperty({ description: '任务名称' }) + name: string + + @ApiProperty({ description: '创建时间' }) + createdAt: string + + @ApiProperty({ description: '耗时' }) + consumeTime: number + + @ApiProperty({ description: '执行信息' }) + detail: string + + @ApiProperty({ description: '任务执行状态' }) + status: number +} diff --git a/src/modules/system/log/services/captcha-log.service.ts b/src/modules/system/log/services/captcha-log.service.ts new file mode 100644 index 0000000..5bc01e1 --- /dev/null +++ b/src/modules/system/log/services/captcha-log.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' + +import { LessThan, Repository } from 'typeorm' + +import { paginate } from '~/helper/paginate' + +import { CaptchaLogQueryDto } from '../dto/log.dto' +import { CaptchaLogEntity } from '../entities/captcha-log.entity' + +@Injectable() +export class CaptchaLogService { + constructor( + @InjectRepository(CaptchaLogEntity) + private captchaLogRepository: Repository, + ) {} + + async create( + account: string, + code: string, + provider: 'sms' | 'email', + uid?: number, + ): Promise { + await this.captchaLogRepository.save({ + account, + code, + provider, + userId: uid, + }) + } + + async paginate({ page, pageSize }: CaptchaLogQueryDto) { + const queryBuilder = await this.captchaLogRepository + .createQueryBuilder('captcha_log') + .orderBy('captcha_log.id', 'DESC') + + return paginate(queryBuilder, { + page, + pageSize, + }) + } + + async clearLog(): Promise { + await this.captchaLogRepository.clear() + } + + async clearLogBeforeTime(time: Date): Promise { + await this.captchaLogRepository.delete({ createdAt: LessThan(time) }) + } +} diff --git a/src/modules/system/log/services/login-log.service.ts b/src/modules/system/log/services/login-log.service.ts new file mode 100644 index 0000000..b3420de --- /dev/null +++ b/src/modules/system/log/services/login-log.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' + +import { Between, LessThan, Like, Repository } from 'typeorm' + +import UAParser from 'ua-parser-js' + +import { paginateRaw } from '~/helper/paginate' + +import { getIpAddress } from '~/utils/ip.util' + +import { LoginLogQueryDto } from '../dto/log.dto' +import { LoginLogEntity } from '../entities/login-log.entity' +import { LoginLogInfo } from '../models/log.model' + +async function parseLoginLog(e: any, parser: UAParser): Promise { + const uaResult = parser.setUA(e.login_log_ua).getResult() + + return { + id: e.login_log_id, + ip: e.login_log_ip, + address: e.login_log_address, + os: `${`${uaResult.os.name ?? ''} `}${uaResult.os.version}`, + browser: `${`${uaResult.browser.name ?? ''} `}${uaResult.browser.version}`, + username: e.user_username, + time: e.login_log_created_at, + } +} + +@Injectable() +export class LoginLogService { + constructor( + @InjectRepository(LoginLogEntity) + private loginLogRepository: Repository, + + ) {} + + async create(uid: number, ip: string, ua: string): Promise { + try { + const address = await getIpAddress(ip) + + await this.loginLogRepository.save({ + ip, + ua, + address, + user: { id: uid }, + }) + } + catch (e) { + console.error(e) + } + } + + async list({ + page, + pageSize, + username, + ip, + address, + time, + }: LoginLogQueryDto) { + const queryBuilder = await this.loginLogRepository + .createQueryBuilder('login_log') + .innerJoinAndSelect('login_log.user', 'user') + .where({ + ...(ip && { ip: Like(`%${ip}%`) }), + ...(address && { address: Like(`%${address}%`) }), + ...(time && { createdAt: Between(time[0], time[1]) }), + ...(username && { + user: { + username: Like(`%${username}%`), + }, + }), + }) + .orderBy('login_log.created_at', 'DESC') + + const { items, ...rest } = await paginateRaw(queryBuilder, { + page, + pageSize, + }) + + const parser = new UAParser() + const loginLogInfos = await Promise.all( + items.map(item => parseLoginLog(item, parser)), + ) + + return { + items: loginLogInfos, + ...rest, + } + } + + async clearLog(): Promise { + await this.loginLogRepository.clear() + } + + async clearLogBeforeTime(time: Date): Promise { + await this.loginLogRepository.delete({ createdAt: LessThan(time) }) + } +} diff --git a/src/modules/system/log/services/task-log.service.ts b/src/modules/system/log/services/task-log.service.ts new file mode 100644 index 0000000..b57410f --- /dev/null +++ b/src/modules/system/log/services/task-log.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' + +import { LessThan, Repository } from 'typeorm' + +import { paginate } from '~/helper/paginate' + +import { TaskLogQueryDto } from '../dto/log.dto' +import { TaskLogEntity } from '../entities/task-log.entity' + +@Injectable() +export class TaskLogService { + constructor( + @InjectRepository(TaskLogEntity) + private taskLogRepository: Repository, + ) {} + + async create( + tid: number, + status: number, + time?: number, + err?: string, + ): Promise { + const result = await this.taskLogRepository.save({ + status, + detail: err, + time, + task: { id: tid }, + }) + return result.id + } + + async list({ page, pageSize }: TaskLogQueryDto) { + const queryBuilder = await this.taskLogRepository + .createQueryBuilder('task_log') + .leftJoinAndSelect('task_log.task', 'task') + .orderBy('task_log.id', 'DESC') + + return paginate(queryBuilder, { + page, + pageSize, + }) + } + + async clearLog(): Promise { + await this.taskLogRepository.clear() + } + + async clearLogBeforeTime(time: Date): Promise { + await this.taskLogRepository.delete({ createdAt: LessThan(time) }) + } +} diff --git a/src/modules/system/menu/menu.controller.ts b/src/modules/system/menu/menu.controller.ts new file mode 100644 index 0000000..2de0370 --- /dev/null +++ b/src/modules/system/menu/menu.controller.ts @@ -0,0 +1,104 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + Post, + Put, + Query, +} from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' +import { flattenDeep } from 'lodash' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { Perm, definePermission, getDefinePermissions } from '~/modules/auth/decorators/permission.decorator' + +import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto' +import { MenuItemInfo } from './menu.model' +import { MenuService } from './menu.service' + +export const permissions = definePermission('system:menu', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiTags('System - 菜单权限模块') +@ApiSecurityAuth() +@Controller('menus') +export class MenuController { + constructor(private menuService: MenuService) {} + + @Get() + @ApiOperation({ summary: '获取所有菜单列表' }) + @ApiResult({ type: [MenuItemInfo] }) + @Perm(permissions.LIST) + async list(@Query() dto: MenuQueryDto) { + return this.menuService.list(dto) + } + + @Get(':id') + @ApiOperation({ summary: '获取菜单或权限信息' }) + @Perm(permissions.READ) + async info(@IdParam() id: number) { + return this.menuService.getMenuItemAndParentInfo(id) + } + + @Post() + @ApiOperation({ summary: '新增菜单或权限' }) + @Perm(permissions.CREATE) + async create(@Body() dto: MenuDto): Promise { + // check + await this.menuService.check(dto) + if (!dto.parentId) + dto.parentId = null + + await this.menuService.create(dto) + if (dto.type === 2) { + // 如果是权限发生更改,则刷新所有在线用户的权限 + await this.menuService.refreshOnlineUserPerms() + } + } + + @Put(':id') + @ApiOperation({ summary: '更新菜单或权限' }) + @Perm(permissions.UPDATE) + async update( + @IdParam() id: number, @Body() dto: MenuUpdateDto): Promise { + // check + await this.menuService.check(dto) + if (dto.parentId === -1 || !dto.parentId) + dto.parentId = null + + await this.menuService.update(id, dto) + if (dto.type === 2) { + // 如果是权限发生更改,则刷新所有在线用户的权限 + await this.menuService.refreshOnlineUserPerms() + } + } + + @Delete(':id') + @ApiOperation({ summary: '删除菜单或权限' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + if (await this.menuService.checkRoleByMenuId(id)) + throw new BadRequestException('该菜单存在关联角色,无法删除') + + // 如果有子目录,一并删除 + const childMenus = await this.menuService.findChildMenus(id) + await this.menuService.deleteMenuItem(flattenDeep([id, childMenus])) + // 刷新在线用户权限 + await this.menuService.refreshOnlineUserPerms() + } + + @Get('permissions') + @ApiOperation({ summary: '获取后端定义的所有权限集' }) + async getPermissions(): Promise { + return getDefinePermissions() + } +} diff --git a/src/modules/system/menu/menu.dto.ts b/src/modules/system/menu/menu.dto.ts new file mode 100644 index 0000000..5a24aab --- /dev/null +++ b/src/modules/system/menu/menu.dto.ts @@ -0,0 +1,88 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { + IsBoolean, + IsIn, + IsInt, + IsOptional, + IsString, + Min, + MinLength, + ValidateIf, +} from 'class-validator' + +export class MenuDto { + @ApiProperty({ description: '菜单类型' }) + @IsIn([0, 1, 2]) + type: number + + @ApiProperty({ description: '父级菜单' }) + @IsOptional() + parentId: number + + @ApiProperty({ description: '菜单或权限名称' }) + @IsString() + @MinLength(2) + name: string + + @ApiProperty({ description: '排序' }) + @IsInt() + @Min(0) + orderNo: number + + @ApiProperty({ description: '前端路由地址' }) + // @Matches(/^[/]$/) + @ValidateIf(o => o.type !== 2) + path: string + + @ApiProperty({ description: '是否外链', default: false }) + @ValidateIf(o => o.type !== 2) + @IsBoolean() + isExt: boolean + + @ApiProperty({ description: '外链打开方式', default: 1 }) + @ValidateIf((o: MenuDto) => o.isExt) + @IsIn([1, 2]) + extOpenMode: number + + @ApiProperty({ description: '菜单是否显示', default: 1 }) + @ValidateIf((o: MenuDto) => o.type !== 2) + @IsIn([0, 1]) + show: number + + @ApiProperty({ description: '设置当前路由高亮的菜单项,一般用于详情页' }) + @ValidateIf((o: MenuDto) => o.type !== 2 && o.show === 0) + @IsString() + @IsOptional() + activeMenu?: string + + @ApiProperty({ description: '是否开启页面缓存', default: 1 }) + @ValidateIf((o: MenuDto) => o.type === 1) + @IsIn([0, 1]) + keepAlive: number + + @ApiProperty({ description: '状态', default: 1 }) + @IsIn([0, 1]) + status: number + + @ApiProperty({ description: '菜单图标' }) + @IsOptional() + @ValidateIf((o: MenuDto) => o.type !== 2) + @IsString() + icon?: string + + @ApiProperty({ description: '对应权限' }) + @ValidateIf((o: MenuDto) => o.type === 2) + @IsString() + @IsOptional() + permission: string + + @ApiProperty({ description: '菜单路由路径或外链' }) + @ValidateIf((o: MenuDto) => o.type !== 2) + @IsString() + @IsOptional() + component?: string +} + +export class MenuUpdateDto extends PartialType(MenuDto) {} + +export class MenuQueryDto extends PartialType(MenuDto) {} diff --git a/src/modules/system/menu/menu.entity.ts b/src/modules/system/menu/menu.entity.ts new file mode 100644 index 0000000..950d057 --- /dev/null +++ b/src/modules/system/menu/menu.entity.ts @@ -0,0 +1,55 @@ +import { Column, Entity, ManyToMany, Relation } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +import { RoleEntity } from '../role/role.entity' + +@Entity({ name: 'sys_menu' }) +export class MenuEntity extends CommonEntity { + @Column({ name: 'parent_id', nullable: true }) + parentId: number + + @Column() + name: string + + @Column({ nullable: true }) + path: string + + @Column({ nullable: true }) + permission: string + + @Column({ type: 'tinyint', default: 0 }) + type: number + + @Column({ nullable: true, default: '' }) + icon: string + + @Column({ name: 'order_no', type: 'int', nullable: true, default: 0 }) + orderNo: number + + @Column({ name: 'component', nullable: true }) + component: string + + @Column({ name: 'is_ext', type: 'boolean', default: false }) + isExt: boolean + + @Column({ name: 'ext_open_mode', type: 'tinyint', default: 1 }) + extOpenMode: number + + @Column({ name: 'keep_alive', type: 'tinyint', default: 1 }) + keepAlive: number + + @Column({ type: 'tinyint', default: 1 }) + show: number + + @Column({ name: 'active_menu', nullable: true }) + activeMenu: string + + @Column({ type: 'tinyint', default: 1 }) + status: number + + @ManyToMany(() => RoleEntity, role => role.menus, { + onDelete: 'CASCADE', + }) + roles: Relation +} diff --git a/src/modules/system/menu/menu.model.ts b/src/modules/system/menu/menu.model.ts new file mode 100644 index 0000000..36ca084 --- /dev/null +++ b/src/modules/system/menu/menu.model.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger' + +import { MenuEntity } from './menu.entity' + +export class MenuItemInfo extends MenuEntity { + @ApiProperty({ type: [MenuItemInfo] }) + children: MenuItemInfo[] +} diff --git a/src/modules/system/menu/menu.module.ts b/src/modules/system/menu/menu.module.ts new file mode 100644 index 0000000..cdb6ead --- /dev/null +++ b/src/modules/system/menu/menu.module.ts @@ -0,0 +1,24 @@ +import { Module, forwardRef } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { SseService } from '~/modules/sse/sse.service' + +import { RoleModule } from '../role/role.module' + +import { MenuController } from './menu.controller' +import { MenuEntity } from './menu.entity' +import { MenuService } from './menu.service' + +const providers = [MenuService, SseService] + +@Module({ + imports: [ + TypeOrmModule.forFeature([MenuEntity]), + + forwardRef(() => RoleModule), + ], + controllers: [MenuController], + providers: [...providers], + exports: [TypeOrmModule, ...providers], +}) +export class MenuModule {} diff --git a/src/modules/system/menu/menu.service.ts b/src/modules/system/menu/menu.service.ts new file mode 100644 index 0000000..aa288cd --- /dev/null +++ b/src/modules/system/menu/menu.service.ts @@ -0,0 +1,259 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import Redis from 'ioredis' +import { concat, isEmpty, uniq } from 'lodash' + +import { In, IsNull, Like, Not, Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { RedisKeys } from '~/constants/cache.constant' +import { ErrorEnum } from '~/constants/error-code.constant' +import { genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey' +import { SseService } from '~/modules/sse/sse.service' +import { MenuEntity } from '~/modules/system/menu/menu.entity' + +import { deleteEmptyChildren, generatorMenu, generatorRouters } from '~/utils' + +import { RoleService } from '../role/role.service' + +import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto' + +@Injectable() +export class MenuService { + constructor( + @InjectRedis() private redis: Redis, + @InjectRepository(MenuEntity) + private menuRepository: Repository, + private roleService: RoleService, + private sseService: SseService, + ) {} + + /** + * 获取所有菜单以及权限 + */ + async list({ + name, + path, + permission, + component, + status, + }: MenuQueryDto): Promise { + const menus = await this.menuRepository.find({ + where: { + ...(name && { name: Like(`%${name}%`) }), + ...(path && { path: Like(`%${path}%`) }), + ...(permission && { permission: Like(`%${permission}%`) }), + ...(component && { component: Like(`%${component}%`) }), + ...(status && { status }), + }, + order: { orderNo: 'ASC' }, + }) + const menuList = generatorMenu(menus) + + if (!isEmpty(menuList)) { + deleteEmptyChildren(menuList) + return menuList + } + // 如果生产树形结构为空,则返回原始菜单列表 + return menus + } + + async create(menu: MenuDto): Promise { + const result = await this.menuRepository.save(menu) + this.sseService.noticeClientToUpdateMenusByMenuIds([result.id]) + } + + async update(id: number, menu: MenuUpdateDto): Promise { + await this.menuRepository.update(id, menu) + this.sseService.noticeClientToUpdateMenusByMenuIds([id]) + } + + /** + * 根据角色获取所有菜单 + */ + async getMenus(uid: number): Promise { + const roleIds = await this.roleService.getRoleIdsByUser(uid) + let menus: MenuEntity[] = [] + + if (isEmpty(roleIds)) + return generatorRouters([]) + + if (this.roleService.hasAdminRole(roleIds)) { + menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } }) + } + else { + menus = await this.menuRepository + .createQueryBuilder('menu') + .innerJoinAndSelect('menu.roles', 'role') + .andWhere('role.id IN (:...roleIds)', { roleIds }) + .orderBy('menu.order_no', 'ASC') + .getMany() + } + + const menuList = generatorRouters(menus) + return menuList + } + + /** + * 检查菜单创建规则是否符合 + */ + async check(dto: Partial): Promise { + if (dto.type === 2 && !dto.parentId) { + // 无法直接创建权限,必须有parent + throw new BusinessException(ErrorEnum.PERMISSION_REQUIRES_PARENT) + } + if (dto.type === 1 && dto.parentId) { + const parent = await this.getMenuItemInfo(dto.parentId) + if (isEmpty(parent)) + throw new BusinessException(ErrorEnum.PARENT_MENU_NOT_FOUND) + + if (parent && parent.type === 1) { + // 当前新增为菜单但父节点也为菜单时为非法操作 + throw new BusinessException( + ErrorEnum.ILLEGAL_OPERATION_DIRECTORY_PARENT, + ) + } + } + } + + /** + * 查找当前菜单下的子菜单,目录以及菜单 + */ + async findChildMenus(mid: number): Promise { + const allMenus: any = [] + const menus = await this.menuRepository.findBy({ parentId: mid }) + // if (_.isEmpty(menus)) { + // return allMenus; + // } + // const childMenus: any = []; + for (const menu of menus) { + if (menu.type !== 2) { + // 子目录下是菜单或目录,继续往下级查找 + const c = await this.findChildMenus(menu.id) + allMenus.push(c) + } + allMenus.push(menu.id) + } + return allMenus + } + + /** + * 获取某个菜单的信息 + * @param mid menu id + */ + async getMenuItemInfo(mid: number): Promise { + const menu = await this.menuRepository.findOneBy({ id: mid }) + return menu + } + + /** + * 获取某个菜单以及关联的父菜单的信息 + */ + async getMenuItemAndParentInfo(mid: number) { + const menu = await this.menuRepository.findOneBy({ id: mid }) + let parentMenu: MenuEntity | undefined + if (menu && menu.parentId) + parentMenu = await this.menuRepository.findOneBy({ id: menu.parentId }) + + return { menu, parentMenu } + } + + /** + * 查找节点路由是否存在 + */ + async findRouterExist(path: string): Promise { + const menus = await this.menuRepository.findOneBy({ path }) + return !isEmpty(menus) + } + + /** + * 获取当前用户的所有权限 + */ + async getPermissions(uid: number): Promise { + const roleIds = await this.roleService.getRoleIdsByUser(uid) + let permission: any[] = [] + let result: any = null + if (this.roleService.hasAdminRole(roleIds)) { + result = await this.menuRepository.findBy({ + permission: Not(IsNull()), + type: In([1, 2]), + }) + } + else { + if (isEmpty(roleIds)) + return permission + + result = await this.menuRepository + .createQueryBuilder('menu') + .innerJoinAndSelect('menu.roles', 'role') + .andWhere('role.id IN (:...roleIds)', { roleIds }) + .andWhere('menu.type IN (1,2)') + .andWhere('menu.permission IS NOT NULL') + .getMany() + } + if (!isEmpty(result)) { + result.forEach((e) => { + if (e.permission) + permission = concat(permission, e.permission.split(',')) + }) + permission = uniq(permission) + } + return permission + } + + /** + * 删除多项菜单 + */ + async deleteMenuItem(mids: number[]): Promise { + await this.menuRepository.delete(mids) + } + + /** + * 刷新指定用户ID的权限 + */ + async refreshPerms(uid: number): Promise { + const perms = await this.getPermissions(uid) + const online = await this.redis.get(genAuthTokenKey(uid)) + if (online) { + // 判断是否在线 + await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms)) + console.log('refreshPerms') + + this.sseService.noticeClientToUpdateMenusByUserIds([uid]) + } + } + + /** + * 刷新所有在线用户的权限 + */ + async refreshOnlineUserPerms(): Promise { + const onlineUserIds: string[] = await this.redis.keys(genAuthTokenKey('*')) + if (onlineUserIds && onlineUserIds.length > 0) { + const promiseArr = onlineUserIds + .map(i => Number.parseInt(i.split(RedisKeys.AUTH_TOKEN_PREFIX)[1])) + .filter(i => i) + .map(async (uid) => { + const perms = await this.getPermissions(uid) + await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms)) + return uid + }) + const uids = await Promise.all(promiseArr) + console.log('refreshOnlineUserPerms') + this.sseService.noticeClientToUpdateMenusByUserIds(uids) + } + } + + /** + * 根据菜单ID查找是否有关联角色 + */ + async checkRoleByMenuId(id: number): Promise { + return !!(await this.menuRepository.findOne({ + where: { + roles: { + id, + }, + }, + })) + } +} diff --git a/src/modules/system/online/online.controller.ts b/src/modules/system/online/online.controller.ts new file mode 100644 index 0000000..c484037 --- /dev/null +++ b/src/modules/system/online/online.controller.ts @@ -0,0 +1,46 @@ +import { Body, Controller, Get, Post } from '@nestjs/common' +import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' + +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' + +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' + +import { KickDto } from './online.dto' +import { OnlineUserInfo } from './online.model' +import { OnlineService } from './online.service' + +export const permissions = definePermission('system:online', { + LIST: 'list', + KICK: 'kick', +} as const) + +@ApiTags('System - 在线用户模块') +@ApiSecurityAuth() +@ApiExtraModels(OnlineUserInfo) +@Controller('online') +export class OnlineController { + constructor(private onlineService: OnlineService) {} + + @Get('list') + @ApiOperation({ summary: '查询当前在线用户' }) + @ApiResult({ type: [OnlineUserInfo] }) + @Perm(permissions.LIST) + async list(@AuthUser() user: IAuthUser): Promise { + return this.onlineService.listOnlineUser(user.uid) + } + + @Post('kick') + @ApiOperation({ summary: '下线指定在线用户' }) + @Perm(permissions.KICK) + async kick(@Body() dto: KickDto, @AuthUser() user: IAuthUser): Promise { + if (dto.id === user.uid) + throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER) + + await this.onlineService.kickUser(dto.id, user.uid) + } +} diff --git a/src/modules/system/online/online.dto.ts b/src/modules/system/online/online.dto.ts new file mode 100644 index 0000000..fbe8f8b --- /dev/null +++ b/src/modules/system/online/online.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsInt } from 'class-validator' + +export class KickDto { + @ApiProperty({ description: '需要下线的角色ID' }) + @IsInt() + id: number +} diff --git a/src/modules/system/online/online.model.ts b/src/modules/system/online/online.model.ts new file mode 100644 index 0000000..a08f812 --- /dev/null +++ b/src/modules/system/online/online.model.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class OnlineUserInfo { + @ApiProperty({ description: '最近的一条登录日志ID' }) + id: number + + @ApiProperty({ description: '登录IP' }) + ip: string + + @ApiProperty({ description: '登录地点' }) + address: string + + @ApiProperty({ description: '用户名' }) + username: string + + @ApiProperty({ description: '是否当前' }) + isCurrent: boolean + + @ApiProperty({ description: '系统' }) + os: string + + @ApiProperty({ description: '浏览器' }) + browser: string + + @ApiProperty({ description: '是否禁用' }) + disable: boolean +} diff --git a/src/modules/system/online/online.module.ts b/src/modules/system/online/online.module.ts new file mode 100644 index 0000000..4de292a --- /dev/null +++ b/src/modules/system/online/online.module.ts @@ -0,0 +1,27 @@ +import { Module, forwardRef } from '@nestjs/common' + +import { AuthModule } from '~/modules/auth/auth.module' +import { SocketModule } from '~/socket/socket.module' + +import { UserModule } from '../../user/user.module' +import { RoleModule } from '../role/role.module' +import { SystemModule } from '../system.module' + +import { OnlineController } from './online.controller' +import { OnlineService } from './online.service' + +const providers = [OnlineService] + +@Module({ + imports: [ + forwardRef(() => SystemModule), + forwardRef(() => SocketModule), + AuthModule, + UserModule, + RoleModule, + ], + controllers: [OnlineController], + providers, + exports: [...providers], +}) +export class OnlineModule {} diff --git a/src/modules/system/online/online.service.ts b/src/modules/system/online/online.service.ts new file mode 100644 index 0000000..31ad4c0 --- /dev/null +++ b/src/modules/system/online/online.service.ts @@ -0,0 +1,134 @@ +import { Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { InjectEntityManager } from '@nestjs/typeorm' + +import { RemoteSocket } from 'socket.io' +import { EntityManager } from 'typeorm' + +import { UAParser } from 'ua-parser-js' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' + +import { BusinessEvents } from '~/socket/business-event.constant' +import { AdminEventsGateway } from '~/socket/events/admin.gateway' + +import { UserService } from '../../user/user.service' + +import { OnlineUserInfo } from './online.model' + +@Injectable() +export class OnlineService { + constructor( + @InjectEntityManager() private readonly entityManager: EntityManager, + private readonly userService: UserService, + private readonly adminEventsGateWay: AdminEventsGateway, + private readonly jwtService: JwtService, + ) {} + + /** + * 罗列在线用户列表 + */ + async listOnlineUser(currentUid: number): Promise { + const onlineSockets = await this.getOnlineSockets() + if (!onlineSockets || onlineSockets.length <= 0) + return [] + + const onlineIds = onlineSockets.map((socket) => { + const token = socket.handshake.query?.token as string + return this.jwtService.verify(token).uid + }) + return this.findLastLoginInfoList(onlineIds, currentUid) + } + + /** + * 下线当前用户 + */ + async kickUser(uid: number, currentUid: number): Promise { + const rootUserId = await this.userService.findRootUserId() + const currentUserInfo = await this.userService.getAccountInfo(currentUid) + if (uid === rootUserId) + throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER) + + // reset redis keys + await this.userService.forbidden(uid) + // socket emit + const socket = await this.findSocketIdByUid(uid) + if (socket) { + // socket emit event + this.adminEventsGateWay.server + .to(socket.id) + .emit(BusinessEvents.USER_KICK, { operater: currentUserInfo.username }) + // close socket + socket.disconnect() + } + } + + /** + * 根据用户id列表查找最近登录信息和用户信息 + */ + async findLastLoginInfoList( + ids: number[], + currentUid: number, + ): Promise { + const rootUserId = await this.userService.findRootUserId() + const result = await this.entityManager.query( + ` + SELECT sys_login_log.created_at, sys_login_log.ip, sys_login_log.address, sys_login_log.ua, sys_user.id, sys_user.username, sys_user.nick_name + FROM sys_login_log + INNER JOIN sys_user ON sys_login_log.user_id = sys_user.id + WHERE sys_login_log.created_at IN (SELECT MAX(created_at) as createdAt FROM sys_login_log GROUP BY user_id) + AND sys_user.id IN (?) + `, + [ids], + ) + if (result) { + const parser = new UAParser() + return result.map((e) => { + const u = parser.setUA(e.ua).getResult() + return { + id: e.id, + ip: e.ip, + address: e.address, + username: `${e.nick_name}(${e.username})`, + isCurrent: currentUid === e.id, + time: e.created_at, + os: `${u.os.name} ${u.os.version}`, + browser: `${u.browser.name} ${u.browser.version}`, + disable: currentUid === e.id || e.id === rootUserId, + } + }) + } + return [] + } + + /** + * 根据uid查找socketid + */ + async findSocketIdByUid(uid: number): Promise> { + const onlineSockets = await this.getOnlineSockets() + const socket = onlineSockets.find((socket) => { + const token = socket.handshake.query?.token as string + const tokenUid = this.jwtService.verify(token).uid + return tokenUid === uid + }) + return socket + } + + async filterSocketIdByUidArr( + uids: number[], + ): Promise[]> { + const onlineSockets = await this.getOnlineSockets() + const sockets = onlineSockets.filter((socket) => { + const token = socket.handshake.query?.token as string + const tokenUid = this.jwtService.verify(token).uid + return uids.includes(tokenUid) + }) + return sockets + } + + async getOnlineSockets() { + const onlineSockets = await this.adminEventsGateWay.server.fetchSockets() + return onlineSockets + } +} diff --git a/src/modules/system/param-config/param-config.controller.ts b/src/modules/system/param-config/param-config.controller.ts new file mode 100644 index 0000000..b96ab9d --- /dev/null +++ b/src/modules/system/param-config/param-config.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { Pagination } from '~/helper/paginate/pagination' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity' + +import { ParamConfigDto, ParamConfigQueryDto } from './param-config.dto' +import { ParamConfigService } from './param-config.service' + +export const permissions = definePermission('system:param-config', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiTags('System - 参数配置模块') +@ApiSecurityAuth() +@Controller('param-config') +export class ParamConfigController { + constructor(private paramConfigService: ParamConfigService) {} + + @Get() + @ApiOperation({ summary: '获取参数配置列表' }) + @ApiResult({ type: [ParamConfigEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: ParamConfigQueryDto): Promise> { + return this.paramConfigService.page(dto) + } + + @Post() + @ApiOperation({ summary: '新增参数配置' }) + @Perm(permissions.CREATE) + async create(@Body() dto: ParamConfigDto): Promise { + await this.paramConfigService.isExistKey(dto.key) + await this.paramConfigService.create(dto) + } + + @Get(':id') + @ApiOperation({ summary: '查询参数配置信息' }) + @ApiResult({ type: ParamConfigEntity }) + @Perm(permissions.READ) + async info(@IdParam() id: number): Promise { + return this.paramConfigService.findOne(id) + } + + @Post(':id') + @ApiOperation({ summary: '更新参数配置' }) + @Perm(permissions.UPDATE) + async update(@IdParam() id: number, @Body() dto: ParamConfigDto): Promise { + await this.paramConfigService.update(id, dto) + } + + @Delete(':id') + @ApiOperation({ summary: '删除指定的参数配置' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + await this.paramConfigService.delete(id) + } +} diff --git a/src/modules/system/param-config/param-config.dto.ts b/src/modules/system/param-config/param-config.dto.ts new file mode 100644 index 0000000..5b720ba --- /dev/null +++ b/src/modules/system/param-config/param-config.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsOptional, IsString, MinLength } from 'class-validator' + +import { PagerDto } from '~/common/dto/pager.dto' + +export class ParamConfigDto { + @ApiProperty({ description: '参数名称' }) + @IsString() + name: string + + @ApiProperty({ description: '参数键名' }) + @IsString() + @MinLength(3) + key: string + + @ApiProperty({ description: '参数值' }) + @IsString() + value: string + + @ApiProperty({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string +} + +export class ParamConfigQueryDto extends PagerDto { + @ApiProperty({ description: '参数名称' }) + @IsString() + @IsOptional() + name: string +} diff --git a/src/modules/system/param-config/param-config.entity.ts b/src/modules/system/param-config/param-config.entity.ts new file mode 100644 index 0000000..2b5c4e0 --- /dev/null +++ b/src/modules/system/param-config/param-config.entity.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +@Entity({ name: 'sys_config' }) +export class ParamConfigEntity extends CommonEntity { + @Column({ type: 'varchar', length: 50 }) + @ApiProperty({ description: '配置名' }) + name: string + + @Column({ type: 'varchar', length: 50, unique: true }) + @ApiProperty({ description: '配置键名' }) + key: string + + @Column({ type: 'varchar', nullable: true }) + @ApiProperty({ description: '配置值' }) + value: string + + @Column({ type: 'varchar', nullable: true }) + @ApiProperty({ description: '配置描述' }) + remark: string +} diff --git a/src/modules/system/param-config/param-config.module.ts b/src/modules/system/param-config/param-config.module.ts new file mode 100644 index 0000000..e1c7a9e --- /dev/null +++ b/src/modules/system/param-config/param-config.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { ParamConfigController } from './param-config.controller' +import { ParamConfigEntity } from './param-config.entity' +import { ParamConfigService } from './param-config.service' + +const services = [ParamConfigService] + +@Module({ + imports: [TypeOrmModule.forFeature([ParamConfigEntity])], + controllers: [ParamConfigController], + providers: [...services], + exports: [TypeOrmModule, ...services], +}) +export class ParamConfigModule {} diff --git a/src/modules/system/param-config/param-config.service.ts b/src/modules/system/param-config/param-config.service.ts new file mode 100644 index 0000000..36cbee3 --- /dev/null +++ b/src/modules/system/param-config/param-config.service.ts @@ -0,0 +1,91 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' + +import { Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' +import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity' + +import { ParamConfigDto, ParamConfigQueryDto } from './param-config.dto' + +@Injectable() +export class ParamConfigService { + constructor( + @InjectRepository(ParamConfigEntity) + private paramConfigRepository: Repository, + ) {} + + /** + * 罗列所有配置 + */ + async page({ + page, + pageSize, + name, + }: ParamConfigQueryDto): Promise> { + const queryBuilder = this.paramConfigRepository.createQueryBuilder('config') + + if (name) { + queryBuilder.where('config.name LIKE :name', { + name: `%${name}%`, + }) + } + + return paginate(queryBuilder, { page, pageSize }) + } + + /** + * 获取参数总数 + */ + async countConfigList(): Promise { + return this.paramConfigRepository.count() + } + + /** + * 新增 + */ + async create(dto: ParamConfigDto): Promise { + await this.paramConfigRepository.insert(dto) + } + + /** + * 更新 + */ + async update(id: number, dto: Partial): Promise { + await this.paramConfigRepository.update(id, dto) + } + + /** + * 删除 + */ + async delete(id: number): Promise { + await this.paramConfigRepository.delete(id) + } + + /** + * 查询单个 + */ + async findOne(id: number): Promise { + return this.paramConfigRepository.findOneBy({ id }) + } + + async isExistKey(key: string): Promise { + const result = await this.paramConfigRepository.findOneBy({ key }) + if (result) + throw new BusinessException(ErrorEnum.PARAMETER_CONFIG_KEY_EXISTS) + } + + async findValueByKey(key: string): Promise { + const result = await this.paramConfigRepository.findOne({ + where: { key }, + select: ['value'], + }) + if (result) + return result.value + + return null + } +} diff --git a/src/modules/system/role/role.controller.ts b/src/modules/system/role/role.controller.ts new file mode 100644 index 0000000..c3ac95b --- /dev/null +++ b/src/modules/system/role/role.controller.ts @@ -0,0 +1,85 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + Post, + Put, + Query, +} from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { PagerDto } from '~/common/dto/pager.dto' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { RoleEntity } from '~/modules/system/role/role.entity' + +import { MenuService } from '../menu/menu.service' + +import { RoleDto, RoleUpdateDto } from './role.dto' +import { RoleInfo } from './role.model' +import { RoleService } from './role.service' + +export const permissions = definePermission('system:role', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiTags('System - 角色模块') +@ApiSecurityAuth() +@Controller('roles') +export class RoleController { + constructor( + private roleService: RoleService, + private menuService: MenuService, + ) {} + + @Get() + @ApiOperation({ summary: '获取角色列表' }) + @ApiResult({ type: [RoleEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: PagerDto) { + return this.roleService.findAll(dto) + } + + @Get(':id') + @ApiOperation({ summary: '获取角色信息' }) + @ApiResult({ type: RoleInfo }) + @Perm(permissions.READ) + async info(@IdParam() id: number) { + return this.roleService.info(id) + } + + @Post() + @ApiOperation({ summary: '新增角色' }) + @Perm(permissions.CREATE) + async create(@Body() dto: RoleDto): Promise { + await this.roleService.create(dto) + } + + @Put(':id') + @ApiOperation({ summary: '更新角色' }) + @Perm(permissions.UPDATE) + async update( + @IdParam() id: number, @Body() dto: RoleUpdateDto): Promise { + await this.roleService.update(id, dto) + await this.menuService.refreshOnlineUserPerms() + } + + @Delete(':id') + @ApiOperation({ summary: '删除角色' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + if (await this.roleService.checkUserByRoleId(id)) + throw new BadRequestException('该角色存在关联用户,无法删除') + + await this.roleService.delete(id) + await this.menuService.refreshOnlineUserPerms() + } +} diff --git a/src/modules/system/role/role.dto.ts b/src/modules/system/role/role.dto.ts new file mode 100644 index 0000000..66d2178 --- /dev/null +++ b/src/modules/system/role/role.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty, PartialType } from '@nestjs/swagger' +import { + IsArray, + IsIn, + IsOptional, + IsString, + Matches, + MinLength, +} from 'class-validator' + +export class RoleDto { + @ApiProperty({ description: '角色名称' }) + @IsString() + @MinLength(2, { message: '角色名称长度不能小于2' }) + name: string + + @ApiProperty({ description: '角色值' }) + @IsString() + @Matches(/^[a-z0-9A-Z]+$/, { message: '角色值只能包含字母和数字' }) + @MinLength(2, { message: '角色值长度不能小于2' }) + value: string + + @ApiProperty({ description: '角色备注' }) + @IsString() + @IsOptional() + remark?: string + + @ApiProperty({ description: '状态' }) + @IsIn([0, 1]) + status: number + + @ApiProperty({ description: '关联菜单、权限编号' }) + @IsOptional() + @IsArray() + menuIds?: number[] +} + +export class RoleUpdateDto extends PartialType(RoleDto) {} diff --git a/src/modules/system/role/role.entity.ts b/src/modules/system/role/role.entity.ts new file mode 100644 index 0000000..1317697 --- /dev/null +++ b/src/modules/system/role/role.entity.ts @@ -0,0 +1,43 @@ +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +import { UserEntity } from '../../user/user.entity' +import { MenuEntity } from '../menu/menu.entity' + +@Entity({ name: 'sys_role' }) +export class RoleEntity extends CommonEntity { + @Column({ length: 50, unique: true }) + @ApiProperty({ description: '角色名' }) + name: string + + @Column({ unique: true }) + @ApiProperty({ description: '角色标识' }) + value: string + + @Column({ nullable: true }) + @ApiProperty({ description: '角色描述' }) + remark: string + + @Column({ type: 'tinyint', nullable: true, default: 1 }) + @ApiProperty({ description: '状态:1启用,0禁用' }) + status: number + + @Column({ nullable: true }) + @ApiProperty({ description: '是否默认用户' }) + default: boolean + + @ApiHideProperty() + @ManyToMany(() => UserEntity, user => user.roles) + users: Relation + + @ApiHideProperty() + @ManyToMany(() => MenuEntity, menu => menu.roles, {}) + @JoinTable({ + name: 'sys_role_menus', + joinColumn: { name: 'role_id', referencedColumnName: 'id' }, + inverseJoinColumn: { name: 'menu_id', referencedColumnName: 'id' }, + }) + menus: Relation +} diff --git a/src/modules/system/role/role.model.ts b/src/modules/system/role/role.model.ts new file mode 100644 index 0000000..2d11ab4 --- /dev/null +++ b/src/modules/system/role/role.model.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger' + +import { RoleEntity } from './role.entity' + +export class RoleInfo extends RoleEntity { + @ApiProperty({ type: [Number] }) + menuIds: number[] +} diff --git a/src/modules/system/role/role.module.ts b/src/modules/system/role/role.module.ts new file mode 100644 index 0000000..98c24e6 --- /dev/null +++ b/src/modules/system/role/role.module.ts @@ -0,0 +1,23 @@ +import { Module, forwardRef } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { SseService } from '~/modules/sse/sse.service' + +import { MenuModule } from '../menu/menu.module' + +import { RoleController } from './role.controller' +import { RoleEntity } from './role.entity' +import { RoleService } from './role.service' + +const providers = [RoleService, SseService] + +@Module({ + imports: [ + TypeOrmModule.forFeature([RoleEntity]), + forwardRef(() => MenuModule), + ], + controllers: [RoleController], + providers: [...providers], + exports: [TypeOrmModule, ...providers], +}) +export class RoleModule {} diff --git a/src/modules/system/role/role.service.ts b/src/modules/system/role/role.service.ts new file mode 100644 index 0000000..3eaba39 --- /dev/null +++ b/src/modules/system/role/role.service.ts @@ -0,0 +1,152 @@ +import { Injectable } from '@nestjs/common' +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' +import { isEmpty } from 'lodash' +import { EntityManager, In, Repository } from 'typeorm' + +import { PagerDto } from '~/common/dto/pager.dto' +import { ROOT_ROLE_ID } from '~/constants/system.constant' +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' +import { SseService } from '~/modules/sse/sse.service' +import { MenuEntity } from '~/modules/system/menu/menu.entity' +import { RoleEntity } from '~/modules/system/role/role.entity' + +import { RoleDto, RoleUpdateDto } from './role.dto' + +@Injectable() +export class RoleService { + constructor( + @InjectRepository(RoleEntity) + private roleRepository: Repository, + @InjectRepository(MenuEntity) + private menuRepository: Repository, + @InjectEntityManager() private entityManager: EntityManager, + private sseService: SseService, + + ) {} + + /** + * 列举所有角色:除去超级管理员 + */ + async findAll({ + page, + pageSize, + }: PagerDto): Promise> { + return paginate(this.roleRepository, { page, pageSize }) + } + + /** + * 根据角色获取角色信息 + */ + async info(id: number) { + const info = await this.roleRepository + .createQueryBuilder('role') + .where({ + id, + }) + .getOne() + + const menus = await this.menuRepository.find({ + where: { roles: { id } }, + select: ['id'], + }) + + return { ...info, menuIds: menus.map(m => m.id) } + } + + async delete(id: number): Promise { + if (id === ROOT_ROLE_ID) + throw new Error('不能删除超级管理员') + await this.roleRepository.delete(id) + } + + /** + * 增加角色 + */ + async create({ menuIds, ...data }: RoleDto): Promise<{ roleId: number }> { + const role = await this.roleRepository.save({ + ...data, + menus: menuIds + ? await this.menuRepository.findBy({ id: In(menuIds) }) + : [], + }) + + return { roleId: role.id } + } + + /** + * 更新角色信息 + */ + async update(id, { menuIds, ...data }: RoleUpdateDto): Promise { + await this.roleRepository.update(id, data) + + if (!isEmpty(menuIds)) { + // using transaction + await this.entityManager.transaction(async (manager) => { + const menus = await this.menuRepository.find({ + where: { id: In(menuIds) }, + }) + + const role = await this.roleRepository.findOne({ where: { id } }) + role.menus = menus + await manager.save(role) + }) + } + } + + /** + * 根据用户id查找角色信息 + */ + async getRoleIdsByUser(id: number): Promise { + const roles = await this.roleRepository.find({ + where: { + users: { id }, + }, + }) + + if (!isEmpty(roles)) + return roles.map(r => r.id) + + return [] + } + + async getRoleValues(ids: number[]): Promise { + return ( + await this.roleRepository.findBy({ + id: In(ids), + }) + ).map(r => r.value) + } + + async isAdminRoleByUser(uid: number): Promise { + const roles = await this.roleRepository.find({ + where: { + users: { id: uid }, + }, + }) + + if (!isEmpty(roles)) { + return roles.some( + r => r.id === ROOT_ROLE_ID, + ) + } + return false + } + + hasAdminRole(rids: number[]): boolean { + return rids.includes(ROOT_ROLE_ID) + } + + /** + * 根据角色ID查找是否有关联用户 + */ + async checkUserByRoleId(id: number): Promise { + return this.roleRepository.exist({ + where: { + users: { + roles: { id }, + }, + }, + }) + } +} diff --git a/src/modules/system/serve/serve.controller.ts b/src/modules/system/serve/serve.controller.ts new file mode 100644 index 0000000..eb27465 --- /dev/null +++ b/src/modules/system/serve/serve.controller.ts @@ -0,0 +1,31 @@ +import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager' +import { Controller, Get, UseInterceptors } from '@nestjs/common' +import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' + +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' + +import { AllowAnon } from '~/modules/auth/decorators/allow-anon.decorator' + +import { ServeStatInfo } from './serve.model' +import { ServeService } from './serve.service' + +@ApiTags('System - 服务监控') +@ApiSecurityAuth() +@ApiExtraModels(ServeStatInfo) +@Controller('serve') +@UseInterceptors(CacheInterceptor) +@CacheKey('serve_stat') +@CacheTTL(10000) +export class ServeController { + constructor(private serveService: ServeService) {} + + @Get('stat') + @ApiOperation({ summary: '获取服务器运行信息' }) + @ApiResult({ type: ServeStatInfo }) + @AllowAnon() + async stat(): Promise { + return this.serveService.getServeStat() + } +} diff --git a/src/modules/system/serve/serve.model.ts b/src/modules/system/serve/serve.model.ts new file mode 100644 index 0000000..4c602cb --- /dev/null +++ b/src/modules/system/serve/serve.model.ts @@ -0,0 +1,86 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class Runtime { + @ApiProperty({ description: '系统' }) + os?: string + + @ApiProperty({ description: '服务器架构' }) + arch?: string + + @ApiProperty({ description: 'Node版本' }) + nodeVersion?: string + + @ApiProperty({ description: 'Npm版本' }) + npmVersion?: string +} + +export class CoreLoad { + @ApiProperty({ description: '当前CPU资源消耗' }) + rawLoad?: number + + @ApiProperty({ description: '当前空闲CPU资源' }) + rawLoadIdle?: number +} + +// Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz +export class Cpu { + @ApiProperty({ description: '制造商' }) + manufacturer?: string + + @ApiProperty({ description: '品牌' }) + brand?: string + + @ApiProperty({ description: '物理核心数' }) + physicalCores?: number + + @ApiProperty({ description: '型号' }) + model?: string + + @ApiProperty({ description: '速度 in GHz' }) + speed?: number + + @ApiProperty({ description: 'CPU资源消耗 原始滴答' }) + rawCurrentLoad?: number + + @ApiProperty({ description: '空闲CPU资源 原始滴答' }) + rawCurrentLoadIdle?: number + + @ApiProperty({ description: 'cpu资源消耗', type: [CoreLoad] }) + coresLoad?: CoreLoad[] +} + +export class Disk { + @ApiProperty({ description: '磁盘空间大小 (bytes)' }) + size?: number + + @ApiProperty({ description: '已使用磁盘空间 (bytes)' }) + used?: number + + @ApiProperty({ description: '可用磁盘空间 (bytes)' }) + available?: number +} + +export class Memory { + @ApiProperty({ description: 'total memory in bytes' }) + total?: number + + @ApiProperty({ description: '可用内存' }) + available?: number +} + +/** + * 系统信息 + */ +export class ServeStatInfo { + @ApiProperty({ description: '运行环境', type: Runtime }) + runtime?: Runtime + + @ApiProperty({ description: 'CPU信息', type: Cpu }) + cpu?: Cpu + + @ApiProperty({ description: '磁盘信息', type: Disk }) + disk?: Disk + + @ApiProperty({ description: '内存信息', type: Memory }) + memory?: Memory +} diff --git a/src/modules/system/serve/serve.module.ts b/src/modules/system/serve/serve.module.ts new file mode 100644 index 0000000..db44177 --- /dev/null +++ b/src/modules/system/serve/serve.module.ts @@ -0,0 +1,16 @@ +import { Module, forwardRef } from '@nestjs/common' + +import { SystemModule } from '../system.module' + +import { ServeController } from './serve.controller' +import { ServeService } from './serve.service' + +const providers = [ServeService] + +@Module({ + imports: [forwardRef(() => SystemModule)], + controllers: [ServeController], + providers: [...providers], + exports: [...providers], +}) +export class ServeModule {} diff --git a/src/modules/system/serve/serve.service.ts b/src/modules/system/serve/serve.service.ts new file mode 100644 index 0000000..2b92f3a --- /dev/null +++ b/src/modules/system/serve/serve.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@nestjs/common' +import * as si from 'systeminformation' + +import { Disk, ServeStatInfo } from './serve.model' + +@Injectable() +export class ServeService { + /** + * 获取服务器信息 + */ + async getServeStat(): Promise { + const [versions, osinfo, cpuinfo, currentLoadinfo, meminfo] = ( + await Promise.allSettled([ + si.versions('node, npm'), + si.osInfo(), + si.cpu(), + si.currentLoad(), + si.mem(), + ]) + ).map((p: any) => p.value) + + // 计算总空间 + const diskListInfo = await si.fsSize() + const diskinfo = new Disk() + diskinfo.size = 0 + diskinfo.available = 0 + diskinfo.used = 0 + diskListInfo.forEach((d) => { + diskinfo.size += d.size + diskinfo.available += d.available + diskinfo.used += d.used + }) + + return { + runtime: { + npmVersion: versions.npm, + nodeVersion: versions.node, + os: osinfo.platform, + arch: osinfo.arch, + }, + cpu: { + manufacturer: cpuinfo.manufacturer, + brand: cpuinfo.brand, + physicalCores: cpuinfo.physicalCores, + model: cpuinfo.model, + speed: cpuinfo.speed, + rawCurrentLoad: currentLoadinfo.rawCurrentLoad, + rawCurrentLoadIdle: currentLoadinfo.rawCurrentLoadIdle, + coresLoad: currentLoadinfo.cpus.map((e) => { + return { + rawLoad: e.rawLoad, + rawLoadIdle: e.rawLoadIdle, + } + }), + }, + disk: diskinfo, + memory: { + total: meminfo.total, + available: meminfo.available, + }, + } + } +} diff --git a/src/modules/system/system.module.ts b/src/modules/system/system.module.ts new file mode 100644 index 0000000..ae4b12f --- /dev/null +++ b/src/modules/system/system.module.ts @@ -0,0 +1,45 @@ +import { Module } from '@nestjs/common' + +import { RouterModule } from '@nestjs/core' + +import { UserModule } from '../user/user.module' + +import { DeptModule } from './dept/dept.module' +import { DictItemModule } from './dict-item/dict-item.module' +import { DictTypeModule } from './dict-type/dict-type.module' +import { LogModule } from './log/log.module' +import { MenuModule } from './menu/menu.module' +import { OnlineModule } from './online/online.module' +import { ParamConfigModule } from './param-config/param-config.module' +import { RoleModule } from './role/role.module' +import { ServeModule } from './serve/serve.module' +import { TaskModule } from './task/task.module' + +const modules = [ + UserModule, + RoleModule, + MenuModule, + DeptModule, + DictTypeModule, + DictItemModule, + ParamConfigModule, + LogModule, + TaskModule, + OnlineModule, + ServeModule, +] + +@Module({ + imports: [ + ...modules, + RouterModule.register([ + { + path: 'system', + module: SystemModule, + children: [...modules], + }, + ]), + ], + exports: [...modules], +}) +export class SystemModule {} diff --git a/src/modules/system/task/constant.ts b/src/modules/system/task/constant.ts new file mode 100644 index 0000000..53f5d9b --- /dev/null +++ b/src/modules/system/task/constant.ts @@ -0,0 +1,12 @@ +export enum TaskStatus { + Disabled = 0, + Activited = 1, +} + +export enum TaskType { + Cron = 0, + Interval = 1, +} + +export const SYS_TASK_QUEUE_NAME = 'system:sys-task' +export const SYS_TASK_QUEUE_PREFIX = 'system:sys:task' diff --git a/src/modules/system/task/task.controller.ts b/src/modules/system/task/task.controller.ts new file mode 100644 index 0000000..6888664 --- /dev/null +++ b/src/modules/system/task/task.controller.ts @@ -0,0 +1,98 @@ +import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { Pagination } from '~/helper/paginate/pagination' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { TaskEntity } from '~/modules/system/task/task.entity' + +import { TaskDto, TaskQueryDto, TaskUpdateDto } from './task.dto' +import { TaskService } from './task.service' + +export const permissions = definePermission('system:task', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', + + ONCE: 'once', + START: 'start', + STOP: 'stop', +} as const) + +@ApiTags('System - 任务调度模块') +@ApiSecurityAuth() +@Controller('tasks') +export class TaskController { + constructor(private taskService: TaskService) {} + + @Get() + @ApiOperation({ summary: '获取任务列表' }) + @ApiResult({ type: [TaskEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: TaskQueryDto): Promise> { + return this.taskService.list(dto) + } + + @Post() + @ApiOperation({ summary: '添加任务' }) + @Perm(permissions.CREATE) + async create(@Body() dto: TaskDto): Promise { + const serviceCall = dto.service.split('.') + await this.taskService.checkHasMissionMeta(serviceCall[0], serviceCall[1]) + await this.taskService.create(dto) + } + + @Put(':id') + @ApiOperation({ summary: '更新任务' }) + @Perm(permissions.UPDATE) + async update(@IdParam() id: number, @Body() dto: TaskUpdateDto): Promise { + const serviceCall = dto.service.split('.') + await this.taskService.checkHasMissionMeta(serviceCall[0], serviceCall[1]) + await this.taskService.update(id, dto) + } + + @Get(':id') + @ApiOperation({ summary: '查询任务详细信息' }) + @ApiResult({ type: TaskEntity }) + @Perm(permissions.READ) + async info(@IdParam() id: number): Promise { + return this.taskService.info(id) + } + + @Delete(':id') + @ApiOperation({ summary: '删除任务' }) + @Perm(permissions.DELETE) + async delete(@IdParam() id: number): Promise { + const task = await this.taskService.info(id) + await this.taskService.delete(task) + } + + @Put(':id/once') + @ApiOperation({ summary: '手动执行一次任务' }) + @Perm(permissions.ONCE) + async once(@IdParam() id: number): Promise { + const task = await this.taskService.info(id) + await this.taskService.once(task) + } + + @Put(':id/stop') + @ApiOperation({ summary: '停止任务' }) + @Perm(permissions.STOP) + async stop(@IdParam() id: number): Promise { + const task = await this.taskService.info(id) + await this.taskService.stop(task) + } + + @Put(':id/start') + @ApiOperation({ summary: '启动任务' }) + @Perm(permissions.START) + async start(@IdParam() id: number): Promise { + const task = await this.taskService.info(id) + + await this.taskService.start(task) + } +} diff --git a/src/modules/system/task/task.dto.ts b/src/modules/system/task/task.dto.ts new file mode 100644 index 0000000..c02d24f --- /dev/null +++ b/src/modules/system/task/task.dto.ts @@ -0,0 +1,105 @@ +import { BadRequestException } from '@nestjs/common' +import { ApiProperty, ApiPropertyOptional, IntersectionType, PartialType } from '@nestjs/swagger' +import { + IsDateString, + IsIn, + IsInt, + IsOptional, + IsString, + MaxLength, + Min, + MinLength, + Validate, + ValidateIf, + ValidationArguments, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator' +import * as parser from 'cron-parser' +import { isEmpty } from 'lodash' + +import { PagerDto } from '~/common/dto/pager.dto' + +// cron 表达式验证,bull lib下引用了cron-parser +@ValidatorConstraint({ name: 'isCronExpression', async: false }) +export class IsCronExpression implements ValidatorConstraintInterface { + validate(value: string, _args: ValidationArguments) { + try { + if (isEmpty(value)) + throw new BadRequestException('cron expression is empty') + + parser.parseExpression(value) + return true + } + catch (e) { + return false + } + } + + defaultMessage(_args: ValidationArguments) { + return 'this cron expression ($value) invalid' + } +} + +export class TaskDto { + @ApiProperty({ description: '任务名称' }) + @IsString() + @MinLength(2) + @MaxLength(50) + name: string + + @ApiProperty({ description: '调用的服务' }) + @IsString() + @MinLength(1) + service: string + + @ApiProperty({ description: '任务类别:cron | interval' }) + @IsIn([0, 1]) + type: number + + @ApiProperty({ description: '任务状态' }) + @IsIn([0, 1]) + status: number + + @ApiPropertyOptional({ description: '开始时间', type: Date }) + @IsDateString() + @ValidateIf(o => !isEmpty(o.startTime)) + startTime: string + + @ApiPropertyOptional({ description: '结束时间', type: Date }) + @IsDateString() + @ValidateIf(o => !isEmpty(o.endTime)) + endTime: string + + @ApiPropertyOptional({ + description: '限制执行次数,负数则无限制', + }) + @IsOptional() + @IsInt() + limit?: number = -1 + + @ApiProperty({ description: 'cron表达式' }) + @Validate(IsCronExpression) + @ValidateIf(o => o.type === 0) + cron: string + + @ApiProperty({ description: '执行间隔,毫秒单位' }) + @IsInt() + @Min(100) + @ValidateIf(o => o.type === 1) + every?: number + + @ApiPropertyOptional({ description: '执行参数' }) + @IsOptional() + @IsString() + data?: string + + @ApiPropertyOptional({ description: '任务备注' }) + @IsOptional() + @IsString() + remark?: string +} + +export class TaskUpdateDto extends PartialType(TaskDto) {} + +export class TaskQueryDto extends IntersectionType(PagerDto, PartialType(TaskDto)) {} diff --git a/src/modules/system/task/task.entity.ts b/src/modules/system/task/task.entity.ts new file mode 100644 index 0000000..9ceb548 --- /dev/null +++ b/src/modules/system/task/task.entity.ts @@ -0,0 +1,55 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +@Entity({ name: 'sys_task' }) +export class TaskEntity extends CommonEntity { + @Column({ type: 'varchar', length: 50, unique: true }) + @ApiProperty({ description: '任务名' }) + name: string + + @Column() + @ApiProperty({ description: '任务标识' }) + service: string + + @Column({ type: 'tinyint', default: 0 }) + @ApiProperty({ description: '任务类型 0cron 1间隔' }) + type: number + + @Column({ type: 'tinyint', default: 1 }) + @ApiProperty({ description: '任务状态 0禁用 1启用' }) + status: number + + @Column({ name: 'start_time', type: 'datetime', nullable: true }) + @ApiProperty({ description: '开始时间' }) + startTime: Date + + @Column({ name: 'end_time', type: 'datetime', nullable: true }) + @ApiProperty({ description: '结束时间' }) + endTime: Date + + @Column({ type: 'int', nullable: true, default: 0 }) + @ApiProperty({ description: '间隔时间' }) + limit: number + + @Column({ nullable: true }) + @ApiProperty({ description: 'cron表达式' }) + cron: string + + @Column({ type: 'int', nullable: true }) + @ApiProperty({ description: '执行次数' }) + every: number + + @Column({ type: 'text', nullable: true }) + @ApiProperty({ description: '任务参数' }) + data: string + + @Column({ name: 'job_opts', type: 'text', nullable: true }) + @ApiProperty({ description: '任务配置' }) + jobOpts: string + + @Column({ nullable: true }) + @ApiProperty({ description: '任务描述' }) + remark: string +} diff --git a/src/modules/system/task/task.module.ts b/src/modules/system/task/task.module.ts new file mode 100644 index 0000000..1fed543 --- /dev/null +++ b/src/modules/system/task/task.module.ts @@ -0,0 +1,37 @@ +import { BullModule } from '@nestjs/bull' +import { Module } from '@nestjs/common' + +import { ConfigService } from '@nestjs/config' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { ConfigKeyPaths, IRedisConfig } from '~/config' + +import { LogModule } from '../log/log.module' + +import { SYS_TASK_QUEUE_NAME, SYS_TASK_QUEUE_PREFIX } from './constant' + +import { TaskController } from './task.controller' +import { TaskEntity } from './task.entity' +import { TaskConsumer } from './task.processor' +import { TaskService } from './task.service' + +const providers = [TaskService, TaskConsumer] + +@Module({ + imports: [ + TypeOrmModule.forFeature([TaskEntity]), + BullModule.registerQueueAsync({ + name: SYS_TASK_QUEUE_NAME, + useFactory: (configService: ConfigService) => ({ + redis: configService.get('redis'), + prefix: SYS_TASK_QUEUE_PREFIX, + }), + inject: [ConfigService], + }), + LogModule, + ], + controllers: [TaskController], + providers: [...providers], + exports: [TypeOrmModule, ...providers], +}) +export class TaskModule {} diff --git a/src/modules/system/task/task.processor.ts b/src/modules/system/task/task.processor.ts new file mode 100644 index 0000000..d45e24c --- /dev/null +++ b/src/modules/system/task/task.processor.ts @@ -0,0 +1,44 @@ +import { OnQueueCompleted, Process, Processor } from '@nestjs/bull' +import { Job } from 'bull' + +import { TaskLogService } from '../log/services/task-log.service' + +import { SYS_TASK_QUEUE_NAME } from './constant' + +import { TaskService } from './task.service' + +export interface ExecuteData { + id: number + args?: string | null + service: string +} + +@Processor(SYS_TASK_QUEUE_NAME) +export class TaskConsumer { + constructor( + private taskService: TaskService, + private taskLogService: TaskLogService, + ) {} + + @Process() + async handle(job: Job): Promise { + const startTime = Date.now() + const { data } = job + try { + await this.taskService.callService(data.service, data.args) + const timing = Date.now() - startTime + // 任务执行成功 + await this.taskLogService.create(data.id, 1, timing) + } + catch (e) { + const timing = Date.now() - startTime + // 执行失败 + await this.taskLogService.create(data.id, 0, timing, `${e}`) + } + } + + @OnQueueCompleted() + onCompleted(job: Job) { + this.taskService.updateTaskCompleteStatus(job.data.id) + } +} diff --git a/src/modules/system/task/task.service.ts b/src/modules/system/task/task.service.ts new file mode 100644 index 0000000..8cc0e28 --- /dev/null +++ b/src/modules/system/task/task.service.ts @@ -0,0 +1,369 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { InjectQueue } from '@nestjs/bull' +import { + BadRequestException, + Injectable, + Logger, + NotFoundException, + OnModuleInit, +} from '@nestjs/common' +import { ModuleRef, Reflector } from '@nestjs/core' +import { UnknownElementException } from '@nestjs/core/errors/exceptions/unknown-element.exception' +import { InjectRepository } from '@nestjs/typeorm' +import { Queue } from 'bull' +import Redis from 'ioredis' +import { isEmpty } from 'lodash' +import { Like, Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' + +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' + +import { TaskEntity } from '~/modules/system/task/task.entity' +import { MISSION_DECORATOR_KEY } from '~/modules/tasks/mission.decorator' + +import { + SYS_TASK_QUEUE_NAME, + SYS_TASK_QUEUE_PREFIX, + TaskStatus, +} from './constant' +import { TaskDto, TaskQueryDto, TaskUpdateDto } from './task.dto' + +@Injectable() +export class TaskService implements OnModuleInit { + private logger = new Logger(TaskService.name) + + constructor( + @InjectRepository(TaskEntity) + private taskRepository: Repository, + @InjectQueue(SYS_TASK_QUEUE_NAME) private taskQueue: Queue, + private moduleRef: ModuleRef, + private reflector: Reflector, + @InjectRedis() private redis: Redis, + ) {} + + /** + * module init + */ + async onModuleInit() { + await this.initTask() + } + + /** + * 初始化任务,系统启动前调用 + */ + async initTask(): Promise { + const initKey = `${SYS_TASK_QUEUE_PREFIX}:init` + // 防止重复初始化 + const result = await this.redis + .multi() + .setnx(initKey, new Date().getTime()) + .expire(initKey, 60 * 30) + .exec() + if (result[0][1] === 0) { + // 存在锁则直接跳过防止重复初始化 + this.logger.log('Init task is lock', TaskService.name) + return + } + const jobs = await this.taskQueue.getJobs([ + 'active', + 'delayed', + 'failed', + 'paused', + 'waiting', + 'completed', + ]) + jobs.forEach((j) => { + j.remove() + }) + + // 查找所有需要运行的任务 + const tasks = await this.taskRepository.findBy({ status: 1 }) + if (tasks && tasks.length > 0) { + for (const t of tasks) + await this.start(t) + } + // 启动后释放锁 + await this.redis.del(initKey) + } + + async list({ + page, + pageSize, + name, + service, + type, + status, + }: TaskQueryDto): Promise> { + const queryBuilder = this.taskRepository + .createQueryBuilder('task') + .where({ + ...(name ? { name: Like(`%${name}%`) } : null), + ...(service ? { service: Like(`%${service}%`) } : null), + ...(type ? { type } : null), + ...(status ? { status } : null), + }) + .orderBy('task.id', 'ASC') + + return paginate(queryBuilder, { page, pageSize }) + } + + /** + * task info + */ + async info(id: number): Promise { + const task = this.taskRepository + .createQueryBuilder('task') + .where({ id }) + .getOne() + + if (!task) + throw new NotFoundException('Task Not Found') + + return task + } + + /** + * delete task + */ + async delete(task: TaskEntity): Promise { + if (!task) + throw new BadRequestException('Task is Empty') + + await this.stop(task) + await this.taskRepository.delete(task.id) + } + + /** + * 手动执行一次 + */ + async once(task: TaskEntity): Promise { + if (task) { + await this.taskQueue.add( + { id: task.id, service: task.service, args: task.data }, + { jobId: task.id, removeOnComplete: true, removeOnFail: true }, + ) + } + else { + throw new BadRequestException('Task is Empty') + } + } + + async create(dto: TaskDto): Promise { + const result = await this.taskRepository.save(dto) + const task = await this.info(result.id) + if (result.status === 0) + await this.stop(task) + else if (result.status === TaskStatus.Activited) + await this.start(task) + } + + async update(id: number, dto: TaskUpdateDto): Promise { + await this.taskRepository.update(id, dto) + const task = await this.info(id) + if (task.status === 0) + await this.stop(task) + else if (task.status === TaskStatus.Activited) + await this.start(task) + } + + /** + * 启动任务 + */ + async start(task: TaskEntity): Promise { + if (!task) + throw new BadRequestException('Task is Empty') + + // 先停掉之前存在的任务 + await this.stop(task) + let repeat: any + if (task.type === 1) { + // 间隔 Repeat every millis (cron setting cannot be used together with this setting.) + repeat = { + every: task.every, + } + } + else { + // cron + repeat = { + cron: task.cron, + } + // Start date when the repeat job should start repeating (only with cron). + if (task.startTime) + repeat.startDate = task.startTime + + if (task.endTime) + repeat.endDate = task.endTime + } + if (task.limit > 0) + repeat.limit = task.limit + + const job = await this.taskQueue.add( + { id: task.id, service: task.service, args: task.data }, + { jobId: task.id, removeOnComplete: true, removeOnFail: true, repeat }, + ) + if (job && job.opts) { + await this.taskRepository.update(task.id, { + jobOpts: JSON.stringify(job.opts.repeat), + status: 1, + }) + } + else { + // update status to 0,标识暂停任务,因为启动失败 + await job?.remove() + await this.taskRepository.update(task.id, { + status: TaskStatus.Disabled, + }) + throw new BadRequestException('Task Start failed') + } + } + + /** + * 停止任务 + */ + async stop(task: TaskEntity): Promise { + if (!task) + throw new BadRequestException('Task is Empty') + + const exist = await this.existJob(task.id.toString()) + if (!exist) { + await this.taskRepository.update(task.id, { + status: TaskStatus.Disabled, + }) + return + } + const jobs = await this.taskQueue.getJobs([ + 'active', + 'delayed', + 'failed', + 'paused', + 'waiting', + 'completed', + ]) + jobs + .filter(j => j.data.id === task.id) + .forEach(async (j) => { + await j.remove() + }) + + await this.taskRepository.update(task.id, { status: TaskStatus.Disabled }) + // if (task.jobOpts) { + // await this.app.queue.sys.removeRepeatable(JSON.parse(task.jobOpts)); + // // update status + // await this.getRepo().admin.sys.Task.update(task.id, { status: TaskStatus.Disabled, }); + // } + } + + /** + * 查看队列中任务是否存在 + */ + async existJob(jobId: string): Promise { + // https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueremoverepeatablebykey + const jobs = await this.taskQueue.getRepeatableJobs() + const ids = jobs.map((e) => { + return e.id + }) + return ids.includes(jobId) + } + + /** + * 更新是否已经完成,完成则移除该任务并修改状态 + */ + async updateTaskCompleteStatus(tid: number): Promise { + const jobs = await this.taskQueue.getRepeatableJobs() + const task = await this.taskRepository.findOneBy({ id: tid }) + // 如果下次执行时间小于当前时间,则表示已经执行完成。 + for (const job of jobs) { + const currentTime = new Date().getTime() + if (job.id === tid.toString() && job.next < currentTime) { + // 如果下次执行时间小于当前时间,则表示已经执行完成。 + await this.stop(task) + break + } + } + } + + /** + * 检测service是否有注解定义 + * @param serviceName service + */ + async checkHasMissionMeta( + nameOrInstance: string | unknown, + exec: string, + ): Promise { + try { + let service: any + if (typeof nameOrInstance === 'string') + service = await this.moduleRef.get(nameOrInstance, { strict: false }) + else + service = nameOrInstance + + // 所执行的任务不存在 + if (!service || !(exec in service)) + throw new NotFoundException('任务不存在') + + // 检测是否有Mission注解 + const hasMission = this.reflector.get( + MISSION_DECORATOR_KEY, + service.constructor, + ) + // 如果没有,则抛出错误 + if (!hasMission) + throw new BusinessException(ErrorEnum.INSECURE_MISSION) + } + catch (e) { + if (e instanceof UnknownElementException) { + // 任务不存在 + throw new NotFoundException('任务不存在') + } + else { + // 其余错误则不处理,继续抛出 + throw e + } + } + } + + /** + * 根据serviceName调用service,例如 LogService.clearReqLog + */ + async callService(name: string, args: string): Promise { + if (name) { + const [serviceName, methodName] = name.split('.') + if (!methodName) + throw new BadRequestException('serviceName define BadRequestException') + + const service = await this.moduleRef.get(serviceName, { + strict: false, + }) + + // 安全注解检查 + await this.checkHasMissionMeta(service, methodName) + if (isEmpty(args)) { + await service[methodName]() + } + else { + // 参数安全判断 + const parseArgs = this.safeParse(args) + + if (Array.isArray(parseArgs)) { + // 数组形式则自动扩展成方法参数回掉 + await service[methodName](...parseArgs) + } + else { + await service[methodName](parseArgs) + } + } + } + } + + safeParse(args: string): unknown | string { + try { + return JSON.parse(args) + } + catch (e) { + return args + } + } +} diff --git a/src/modules/system/task/task.ts b/src/modules/system/task/task.ts new file mode 100644 index 0000000..26d1e2c --- /dev/null +++ b/src/modules/system/task/task.ts @@ -0,0 +1,2 @@ +export const SYS_TASK_QUEUE_NAME = 'system:sys-task' +export const SYS_TASK_QUEUE_PREFIX = 'system:sys:task' diff --git a/src/modules/tasks/jobs/email.job.ts b/src/modules/tasks/jobs/email.job.ts new file mode 100644 index 0000000..1f923e4 --- /dev/null +++ b/src/modules/tasks/jobs/email.job.ts @@ -0,0 +1,29 @@ +import { BadRequestException, Injectable } from '@nestjs/common' + +import { LoggerService } from '~/shared/logger/logger.service' +import { MailerService } from '~/shared/mailer/mailer.service' + +import { Mission } from '../mission.decorator' + +/** + * Api接口请求类型任务 + */ +@Injectable() +@Mission() +export class EmailJob { + constructor( + private readonly emailService: MailerService, + private readonly logger: LoggerService, + ) {} + + async send(config: any): Promise { + if (config) { + const { to, subject, content } = config + const result = await this.emailService.send(to, subject, content) + this.logger.log(result, EmailJob.name) + } + else { + throw new BadRequestException('Email send job param is empty') + } + } +} diff --git a/src/modules/tasks/jobs/http-request.job.ts b/src/modules/tasks/jobs/http-request.job.ts new file mode 100644 index 0000000..359cb06 --- /dev/null +++ b/src/modules/tasks/jobs/http-request.job.ts @@ -0,0 +1,32 @@ +import { HttpService } from '@nestjs/axios' +import { BadRequestException, Injectable } from '@nestjs/common' + +import { LoggerService } from '~/shared/logger/logger.service' + +import { Mission } from '../mission.decorator' + +/** + * Api接口请求类型任务 + */ +@Injectable() +@Mission() +export class HttpRequestJob { + constructor( + private readonly httpService: HttpService, + private readonly logger: LoggerService, + ) {} + + /** + * 发起请求 + * @param config {AxiosRequestConfig} + */ + async handle(config: unknown): Promise { + if (config) { + const result = await this.httpService.request(config) + this.logger.log(result, HttpRequestJob.name) + } + else { + throw new BadRequestException('Http request job param is empty') + } + } +} diff --git a/src/modules/tasks/jobs/log-clear.job.ts b/src/modules/tasks/jobs/log-clear.job.ts new file mode 100644 index 0000000..fe6fa52 --- /dev/null +++ b/src/modules/tasks/jobs/log-clear.job.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common' + +import { LoginLogService } from '~/modules/system/log/services/login-log.service' +import { TaskLogService } from '~/modules/system/log/services/task-log.service' + +import { Mission } from '../mission.decorator' + +/** + * 管理后台日志清理任务 + */ +@Injectable() +@Mission() +export class LogClearJob { + constructor( + private loginLogService: LoginLogService, + private taskLogService: TaskLogService, + ) {} + + async clearLoginLog(): Promise { + await this.loginLogService.clearLog() + } + + async clearTaskLog(): Promise { + await this.taskLogService.clearLog() + } +} diff --git a/src/modules/tasks/mission.decorator.ts b/src/modules/tasks/mission.decorator.ts new file mode 100644 index 0000000..c14c7c9 --- /dev/null +++ b/src/modules/tasks/mission.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common' + +export const MISSION_DECORATOR_KEY = 'decorator:mission' + +/** + * 定时任务标记,没有该任务标记的任务不会被执行,保证全局获取下的模块被安全执行 + */ +export const Mission = () => SetMetadata(MISSION_DECORATOR_KEY, true) diff --git a/src/modules/tasks/tasks.module.ts b/src/modules/tasks/tasks.module.ts new file mode 100644 index 0000000..9c6b1de --- /dev/null +++ b/src/modules/tasks/tasks.module.ts @@ -0,0 +1,46 @@ +import { DynamicModule, ExistingProvider, Module } from '@nestjs/common' + +import { LogModule } from '~/modules/system/log/log.module' +import { SystemModule } from '~/modules/system/system.module' + +import { EmailJob } from './jobs/email.job' +import { HttpRequestJob } from './jobs/http-request.job' +import { LogClearJob } from './jobs/log-clear.job' + +const providers = [LogClearJob, HttpRequestJob, EmailJob] + +/** + * auto create alias + * { + * provide: 'LogClearMissionService', + * useExisting: LogClearMissionService, + * } + */ +function createAliasProviders(): ExistingProvider[] { + const aliasProviders: ExistingProvider[] = [] + for (const p of providers) { + aliasProviders.push({ + provide: p.name, + useExisting: p, + }) + } + return aliasProviders +} + +/** + * 所有需要执行的定时任务都需要在这里注册 + */ +@Module({}) +export class TasksModule { + static forRoot(): DynamicModule { + // 使用Alias定义别名,使得可以通过字符串类型获取定义的Service,否则无法获取 + const aliasProviders = createAliasProviders() + return { + global: true, + module: TasksModule, + imports: [SystemModule, LogModule], + providers: [...providers, ...aliasProviders], + exports: aliasProviders, + } + } +} diff --git a/src/modules/todo/todo.controller.ts b/src/modules/todo/todo.controller.ts new file mode 100644 index 0000000..0cb95d6 --- /dev/null +++ b/src/modules/todo/todo.controller.ts @@ -0,0 +1,79 @@ +import { + Body, + Controller, + Delete, + Get, + Post, + Put, + Query, + UseGuards, +} from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' + +import { Pagination } from '~/helper/paginate/pagination' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { Resource } from '~/modules/auth/decorators/resource.decorator' + +import { ResourceGuard } from '~/modules/auth/guards/resource.guard' +import { TodoEntity } from '~/modules/todo/todo.entity' + +import { TodoDto, TodoQueryDto, TodoUpdateDto } from './todo.dto' +import { TodoService } from './todo.service' + +export const permissions = definePermission('todo', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const) + +@ApiTags('Business - Todo模块') +@UseGuards(ResourceGuard) +@Controller('todos') +export class TodoController { + constructor(private readonly todoService: TodoService) {} + + @Get() + @ApiOperation({ summary: '获取Todo列表' }) + @ApiResult({ type: [TodoEntity] }) + @Perm(permissions.LIST) + async list(@Query() dto: TodoQueryDto): Promise> { + return this.todoService.list(dto) + } + + @Get(':id') + @ApiOperation({ summary: '获取Todo详情' }) + @ApiResult({ type: TodoEntity }) + @Perm(permissions.READ) + async info(@IdParam() id: number): Promise { + return this.todoService.detail(id) + } + + @Post() + @ApiOperation({ summary: '创建Todo' }) + @Perm(permissions.CREATE) + async create(@Body() dto: TodoDto): Promise { + await this.todoService.create(dto) + } + + @Put(':id') + @ApiOperation({ summary: '更新Todo' }) + @Perm(permissions.UPDATE) + @Resource(TodoEntity) + async update( + @IdParam() id: number, @Body() dto: TodoUpdateDto): Promise { + await this.todoService.update(id, dto) + } + + @Delete(':id') + @ApiOperation({ summary: '删除Todo' }) + @Perm(permissions.DELETE) + @Resource(TodoEntity) + async delete(@IdParam() id: number): Promise { + await this.todoService.delete(id) + } +} diff --git a/src/modules/todo/todo.dto.ts b/src/modules/todo/todo.dto.ts new file mode 100644 index 0000000..8b222b3 --- /dev/null +++ b/src/modules/todo/todo.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger' +import { IsString } from 'class-validator' + +import { PagerDto } from '~/common/dto/pager.dto' + +export class TodoDto { + @ApiProperty({ description: '名称' }) + @IsString() + value: string +} + +export class TodoUpdateDto extends PartialType(TodoDto) {} + +export class TodoQueryDto extends IntersectionType(PagerDto, TodoDto) {} diff --git a/src/modules/todo/todo.entity.ts b/src/modules/todo/todo.entity.ts new file mode 100644 index 0000000..acc32ad --- /dev/null +++ b/src/modules/todo/todo.entity.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' +import { UserEntity } from '~/modules/user/user.entity' + +@Entity('todo') +export class TodoEntity extends CommonEntity { + @Column() + @ApiProperty({ description: 'todo' }) + value: string + + @ApiProperty({ description: 'todo' }) + @Column({ default: false }) + status: boolean + + @ManyToOne(() => UserEntity) + @JoinColumn({ name: 'user_id' }) + user: Relation +} diff --git a/src/modules/todo/todo.module.ts b/src/modules/todo/todo.module.ts new file mode 100644 index 0000000..5a3d417 --- /dev/null +++ b/src/modules/todo/todo.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { TodoController } from './todo.controller' +import { TodoEntity } from './todo.entity' +import { TodoService } from './todo.service' + +const services = [TodoService] + +@Module({ + imports: [TypeOrmModule.forFeature([TodoEntity])], + controllers: [TodoController], + providers: [...services], + exports: [TypeOrmModule, ...services], +}) +export class TodoModule {} diff --git a/src/modules/todo/todo.service.ts b/src/modules/todo/todo.service.ts new file mode 100644 index 0000000..4fbf35e --- /dev/null +++ b/src/modules/todo/todo.service.ts @@ -0,0 +1,46 @@ +import { Injectable, NotFoundException } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Repository } from 'typeorm' + +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' +import { TodoEntity } from '~/modules/todo/todo.entity' + +import { TodoDto, TodoQueryDto, TodoUpdateDto } from './todo.dto' + +@Injectable() +export class TodoService { + constructor( + @InjectRepository(TodoEntity) + private todoRepository: Repository, + ) {} + + async list({ + page, + pageSize, + }: TodoQueryDto): Promise> { + return paginate(this.todoRepository, { page, pageSize }) + } + + async detail(id: number): Promise { + const item = await this.todoRepository.findOneBy({ id }) + if (!item) + throw new NotFoundException('未找到该记录') + + return item + } + + async create(dto: TodoDto) { + await this.todoRepository.save(dto) + } + + async update(id: number, dto: TodoUpdateDto) { + await this.todoRepository.update(id, dto) + } + + async delete(id: number) { + const item = await this.detail(id) + + await this.todoRepository.remove(item) + } +} diff --git a/src/modules/tools/email/email.controller.ts b/src/modules/tools/email/email.controller.ts new file mode 100644 index 0000000..d3a1816 --- /dev/null +++ b/src/modules/tools/email/email.controller.ts @@ -0,0 +1,22 @@ +import { Body, Controller, Post } from '@nestjs/common' + +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { MailerService } from '~/shared/mailer/mailer.service' + +import { EmailSendDto } from './email.dto' + +@ApiTags('System - 邮箱模块') +@ApiSecurityAuth() +@Controller('email') +export class EmailController { + constructor(private emailService: MailerService) {} + + @ApiOperation({ summary: '发送邮件' }) + @Post('send') + async send(@Body() dto: EmailSendDto): Promise { + const { to, subject, content } = dto + await this.emailService.send(to, subject, content, 'html') + } +} diff --git a/src/modules/tools/email/email.dto.ts b/src/modules/tools/email/email.dto.ts new file mode 100644 index 0000000..8dcca35 --- /dev/null +++ b/src/modules/tools/email/email.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsEmail, IsString } from 'class-validator' + +/** + * 发送邮件 + */ +export class EmailSendDto { + @ApiProperty({ description: '收件人邮箱' }) + @IsEmail() + to: string + + @ApiProperty({ description: '标题' }) + @IsString() + subject: string + + @ApiProperty({ description: '正文' }) + @IsString() + content: string +} diff --git a/src/modules/tools/email/email.module.ts b/src/modules/tools/email/email.module.ts new file mode 100644 index 0000000..e9ba5b1 --- /dev/null +++ b/src/modules/tools/email/email.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common' + +import { EmailController } from './email.controller' + +@Module({ + imports: [], + controllers: [EmailController], +}) +export class EmailModule {} diff --git a/src/modules/tools/storage/storage.controller.ts b/src/modules/tools/storage/storage.controller.ts new file mode 100644 index 0000000..bdd92ba --- /dev/null +++ b/src/modules/tools/storage/storage.controller.ts @@ -0,0 +1,41 @@ +import { Body, Controller, Get, Post, Query } from '@nestjs/common' + +import { ApiOperation, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' + +import { Pagination } from '~/helper/paginate/pagination' + +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' + +import { StorageDeleteDto, StoragePageDto } from './storage.dto' +import { StorageInfo } from './storage.modal' +import { StorageService } from './storage.service' + +export const permissions = definePermission('tool:storage', { + LIST: 'list', + DELETE: 'delete', +} as const) + +@ApiTags('Tools - 存储模块') +@ApiSecurityAuth() +@Controller('storage') +export class StorageController { + constructor(private storageService: StorageService) {} + + @Get('list') + @ApiOperation({ summary: '获取本地存储列表' }) + @ApiResult({ type: [StorageInfo], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: StoragePageDto): Promise> { + return this.storageService.list(dto) + } + + @ApiOperation({ summary: '删除文件' }) + @Post('delete') + @Perm(permissions.DELETE) + async delete(@Body() dto: StorageDeleteDto): Promise { + await this.storageService.delete(dto.ids) + } +} diff --git a/src/modules/tools/storage/storage.dto.ts b/src/modules/tools/storage/storage.dto.ts new file mode 100644 index 0000000..5e50398 --- /dev/null +++ b/src/modules/tools/storage/storage.dto.ts @@ -0,0 +1,68 @@ +import { ApiProperty } from '@nestjs/swagger' +import { ArrayNotEmpty, IsArray, IsOptional, IsString } from 'class-validator' + +import { PagerDto } from '~/common/dto/pager.dto' + +export class StoragePageDto extends PagerDto { + @ApiProperty({ description: '文件名' }) + @IsOptional() + @IsString() + name: string + + @ApiProperty({ description: '文件后缀' }) + @IsString() + @IsOptional() + extName: string + + @ApiProperty({ description: '文件类型' }) + @IsString() + @IsOptional() + type: string + + @ApiProperty({ description: '大小' }) + @IsString() + @IsOptional() + size: string + + @ApiProperty({ description: '上传时间' }) + @IsOptional() + time: string[] + + @ApiProperty({ description: '上传者' }) + @IsString() + @IsOptional() + username: string +} + +export class StorageCreateDto { + @ApiProperty({ description: '文件名' }) + @IsString() + name: string + + @ApiProperty({ description: '真实文件名' }) + @IsString() + fileName: string + + @ApiProperty({ description: '文件扩展名' }) + @IsString() + extName: string + + @ApiProperty({ description: '文件路径' }) + @IsString() + path: string + + @ApiProperty({ description: '文件路径' }) + @IsString() + type: string + + @ApiProperty({ description: '文件大小' }) + @IsString() + size: string +} + +export class StorageDeleteDto { + @ApiProperty({ description: '需要删除的文件ID列表', type: [Number] }) + @IsArray() + @ArrayNotEmpty() + ids: number[] +} diff --git a/src/modules/tools/storage/storage.entity.ts b/src/modules/tools/storage/storage.entity.ts new file mode 100644 index 0000000..c68dcf5 --- /dev/null +++ b/src/modules/tools/storage/storage.entity.ts @@ -0,0 +1,40 @@ +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity } from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +@Entity({ name: 'tool_storage' }) +export class Storage extends CommonEntity { + @Column({ type: 'varchar', length: 200, comment: '文件名' }) + @ApiProperty({ description: '文件名' }) + name: string + + @Column({ + type: 'varchar', + length: 200, + nullable: true, + comment: '真实文件名', + }) + @ApiProperty({ description: '真实文件名' }) + fileName: string + + @Column({ name: 'ext_name', type: 'varchar', nullable: true }) + @ApiProperty({ description: '扩展名' }) + extName: string + + @Column({ type: 'varchar' }) + @ApiProperty({ description: '文件类型' }) + path: string + + @Column({ type: 'varchar', nullable: true }) + @ApiProperty({ description: '文件类型' }) + type: string + + @Column({ type: 'varchar', nullable: true }) + @ApiProperty({ description: '文件大小' }) + size: string + + @Column({ nullable: true, name: 'user_id' }) + @ApiProperty({ description: '用户ID' }) + userId: number +} diff --git a/src/modules/tools/storage/storage.modal.ts b/src/modules/tools/storage/storage.modal.ts new file mode 100644 index 0000000..203e974 --- /dev/null +++ b/src/modules/tools/storage/storage.modal.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class StorageInfo { + @ApiProperty({ description: '文件ID' }) + id: number + + @ApiProperty({ description: '文件名' }) + name: string + + @ApiProperty({ description: '文件扩展名' }) + extName: string + + @ApiProperty({ description: '文件路径' }) + path: string + + @ApiProperty({ description: '文件类型' }) + type: string + + @ApiProperty({ description: '大小' }) + size: string + + @ApiProperty({ description: '上传时间' }) + createdAt: string + + @ApiProperty({ description: '上传者' }) + username: string +} diff --git a/src/modules/tools/storage/storage.module.ts b/src/modules/tools/storage/storage.module.ts new file mode 100644 index 0000000..aed7f33 --- /dev/null +++ b/src/modules/tools/storage/storage.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { UserEntity } from '~/modules/user/user.entity' + +import { StorageController } from './storage.controller' +import { Storage } from './storage.entity' +import { StorageService } from './storage.service' + +const services = [StorageService] + +@Module({ + imports: [TypeOrmModule.forFeature([Storage, UserEntity])], + controllers: [StorageController], + providers: [...services], + exports: [TypeOrmModule, ...services], +}) +export class StorageModule {} diff --git a/src/modules/tools/storage/storage.service.ts b/src/modules/tools/storage/storage.service.ts new file mode 100644 index 0000000..e7ddefa --- /dev/null +++ b/src/modules/tools/storage/storage.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Between, Like, Repository } from 'typeorm' + +import { paginateRaw } from '~/helper/paginate' +import { PaginationTypeEnum } from '~/helper/paginate/interface' +import { Pagination } from '~/helper/paginate/pagination' +import { Storage } from '~/modules/tools/storage/storage.entity' +import { UserEntity } from '~/modules/user/user.entity' +import { deleteFile } from '~/utils' + +import { StorageCreateDto, StoragePageDto } from './storage.dto' +import { StorageInfo } from './storage.modal' + +@Injectable() +export class StorageService { + constructor( + @InjectRepository(Storage) + private storageRepository: Repository, + @InjectRepository(UserEntity) + private userRepository: Repository, + ) {} + + async create(dto: StorageCreateDto, userId: number): Promise { + await this.storageRepository.save({ + ...dto, + userId, + }) + } + + /** + * 删除文件 + */ + async delete(fileIds: number[]): Promise { + const items = await this.storageRepository.findByIds(fileIds) + await this.storageRepository.delete(fileIds) + + items.forEach((el) => { + deleteFile(el.path) + }) + } + + async list({ + page, + pageSize, + name, + type, + size, + extName, + time, + username, + }: StoragePageDto): Promise> { + const queryBuilder = this.storageRepository + .createQueryBuilder('storage') + .leftJoinAndSelect('sys_user', 'user', 'storage.user_id = user.id') + .where({ + ...(name && { name: Like(`%${name}%`) }), + ...(type && { type }), + ...(extName && { extName }), + ...(size && { size: Between(size[0], size[1]) }), + ...(time && { createdAt: Between(time[0], time[1]) }), + ...(username && { + userId: await (await this.userRepository.findOneBy({ username })).id, + }), + }) + .orderBy('storage.created_at', 'DESC') + + const { items, ...rest } = await paginateRaw(queryBuilder, { + page, + pageSize, + paginationType: PaginationTypeEnum.LIMIT_AND_OFFSET, + }) + + function formatResult(result: Storage[]) { + return result.map((e: any) => { + return { + id: e.storage_id, + name: e.storage_name, + extName: e.storage_ext_name, + path: e.storage_path, + type: e.storage_type, + size: e.storage_size, + createdAt: e.storage_created_at, + username: e.user_username, + } + }) + } + + return { + items: formatResult(items), + ...rest, + } + } + + async count(): Promise { + return this.storageRepository.count() + } +} diff --git a/src/modules/tools/tools.module.ts b/src/modules/tools/tools.module.ts new file mode 100644 index 0000000..d22d131 --- /dev/null +++ b/src/modules/tools/tools.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common' + +import { RouterModule } from '@nestjs/core' + +import { EmailModule } from './email/email.module' +import { StorageModule } from './storage/storage.module' +import { UploadModule } from './upload/upload.module' + +const modules = [StorageModule, EmailModule, UploadModule] + +@Module({ + imports: [...modules, RouterModule.register([ + { + path: 'tools', + module: ToolsModule, + children: [...modules], + }, + ])], + exports: [...modules], +}) +export class ToolsModule {} diff --git a/src/modules/tools/upload/file.constraint.ts b/src/modules/tools/upload/file.constraint.ts new file mode 100644 index 0000000..dfaa04b --- /dev/null +++ b/src/modules/tools/upload/file.constraint.ts @@ -0,0 +1,64 @@ +import { FastifyMultipartBaseOptions, MultipartFile } from '@fastify/multipart' +import { + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, + registerDecorator, +} from 'class-validator' +import { has, isArray } from 'lodash' + +type FileLimit = Pick< + FastifyMultipartBaseOptions['limits'], + 'fileSize' | 'files' +> & { + mimetypes?: string[] +} +function checkFileAndLimit(file: MultipartFile, limits: FileLimit = {}) { + if (!('mimetype' in file)) + return false + if (limits.mimetypes && !limits.mimetypes.includes(file.mimetype)) + return false + if ( + has(file, '_buf') + && Buffer.byteLength((file as any)._buf) > limits.fileSize + ) + return false + return true +} + +@ValidatorConstraint({ name: 'isFile' }) +export class FileConstraint implements ValidatorConstraintInterface { + validate(value: MultipartFile, args: ValidationArguments) { + const [limits = {}] = args.constraints + const values = (args.object as any)[args.property] + const filesLimit = (limits as FileLimit).files ?? 0 + if (filesLimit > 0 && isArray(values) && values.length > filesLimit) + return false + return checkFileAndLimit(value, limits) + } + + defaultMessage(_args: ValidationArguments) { + return `The file which to upload's conditions are not met` + } +} + +/** + * 图片验证规则 + * @param limits 限制选项 + * @param validationOptions class-validator选项 + */ +export function IsFile( + limits?: FileLimit, + validationOptions?: ValidationOptions, +) { + return (object: Record, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [limits], + validator: FileConstraint, + }) + } +} diff --git a/src/modules/tools/upload/upload.controller.ts b/src/modules/tools/upload/upload.controller.ts new file mode 100644 index 0000000..8a73704 --- /dev/null +++ b/src/modules/tools/upload/upload.controller.ts @@ -0,0 +1,53 @@ +import { BadRequestException, Controller, Post, Req } from '@nestjs/common' +import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger' +import { FastifyRequest } from 'fastify' + +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' + +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' + +import { FileUploadDto } from './upload.dto' +import { UploadService } from './upload.service' + +export const permissions = definePermission('upload', { + UPLOAD: 'upload', +} as const) + +@ApiSecurityAuth() +@ApiTags('Tools - 上传模块') +@Controller('upload') +export class UploadController { + constructor(private uploadService: UploadService) {} + + @Post() + @Perm(permissions.UPLOAD) + @ApiOperation({ summary: '上传' }) + @ApiConsumes('multipart/form-data') + @ApiBody({ + type: FileUploadDto, + }) + async upload(@Req() req: FastifyRequest, @AuthUser() user: IAuthUser) { + if (!req.isMultipart()) + throw new BadRequestException('Request is not multipart') + + const file = await req.file() + + // https://github.com/fastify/fastify-multipart + // const parts = req.files() + // for await (const part of parts) + // console.log(part.file) + + try { + const path = await this.uploadService.saveFile(file, user.uid) + + return { + filename: path, + } + } + catch (error) { + console.log(error) + throw new BadRequestException('上传失败') + } + } +} diff --git a/src/modules/tools/upload/upload.dto.ts b/src/modules/tools/upload/upload.dto.ts new file mode 100644 index 0000000..3ab9a01 --- /dev/null +++ b/src/modules/tools/upload/upload.dto.ts @@ -0,0 +1,27 @@ +import { MultipartFile } from '@fastify/multipart' +import { ApiProperty } from '@nestjs/swagger' + +import { IsDefined } from 'class-validator' + +import { IsFile } from './file.constraint' + +export class FileUploadDto { + @ApiProperty({ type: Buffer, format: 'binary', description: '文件' }) + @IsDefined() + @IsFile( + { + mimetypes: [ + 'image/png', + 'image/gif', + 'image/jpeg', + 'image/webp', + 'image/svg+xml', + ], + fileSize: 1024 * 1024 * 10, + }, + { + message: '文件类型不正确', + }, + ) + file: MultipartFile +} diff --git a/src/modules/tools/upload/upload.module.ts b/src/modules/tools/upload/upload.module.ts new file mode 100644 index 0000000..6674739 --- /dev/null +++ b/src/modules/tools/upload/upload.module.ts @@ -0,0 +1,16 @@ +import { Module, forwardRef } from '@nestjs/common' + +import { StorageModule } from '../storage/storage.module' + +import { UploadController } from './upload.controller' +import { UploadService } from './upload.service' + +const services = [UploadService] + +@Module({ + imports: [forwardRef(() => StorageModule)], + controllers: [UploadController], + providers: [...services], + exports: [...services], +}) +export class UploadModule {} diff --git a/src/modules/tools/upload/upload.service.ts b/src/modules/tools/upload/upload.service.ts new file mode 100644 index 0000000..792448f --- /dev/null +++ b/src/modules/tools/upload/upload.service.ts @@ -0,0 +1,53 @@ +import { MultipartFile } from '@fastify/multipart' +import { Injectable, NotFoundException } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { isNil } from 'lodash' +import { Repository } from 'typeorm' + +import { Storage } from '~/modules/tools/storage/storage.entity' + +import { + fileRename, + getExtname, + getFilePath, + getFileType, + getSize, + saveLocalFile, +} from '~/utils/file.util' + +@Injectable() +export class UploadService { + constructor( + @InjectRepository(Storage) + private storageRepository: Repository, + ) {} + + /** + * 保存文件上传记录 + */ + async saveFile(file: MultipartFile, userId: number): Promise { + if (isNil(file)) + throw new NotFoundException('Have not any file to upload!') + + const fileName = file.filename + const size = getSize(file.file.bytesRead) + const extName = getExtname(fileName) + const type = getFileType(extName) + const name = fileRename(fileName) + const path = getFilePath(name) + + saveLocalFile(await file.toBuffer(), name) + + await this.storageRepository.save({ + name, + fileName, + extName, + path, + type, + size, + userId, + }) + + return path + } +} diff --git a/src/modules/user/constant.ts b/src/modules/user/constant.ts new file mode 100644 index 0000000..2510ff6 --- /dev/null +++ b/src/modules/user/constant.ts @@ -0,0 +1,4 @@ +export enum UserStatus { + Disable = 0, + Enabled = 1, +} diff --git a/src/modules/user/dto/password.dto.ts b/src/modules/user/dto/password.dto.ts new file mode 100644 index 0000000..3a6d97b --- /dev/null +++ b/src/modules/user/dto/password.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger' +import { IsString, Matches, MaxLength, MinLength } from 'class-validator' + +export class PasswordUpdateDto { + @ApiProperty({ description: '旧密码' }) + @IsString() + @Matches(/^[a-z0-9A-Z\W_]+$/) + @MinLength(6) + @MaxLength(20) + oldPassword: string + + @ApiProperty({ description: '新密码' }) + @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { + message: '密码必须包含数字、字母,长度为6-16', + }) + newPassword: string +} + +export class UserPasswordDto { + // @ApiProperty({ description: '管理员/用户ID' }) + // @IsEntityExist(UserEntity, { message: '用户不存在' }) + // @IsInt() + // id: number + + @ApiProperty({ description: '更改后的密码' }) + @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { + message: '密码格式不正确', + }) + password: string +} + +export class UserExistDto { + @ApiProperty({ description: '登录账号' }) + @IsString() + @Matches(/^[a-zA-Z0-9_-]{4,16}$/) + @MinLength(6) + @MaxLength(20) + username: string +} diff --git a/src/modules/user/dto/user.dto.ts b/src/modules/user/dto/user.dto.ts new file mode 100644 index 0000000..31e5ddc --- /dev/null +++ b/src/modules/user/dto/user.dto.ts @@ -0,0 +1,98 @@ +import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger' +import { Type } from 'class-transformer' +import { + ArrayMaxSize, + ArrayMinSize, + ArrayNotEmpty, + IsEmail, + IsIn, + IsInt, + IsOptional, + IsString, + Matches, + MaxLength, + MinLength, + ValidateIf, +} from 'class-validator' +import { isEmpty } from 'lodash' + +import { PagerDto } from '~/common/dto/pager.dto' + +export class UserDto { + @ApiProperty({ description: '头像' }) + @IsOptional() + @IsString() + avatar?: string + + @ApiProperty({ description: '登录账号', example: 'admin' }) + @IsString() + @Matches(/^[a-z0-9A-Z\W_]+$/) + @MinLength(4) + @MaxLength(20) + username: string + + @ApiProperty({ description: '登录密码', example: 'a123456' }) + @IsOptional() + @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { + message: '密码必须包含数字、字母,长度为6-16', + }) + password: string + + @ApiProperty({ description: '归属角色', type: [Number] }) + @ArrayNotEmpty() + @ArrayMinSize(1) + @ArrayMaxSize(3) + roleIds: number[] + + @ApiProperty({ description: '归属大区', type: Number }) + @Type(() => Number) + @IsInt() + @IsOptional() + deptId?: number + + @ApiProperty({ description: '呢称', example: 'admin' }) + @IsOptional() + @IsString() + nickname: string + + @ApiProperty({ description: '邮箱', example: 'bqy.dev@qq.com' }) + @IsEmail() + @ValidateIf(o => !isEmpty(o.email)) + email: string + + @ApiProperty({ description: '手机号' }) + @IsOptional() + @IsString() + phone?: string + + @ApiProperty({ description: 'QQ' }) + @IsOptional() + @IsString() + @Matches(/^[1-9][0-9]{4,10}$/) + @MinLength(5) + @MaxLength(11) + qq?: string + + @ApiProperty({ description: '备注' }) + @IsOptional() + @IsString() + remark?: string + + @ApiProperty({ description: '状态' }) + @IsIn([0, 1]) + status: number +} + +export class UserUpdateDto extends PartialType(UserDto) {} + +export class UserQueryDto extends IntersectionType(PagerDto, PartialType(UserDto)) { + @ApiProperty({ description: '归属大区', example: 1, required: false }) + @IsInt() + @IsOptional() + deptId?: number + + @ApiProperty({ description: '状态', example: 0, required: false }) + @IsInt() + @IsOptional() + status?: number +} diff --git a/src/modules/user/user.controller.ts b/src/modules/user/user.controller.ts new file mode 100644 index 0000000..7b4fd1f --- /dev/null +++ b/src/modules/user/user.controller.ts @@ -0,0 +1,82 @@ +import { Body, Controller, Delete, Get, Param, ParseArrayPipe, Post, Put, Query } from '@nestjs/common' +import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger' + +import { ApiResult } from '~/common/decorators/api-result.decorator' +import { IdParam } from '~/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { MenuService } from '~/modules/system/menu/menu.service' + +import { Perm, definePermission } from '../auth/decorators/permission.decorator' + +import { UserPasswordDto } from './dto/password.dto' +import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto' +import { UserEntity } from './user.entity' +import { UserService } from './user.service' + +export const permissions = definePermission('system:user', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', + + PASSWORD_UPDATE: 'password:update', + PASSWORD_RESET: 'pass:reset', +} as const) + +@ApiTags('System - 用户模块') +@ApiSecurityAuth() +@Controller('users') +export class UserController { + constructor( + private userService: UserService, + private menuService: MenuService, + ) {} + + @Get() + @ApiOperation({ summary: '获取用户列表' }) + @ApiResult({ type: [UserEntity], isPage: true }) + @Perm(permissions.LIST) + async list(@Query() dto: UserQueryDto) { + return this.userService.list(dto) + } + + @Get(':id') + @ApiOperation({ summary: '查询用户' }) + @Perm(permissions.READ) + async read(@IdParam() id: number) { + return this.userService.info(id) + } + + @Post() + @ApiOperation({ summary: '新增用户' }) + @Perm(permissions.CREATE) + async create(@Body() dto: UserDto): Promise { + await this.userService.create(dto) + } + + @Put(':id') + @ApiOperation({ summary: '更新用户' }) + @Perm(permissions.UPDATE) + async update( + @IdParam() id: number, @Body() dto: UserUpdateDto): Promise { + await this.userService.update(id, dto) + await this.menuService.refreshPerms(id) + } + + @Delete(':id') + @ApiOperation({ summary: '删除用户' }) + @ApiParam({ name: 'id', type: String, schema: { oneOf: [{ type: 'string' }, { type: 'number' }] } }) + @Perm(permissions.DELETE) + async delete(@Param('id', new ParseArrayPipe({ items: Number, separator: ',' })) ids: number[]): Promise { + await this.userService.delete(ids) + await this.userService.multiForbidden(ids) + } + + @Post(':id/password') + @ApiOperation({ summary: '更改用户密码' }) + @Perm(permissions.PASSWORD_UPDATE) + async password(@IdParam() id: number, @Body() dto: UserPasswordDto): Promise { + await this.userService.forceUpdatePassword(id, dto.password) + } +} diff --git a/src/modules/user/user.entity.ts b/src/modules/user/user.entity.ts new file mode 100644 index 0000000..b1f66b8 --- /dev/null +++ b/src/modules/user/user.entity.ts @@ -0,0 +1,69 @@ +import { Exclude } from 'class-transformer' +import { + Column, + Entity, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, + OneToMany, + Relation, +} from 'typeorm' + +import { CommonEntity } from '~/common/entity/common.entity' + +import { AccessTokenEntity } from '~/modules/auth/entities/access-token.entity' + +import { DeptEntity } from '~/modules/system/dept/dept.entity' +import { RoleEntity } from '~/modules/system/role/role.entity' + +@Entity({ name: 'sys_user' }) +export class UserEntity extends CommonEntity { + @Column({ unique: true }) + username: string + + @Exclude() + @Column() + password: string + + @Column({ length: 32 }) + psalt: string + + @Column({ nullable: true }) + nickname: string + + @Column({ name: 'avatar', nullable: true }) + avatar: string + + @Column({ nullable: true }) + qq: string + + @Column({ nullable: true }) + email: string + + @Column({ nullable: true }) + phone: string + + @Column({ nullable: true }) + remark: string + + @Column({ type: 'tinyint', nullable: true, default: 1 }) + status: number + + @ManyToMany(() => RoleEntity, role => role.users) + @JoinTable({ + name: 'sys_user_roles', + joinColumn: { name: 'user_id', referencedColumnName: 'id' }, + inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' }, + }) + roles: Relation + + @ManyToOne(() => DeptEntity, dept => dept.users) + @JoinColumn({ name: 'dept_id' }) + dept: Relation + + @OneToMany(() => AccessTokenEntity, accessToken => accessToken.user, { + cascade: true, + }) + accessTokens: Relation +} diff --git a/src/modules/user/user.model.ts b/src/modules/user/user.model.ts new file mode 100644 index 0000000..1493187 --- /dev/null +++ b/src/modules/user/user.model.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class AccountInfo { + @ApiProperty({ description: '用户名' }) + username: string + + @ApiProperty({ description: '昵称' }) + nickname: string + + @ApiProperty({ description: '邮箱' }) + email: string + + @ApiProperty({ description: '手机号' }) + phone: string + + @ApiProperty({ description: '备注' }) + remark: string + + @ApiProperty({ description: '头像' }) + avatar: string +} diff --git a/src/modules/user/user.module.ts b/src/modules/user/user.module.ts new file mode 100644 index 0000000..bd26eae --- /dev/null +++ b/src/modules/user/user.module.ts @@ -0,0 +1,26 @@ +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { MenuModule } from '../system/menu/menu.module' +import { ParamConfigModule } from '../system/param-config/param-config.module' + +import { RoleModule } from '../system/role/role.module' + +import { UserController } from './user.controller' +import { UserEntity } from './user.entity' +import { UserService } from './user.service' + +const providers = [UserService] + +@Module({ + imports: [ + TypeOrmModule.forFeature([UserEntity]), + RoleModule, + MenuModule, + ParamConfigModule, + ], + controllers: [UserController], + providers: [...providers], + exports: [TypeOrmModule, ...providers], +}) +export class UserModule {} diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts new file mode 100644 index 0000000..806526c --- /dev/null +++ b/src/modules/user/user.service.ts @@ -0,0 +1,372 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { BadRequestException, Injectable } from '@nestjs/common' +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' +import Redis from 'ioredis' +import { isEmpty, isNil } from 'lodash' + +import { EntityManager, In, Like, Repository } from 'typeorm' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { ErrorEnum } from '~/constants/error-code.constant' +import { ROOT_ROLE_ID, SYS_USER_INITPASSWORD } from '~/constants/system.constant' +import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey' + +import { paginate } from '~/helper/paginate' +import { Pagination } from '~/helper/paginate/pagination' +import { AccountUpdateDto } from '~/modules/auth/dto/account.dto' +import { RegisterDto } from '~/modules/auth/dto/auth.dto' +import { QQService } from '~/shared/helper/qq.service' + +import { md5, randomValue } from '~/utils' + +import { DeptEntity } from '../system/dept/dept.entity' +import { ParamConfigService } from '../system/param-config/param-config.service' +import { RoleEntity } from '../system/role/role.entity' + +import { UserStatus } from './constant' +import { PasswordUpdateDto } from './dto/password.dto' +import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto' +import { UserEntity } from './user.entity' +import { AccountInfo } from './user.model' + +@Injectable() +export class UserService { + constructor( + @InjectRedis() + private readonly redis: Redis, + @InjectRepository(UserEntity) + private readonly userRepository: Repository, + @InjectRepository(RoleEntity) + private readonly roleRepository: Repository, + @InjectEntityManager() private entityManager: EntityManager, + private readonly paramConfigService: ParamConfigService, + private readonly qqService: QQService, + ) {} + + async findUserById(id: number): Promise { + return this.userRepository + .createQueryBuilder('user') + .where({ + id, + status: UserStatus.Enabled, + }) + .getOne() + } + + async findUserByUserName(username: string): Promise { + return this.userRepository + .createQueryBuilder('user') + .where({ + username, + status: UserStatus.Enabled, + }) + .getOne() + } + + /** + * 获取用户信息 + * @param uid user id + */ + async getAccountInfo(uid: number): Promise { + const user: UserEntity = await this.userRepository + .createQueryBuilder('user') + .leftJoinAndSelect('user.roles', 'role') + .where(`user.id = :uid`, { uid }) + .getOne() + + if (isEmpty(user)) + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) + + delete user?.psalt + + return user + } + + /** + * 更新个人信息 + */ + async updateAccountInfo(uid: number, info: AccountUpdateDto): Promise { + const user = await this.userRepository.findOneBy({ id: uid }) + if (isEmpty(user)) + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) + + const data = { + ...(info.nickname ? { nickname: info.nickname } : null), + ...(info.avatar ? { avatar: info.avatar } : null), + ...(info.email ? { email: info.email } : null), + ...(info.phone ? { phone: info.phone } : null), + ...(info.qq ? { qq: info.qq } : null), + ...(info.remark ? { remark: info.remark } : null), + } + + if (!info.avatar && info.qq) { + // 如果qq不等于原qq,则更新qq头像 + if (info.qq !== user.qq) + data.avatar = await this.qqService.getAvater(info.qq) + } + + await this.userRepository.update(uid, data) + } + + /** + * 更改密码 + */ + async updatePassword(uid: number, dto: PasswordUpdateDto): Promise { + const user = await this.userRepository.findOneBy({ id: uid }) + if (isEmpty(user)) + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) + + const comparePassword = md5(`${dto.oldPassword}${user.psalt}`) + // 原密码不一致,不允许更改 + if (user.password !== comparePassword) + throw new BusinessException(ErrorEnum.PASSWORD_MISMATCH) + + const password = md5(`${dto.newPassword}${user.psalt}`) + await this.userRepository.update({ id: uid }, { password }) + await this.upgradePasswordV(user.id) + } + + /** + * 直接更改密码 + */ + async forceUpdatePassword(uid: number, password: string): Promise { + const user = await this.userRepository.findOneBy({ id: uid }) + + const newPassword = md5(`${password}${user.psalt}`) + await this.userRepository.update({ id: uid }, { password: newPassword }) + await this.upgradePasswordV(user.id) + } + + /** + * 增加系统用户,如果返回false则表示已存在该用户 + */ + async create({ + username, + password, + roleIds, + deptId, + ...data + }: UserDto): Promise { + const exists = await this.userRepository.findOneBy({ + username, + }) + if (!isEmpty(exists)) + throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) + + await this.entityManager.transaction(async (manager) => { + const salt = randomValue(32) + + if (!password) { + const initPassword = await this.paramConfigService.findValueByKey( + SYS_USER_INITPASSWORD, + ) + password = md5(`${initPassword ?? '123456'}${salt}`) + } + else { + password = md5(`${password ?? '123456'}${salt}`) + } + const u = manager.create(UserEntity, { + username, + password, + ...data, + psalt: salt, + roles: await this.roleRepository.findBy({ id: In(roleIds) }), + dept: await DeptEntity.findOneBy({ id: deptId }), + }) + + const result = await manager.save(u) + return result + }) + } + + /** + * 更新用户信息 + */ + async update( + id: number, + { password, deptId, roleIds, status, ...data }: UserUpdateDto, + ): Promise { + await this.entityManager.transaction(async (manager) => { + if (password) + await this.forceUpdatePassword(id, password) + + await manager.update(UserEntity, id, { + ...data, + status, + }) + + const user = await this.userRepository + .createQueryBuilder('user') + .leftJoinAndSelect('user.roles', 'roles') + .leftJoinAndSelect('user.dept', 'dept') + .where('user.id = :id', { id }) + .getOne() + + await manager + .createQueryBuilder() + .relation(UserEntity, 'roles') + .of(id) + .addAndRemove(roleIds, user.roles) + + await manager + .createQueryBuilder() + .relation(UserEntity, 'dept') + .of(id) + .set(deptId) + + if (status === 0) { + // 禁用状态 + await this.forbidden(id) + } + }) + } + + /** + * 查找用户信息 + * @param id 用户id + */ + async info(id: number): Promise { + const user = await this.userRepository + .createQueryBuilder('user') + .leftJoinAndSelect('user.roles', 'roles') + .leftJoinAndSelect('user.dept', 'dept') + .where('user.id = :id', { id }) + .getOne() + + delete user.password + delete user.psalt + + return user + } + + /** + * 根据ID列表删除用户 + */ + async delete(userIds: number[]): Promise { + const rootUserId = await this.findRootUserId() + if (userIds.includes(rootUserId)) + throw new BadRequestException('不能删除root用户!') + + await this.userRepository.delete(userIds) + } + + /** + * 查找超管的用户ID + */ + async findRootUserId(): Promise { + const user = await this.userRepository.findOneBy({ + roles: { id: ROOT_ROLE_ID }, + }) + return user.id + } + + /** + * 查询用户列表 + */ + async list({ + page, + pageSize, + username, + nickname, + deptId, + email, + status, + }: UserQueryDto): Promise> { + const queryBuilder = this.userRepository + .createQueryBuilder('user') + .leftJoinAndSelect('user.dept', 'dept') + .leftJoinAndSelect('user.roles', 'role') + // .where('user.id NOT IN (:...ids)', { ids: [rootUserId, uid] }) + .where({ + ...(username ? { username: Like(`%${username}%`) } : null), + ...(nickname ? { nickname: Like(`%${nickname}%`) } : null), + ...(email ? { email: Like(`%${email}%`) } : null), + ...(status ? { status } : null), + }) + + if (deptId) + queryBuilder.andWhere('dept.id = :deptId', { deptId }) + + return paginate(queryBuilder, { + page, + pageSize, + }) + } + + /** + * 禁用用户 + */ + async forbidden(uid: number): Promise { + await this.redis.del(genAuthPVKey(uid)) + await this.redis.del(genAuthTokenKey(uid)) + await this.redis.del(genAuthPermKey(uid)) + } + + /** + * 禁用多个用户 + */ + async multiForbidden(uids: number[]): Promise { + if (uids) { + const pvs: string[] = [] + const ts: string[] = [] + const ps: string[] = [] + uids.forEach((uid) => { + pvs.push(genAuthPVKey(uid)) + ts.push(genAuthTokenKey(uid)) + ps.push(genAuthPermKey(uid)) + }) + await this.redis.del(pvs) + await this.redis.del(ts) + await this.redis.del(ps) + } + } + + /** + * 升级用户版本密码 + */ + async upgradePasswordV(id: number): Promise { + // admin:passwordVersion:${param.id} + const v = await this.redis.get(genAuthPVKey(id)) + if (!isEmpty(v)) + await this.redis.set(genAuthPVKey(id), Number.parseInt(v) + 1) + } + + /** + * 判断用户名是否存在 + */ + async exist(username: string) { + const user = await this.userRepository.findOneBy({ username }) + if (isNil(user)) + throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) + + return true + } + + /** + * 注册 + */ + async register({ username, ...data }: RegisterDto): Promise { + const exists = await this.userRepository.findOneBy({ + username, + }) + if (!isEmpty(exists)) + throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) + + await this.entityManager.transaction(async (manager) => { + const salt = randomValue(32) + + const password = md5(`${data.password ?? 'a123456'}${salt}`) + + const u = manager.create(UserEntity, { + username, + password, + status: 1, + psalt: salt, + }) + + const user = await manager.save(u) + + return user + }) + } +} diff --git a/src/repl.ts b/src/repl.ts new file mode 100644 index 0000000..649d7ba --- /dev/null +++ b/src/repl.ts @@ -0,0 +1,12 @@ +import { repl } from '@nestjs/core' + +import { AppModule } from './app.module' + +async function bootstrap() { + const replServer = await repl(AppModule) + replServer.setupHistory('.nestjs_repl_history', (err) => { + if (err) + console.error(err) + }) +} +bootstrap() diff --git a/src/setup-swagger.ts b/src/setup-swagger.ts new file mode 100644 index 0000000..4858584 --- /dev/null +++ b/src/setup-swagger.ts @@ -0,0 +1,44 @@ +import { INestApplication, Logger } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' + +import { API_SECURITY_AUTH } from './common/decorators/swagger.decorator' +import { CommonEntity } from './common/entity/common.entity' +import { ResOp, TreeResult } from './common/model/response.model' +import { ConfigKeyPaths, IAppConfig, ISwaggerConfig } from './config' +import { Pagination } from './helper/paginate/pagination' + +export function setupSwagger( + app: INestApplication, + configService: ConfigService, +): void { + const { name, port } = configService.get('app')! + const { enable, path } = configService.get('swagger')! + + if (!enable) + return + + const documentBuilder = new DocumentBuilder() + .setTitle(name) + .setDescription(`${name} API document`) + .setVersion('1.0') + + // auth security + documentBuilder.addSecurity(API_SECURITY_AUTH, { + description: 'Auth', + type: 'apiKey', + in: 'header', + name: 'Authorization', + }) + + const document = SwaggerModule.createDocument(app, documentBuilder.build(), { + ignoreGlobalPrefix: false, + extraModels: [CommonEntity, ResOp, Pagination, TreeResult], + }) + + SwaggerModule.setup(path, app, document) + + // started log + const logger = new Logger('SwaggerModule') + logger.log(`Document running on http://127.0.0.1:${port}/${path}`) +} diff --git a/src/shared/database/constraints/entity-exist.constraint.ts b/src/shared/database/constraints/entity-exist.constraint.ts new file mode 100644 index 0000000..2ce89cd --- /dev/null +++ b/src/shared/database/constraints/entity-exist.constraint.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common' +import { + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, + registerDecorator, +} from 'class-validator' +import { DataSource, ObjectType, Repository } from 'typeorm' + +interface Condition { + entity: ObjectType + // 如果没有指定字段则使用当前验证的属性作为查询依据 + field?: string +} + +/** + * 查询某个字段的值是否在数据表中存在 + */ +@ValidatorConstraint({ name: 'entityItemExist', async: true }) +@Injectable() +export class EntityExistConstraint implements ValidatorConstraintInterface { + constructor(private dataSource: DataSource) {} + + async validate(value: string, args: ValidationArguments) { + let repo: Repository + + if (!value) + return true + // 默认对比字段是id + let field = 'id' + // 通过传入的 entity 获取其 repository + if ('entity' in args.constraints[0]) { + // 传入的是对象 可以指定对比字段 + field = args.constraints[0].field ?? 'id' + repo = this.dataSource.getRepository(args.constraints[0].entity) + } + else { + // 传入的是实体类 + repo = this.dataSource.getRepository(args.constraints[0]) + } + // 通过查询记录是否存在进行验证 + const item = await repo.findOne({ where: { [field]: value } }) + return !!item + } + + defaultMessage(args: ValidationArguments) { + if (!args.constraints[0]) + return 'Model not been specified!' + + return `All instance of ${args.constraints[0].name} must been exists in databse!` + } +} + +/** + * 数据存在性验证 + * @param params Entity类或验证条件对象 + * @param validationOptions + */ +function IsEntityExist( + entity: ObjectType, + validationOptions?: ValidationOptions, +): (object: Record, propertyName: string) => void + +function IsEntityExist( + condition: { entity: ObjectType, field?: string }, + validationOptions?: ValidationOptions, +): (object: Record, propertyName: string) => void + +function IsEntityExist( + condition: ObjectType | { entity: ObjectType, field?: string }, + validationOptions?: ValidationOptions, +): (object: Record, propertyName: string) => void { + return (object: Record, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [condition], + validator: EntityExistConstraint, + }) + } +} + +export { IsEntityExist } diff --git a/src/shared/database/constraints/unique.constraint.ts b/src/shared/database/constraints/unique.constraint.ts new file mode 100644 index 0000000..65fa562 --- /dev/null +++ b/src/shared/database/constraints/unique.constraint.ts @@ -0,0 +1,97 @@ +import { Injectable } from '@nestjs/common' +import { + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, + registerDecorator, +} from 'class-validator' +import { isNil, merge } from 'lodash' +import { DataSource, ObjectType } from 'typeorm' + +interface Condition { + entity: ObjectType + // 如果没有指定字段则使用当前验证的属性作为查询依据 + field?: string +} + +/** + * 验证某个字段的唯一性 + */ +@ValidatorConstraint({ name: 'entityItemUnique', async: true }) +@Injectable() +export class UniqueConstraint implements ValidatorConstraintInterface { + constructor(private dataSource: DataSource) {} + + async validate(value: any, args: ValidationArguments) { + // 获取要验证的模型和字段 + const config: Omit = { + field: args.property, + } + const condition = ('entity' in args.constraints[0] + ? merge(config, args.constraints[0]) + : { + ...config, + entity: args.constraints[0], + }) as unknown as Required + if (!condition.entity) + return false + try { + // 查询是否存在数据,如果已经存在则验证失败 + const repo = this.dataSource.getRepository(condition.entity) + return isNil( + await repo.findOne({ + where: { [condition.field]: value }, + }), + ) + } + catch (err) { + // 如果数据库操作异常则验证失败 + return false + } + } + + defaultMessage(args: ValidationArguments) { + const { entity, property } = args.constraints[0] + const queryProperty = property ?? args.property + if (!(args.object as any).getManager) + return 'getManager function not been found!' + + if (!entity) + return 'Model not been specified!' + + return `${queryProperty} of ${entity.name} must been unique!` + } +} + +/** + * 数据唯一性验证 + * @param params Entity类或验证条件对象 + * @param validationOptions + */ +function IsUnique( + entity: ObjectType, + validationOptions?: ValidationOptions, +): (object: Record, propertyName: string) => void + +function IsUnique( + condition: Condition, + validationOptions?: ValidationOptions, +): (object: Record, propertyName: string) => void + +function IsUnique( + params: ObjectType | Condition, + validationOptions?: ValidationOptions, +) { + return (object: Record, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName, + options: validationOptions, + constraints: [params], + validator: UniqueConstraint, + }) + } +} + +export { IsUnique } diff --git a/src/shared/database/database.module.ts b/src/shared/database/database.module.ts new file mode 100644 index 0000000..acb5526 --- /dev/null +++ b/src/shared/database/database.module.ts @@ -0,0 +1,51 @@ +import { Module } from '@nestjs/common' + +import { ConfigService } from '@nestjs/config' +import { TypeOrmModule } from '@nestjs/typeorm' + +import { DataSource, LoggerOptions } from 'typeorm' + +import { ConfigKeyPaths, IDatabaseConfig } from '~/config' + +import { env } from '~/global/env' + +import { EntityExistConstraint } from './constraints/entity-exist.constraint' +import { UniqueConstraint } from './constraints/unique.constraint' +import { TypeORMLogger } from './typeorm-logger' + +const providers = [EntityExistConstraint, UniqueConstraint] + +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + let loggerOptions: LoggerOptions = env('DB_LOGGING') as 'all' + + try { + // 解析成 js 数组 ['error'] + loggerOptions = JSON.parse(loggerOptions) + } + catch { + // ignore + } + + return { + ...configService.get('database'), + autoLoadEntities: true, + logging: loggerOptions, + logger: new TypeORMLogger(loggerOptions), + } + }, + // dataSource receives the configured DataSourceOptions + // and returns a Promise. + dataSourceFactory: async (options) => { + const dataSource = await new DataSource(options).initialize() + return dataSource + }, + }), + ], + providers, + exports: providers, +}) +export class DatabaseModule {} diff --git a/src/shared/database/typeorm-logger.ts b/src/shared/database/typeorm-logger.ts new file mode 100644 index 0000000..6db7638 --- /dev/null +++ b/src/shared/database/typeorm-logger.ts @@ -0,0 +1,118 @@ +import { Logger } from '@nestjs/common' +import { Logger as ITypeORMLogger, LoggerOptions, QueryRunner } from 'typeorm' + +export class TypeORMLogger implements ITypeORMLogger { + private logger = new Logger(TypeORMLogger.name) + + constructor(private options: LoggerOptions) {} + + logQuery(query: string, parameters?: any[], _queryRunner?: QueryRunner) { + if (!this.isEnable('query')) + return + + const sql + = query + + (parameters && parameters.length + ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` + : '') + + this.logger.log(`[QUERY]: ${sql}`) + } + + logQueryError( + error: string | Error, + query: string, + parameters?: any[], + _queryRunner?: QueryRunner, + ) { + if (!this.isEnable('error')) + return + + const sql + = query + + (parameters && parameters.length + ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` + : '') + + this.logger.error([`[FAILED QUERY]: ${sql}`, `[QUERY ERROR]: ${error}`]) + } + + logQuerySlow( + time: number, + query: string, + parameters?: any[], + _queryRunner?: QueryRunner, + ) { + const sql + = query + + (parameters && parameters.length + ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` + : '') + + this.logger.warn(`[SLOW QUERY: ${time} ms]: ${sql}`) + } + + logSchemaBuild(message: string, _queryRunner?: QueryRunner) { + if (!this.isEnable('schema')) + return + + this.logger.log(message) + } + + logMigration(message: string, _queryRunner?: QueryRunner) { + if (!this.isEnable('migration')) + return + + this.logger.log(message) + } + + log( + level: 'warn' | 'info' | 'log', + message: any, + _queryRunner?: QueryRunner, + ) { + if (!this.isEnable(level)) + return + + switch (level) { + case 'log': + this.logger.debug(message) + break + case 'info': + this.logger.log(message) + break + case 'warn': + this.logger.warn(message) + break + default: + break + } + } + + /** + * Converts parameters to a string. + * Sometimes parameters can have circular objects and therefor we are handle this case too. + */ + private stringifyParams(parameters: any[]) { + try { + return JSON.stringify(parameters) + } + catch (error) { + // most probably circular objects in parameters + return parameters + } + } + + /** + * check enbale log + */ + private isEnable( + level: 'query' | 'schema' | 'error' | 'warn' | 'info' | 'log' | 'migration', + ): boolean { + return ( + this.options === 'all' + || this.options === true + || (Array.isArray(this.options) && this.options.includes(level)) + ) + } +} diff --git a/src/shared/helper/cron.service.ts b/src/shared/helper/cron.service.ts new file mode 100644 index 0000000..94d031b --- /dev/null +++ b/src/shared/helper/cron.service.ts @@ -0,0 +1,48 @@ +import { Injectable, Logger } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { CronExpression } from '@nestjs/schedule' +import dayjs from 'dayjs' + +import { LessThan } from 'typeorm' + +import { CronOnce } from '~/common/decorators/cron-once.decorator' +import { ConfigKeyPaths } from '~/config' +import { AccessTokenEntity } from '~/modules/auth/entities/access-token.entity' + +@Injectable() +export class CronService { + private logger: Logger = new Logger(CronService.name) + constructor( + private readonly configService: ConfigService, + ) {} + + @CronOnce(CronExpression.EVERY_DAY_AT_MIDNIGHT) + async deleteExpiredJWT() { + this.logger.log('--> 开始扫表,清除过期的 token') + + const expiredTokens = await AccessTokenEntity.find({ + where: { + expired_at: LessThan(new Date()), + }, + }) + + let deleteCount = 0 + await Promise.all( + expiredTokens.map(async (token) => { + const { value, created_at } = token + + await AccessTokenEntity.remove(token) + + this.logger.debug( + `--> 删除过期的 token:${value}, 签发于 ${dayjs(created_at).format( + 'YYYY-MM-DD H:mm:ss', + )}`, + ) + + deleteCount += 1 + }), + ) + + this.logger.log(`--> 删除了 ${deleteCount} 个过期的 token`) + } +} diff --git a/src/shared/helper/helper.module.ts b/src/shared/helper/helper.module.ts new file mode 100644 index 0000000..1261c7b --- /dev/null +++ b/src/shared/helper/helper.module.ts @@ -0,0 +1,17 @@ +import { Global, Module, type Provider } from '@nestjs/common' + +import { CronService } from './cron.service' +import { QQService } from './qq.service' + +const providers: Provider[] = [ + CronService, + QQService, +] + +@Global() +@Module({ + imports: [], + providers, + exports: providers, +}) +export class HelperModule {} diff --git a/src/shared/helper/qq.service.ts b/src/shared/helper/qq.service.ts new file mode 100644 index 0000000..9c5f788 --- /dev/null +++ b/src/shared/helper/qq.service.ts @@ -0,0 +1,19 @@ +import { HttpService } from '@nestjs/axios' +import { Injectable } from '@nestjs/common' + +@Injectable() +export class QQService { + constructor(private readonly http: HttpService) {} + + async getNickname(qq: string | number) { + const { data } = await this.http.axiosRef.get( + `https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=${qq}`, + ) + return data + } + + async getAvater(qq: string | number) { + // https://thirdqq.qlogo.cn/headimg_dl?dst_uin=1743369777&spec=640&img_type=jpg + return `https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=${qq}` + } +} diff --git a/src/shared/logger/logger.module.ts b/src/shared/logger/logger.module.ts new file mode 100644 index 0000000..3757496 --- /dev/null +++ b/src/shared/logger/logger.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common' + +import { LoggerService } from './logger.service' + +@Module({}) +export class LoggerModule { + static forRoot() { + return { + global: true, + module: LoggerModule, + providers: [LoggerService], + exports: [LoggerService], + } + } +} diff --git a/src/shared/logger/logger.service.ts b/src/shared/logger/logger.service.ts new file mode 100644 index 0000000..f2856e1 --- /dev/null +++ b/src/shared/logger/logger.service.ts @@ -0,0 +1,115 @@ +import { ConsoleLogger, ConsoleLoggerOptions, Injectable } from '@nestjs/common' + +import { ConfigService } from '@nestjs/config' +import type { Logger as WinstonLogger } from 'winston' + +import { config, createLogger, format, transports } from 'winston' + +import 'winston-daily-rotate-file' + +import { ConfigKeyPaths } from '~/config' + +export enum LogLevel { + ERROR = 'error', + WARN = 'warn', + INFO = 'info', + DEBUG = 'debug', + VERBOSE = 'verbose', +} + +@Injectable() +export class LoggerService extends ConsoleLogger { + private winstonLogger: WinstonLogger + + constructor( + context: string, + options: ConsoleLoggerOptions, + private configService: ConfigService, + ) { + super(context, options) + this.initWinston() + } + + protected get level(): LogLevel { + return this.configService.get('app.logger.level', { infer: true }) as LogLevel + } + + protected get maxFiles(): number { + return this.configService.get('app.logger.maxFiles', { infer: true }) + } + + protected initWinston(): void { + this.winstonLogger = createLogger({ + levels: config.npm.levels, + format: format.combine( + format.errors({ stack: true }), + format.timestamp(), + format.json(), + ), + transports: [ + new transports.DailyRotateFile({ + level: this.level, + filename: 'logs/app.%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxFiles: this.maxFiles, + format: format.combine(format.timestamp(), format.json()), + auditFile: 'logs/.audit/app.json', + }), + new transports.DailyRotateFile({ + level: LogLevel.ERROR, + filename: 'logs/app-error.%DATE%.log', + datePattern: 'YYYY-MM-DD', + maxFiles: this.maxFiles, + format: format.combine(format.timestamp(), format.json()), + auditFile: 'logs/.audit/app-error.json', + }), + ], + }) + + // if (isDev) { + // this.winstonLogger.add( + // new transports.Console({ + // level: this.level, + // format: format.combine( + // format.simple(), + // format.colorize({ all: true }), + // ), + // }), + // ); + // } + } + + verbose(message: any, context?: string): void { + super.verbose.apply(this, [message, context]) + + this.winstonLogger.log(LogLevel.VERBOSE, message, { context }) + } + + debug(message: any, context?: string): void { + super.debug.apply(this, [message, context]) + + this.winstonLogger.log(LogLevel.DEBUG, message, { context }) + } + + log(message: any, context?: string): void { + super.log.apply(this, [message, context]) + + this.winstonLogger.log(LogLevel.INFO, message, { context }) + } + + warn(message: any, context?: string): void { + super.warn.apply(this, [message, context]) + + this.winstonLogger.log(LogLevel.WARN, message) + } + + error(message: any, stack?: string, context?: string): void { + super.error.apply(this, [message, stack, context]) + + const hasStack = !!context + this.winstonLogger.log(LogLevel.ERROR, { + context: hasStack ? context : stack, + message: hasStack ? new Error(message) : message, + }) + } +} diff --git a/src/shared/mailer/mailer.module.ts b/src/shared/mailer/mailer.module.ts new file mode 100644 index 0000000..9ee4128 --- /dev/null +++ b/src/shared/mailer/mailer.module.ts @@ -0,0 +1,42 @@ +import { join } from 'node:path' + +import { Module, Provider } from '@nestjs/common' +import { ConfigModule, ConfigService } from '@nestjs/config' +import { MailerModule as NestMailerModule } from '@nestjs-modules/mailer' +import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter' + +import { ConfigKeyPaths, IAppConfig, IMailerConfig } from '~/config' + +import { MailerService } from './mailer.service' + +const providers: Provider[] = [ + MailerService, +] + +@Module({ + imports: [ + NestMailerModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + transport: configService.get('mailer'), + defaults: { + from: { + name: configService.get('app').name, + address: configService.get('mailer').auth.user, + }, + }, + template: { + dir: join(__dirname, '..', '..', '/assets/templates'), + adapter: new HandlebarsAdapter(), + options: { + strict: true, + }, + }, + }), + inject: [ConfigService], + }), + ], + providers, + exports: providers, +}) +export class MailerModule {} diff --git a/src/shared/mailer/mailer.service.ts b/src/shared/mailer/mailer.service.ts new file mode 100644 index 0000000..16aba4a --- /dev/null +++ b/src/shared/mailer/mailer.service.ts @@ -0,0 +1,151 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Inject, Injectable } from '@nestjs/common' + +import { MailerService as NestMailerService } from '@nestjs-modules/mailer' +import dayjs from 'dayjs' + +import Redis from 'ioredis' + +import { BusinessException } from '~/common/exceptions/biz.exception' +import { AppConfig, IAppConfig } from '~/config' +import { ErrorEnum } from '~/constants/error-code.constant' +import { randomValue } from '~/utils' + +@Injectable() +export class MailerService { + constructor( + @Inject(AppConfig.KEY) private appConfig: IAppConfig, + @InjectRedis() private redis: Redis, + private mailerService: NestMailerService, + ) {} + + async log(to: string, code: string, ip: string) { + const getRemainTime = () => { + const now = dayjs() + return now.endOf('day').diff(now, 'second') + } + + await this.redis.set(`captcha:${to}`, code, 'EX', 60 * 5) + + const limitCountOfDay = await this.redis.get(`captcha:${to}:limit-day`) + const ipLimitCountOfDay = await this.redis.get(`ip:${ip}:send:limit-day`) + + await this.redis.set(`ip:${ip}:send:limit`, 1, 'EX', 60) + await this.redis.set(`captcha:${to}:limit`, 1, 'EX', 60) + await this.redis.set( + `captcha:${to}:send:limit-count-day`, + limitCountOfDay, + 'EX', + getRemainTime(), + ) + await this.redis.set( + `ip:${ip}:send:limit-count-day`, + ipLimitCountOfDay, + 'EX', + getRemainTime(), + ) + } + + async checkCode(to, code) { + const ret = await this.redis.get(`captcha:${to}`) + if (ret !== code) + throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE) + + await this.redis.del(`captcha:${to}`) + } + + async checkLimit(to, ip) { + const LIMIT_TIME = 5 + + // ip限制 + const ipLimit = await this.redis.get(`ip:${ip}:send:limit`) + if (ipLimit) + throw new BusinessException(ErrorEnum.TOO_MANY_REQUESTS) + + // 1分钟最多接收1条 + const limit = await this.redis.get(`captcha:${to}:limit`) + if (limit) + throw new BusinessException(ErrorEnum.TOO_MANY_REQUESTS) + + // 1天一个邮箱最多接收5条 + let limitCountOfDay: string | number = await this.redis.get( + `captcha:${to}:limit-day`, + ) + limitCountOfDay = limitCountOfDay ? Number(limitCountOfDay) : 0 + if (limitCountOfDay > LIMIT_TIME) { + throw new BusinessException( + ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY, + ) + } + + // 1天一个ip最多发送5条 + let ipLimitCountOfDay: string | number = await this.redis.get( + `ip:${ip}:send:limit-day`, + ) + ipLimitCountOfDay = ipLimitCountOfDay ? Number(ipLimitCountOfDay) : 0 + if (ipLimitCountOfDay > LIMIT_TIME) { + throw new BusinessException( + ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY, + ) + } + } + + async send( + to, + subject, + content: string, + type: 'text' | 'html' = 'text', + ): Promise { + if (type === 'text') { + return this.mailerService.sendMail({ + to, + subject, + text: content, + }) + } + else { + return this.mailerService.sendMail({ + to, + subject, + html: content, + }) + } + } + + async sendVerificationCode(to, code = randomValue(4, '1234567890')) { + const subject = `[${this.appConfig.name}] 验证码` + + try { + await this.mailerService.sendMail({ + to, + subject, + template: './verification-code-zh', + context: { + code, + }, + }) + } + catch (error) { + console.log(error) + throw new BusinessException(ErrorEnum.VERIFICATION_CODE_SEND_FAILED) + } + + return { + to, + code, + } + } + + // async sendUserConfirmation(user: UserEntity, token: string) { + // const url = `example.com/auth/confirm?token=${token}` + // await this.mailerService.sendMail({ + // to: user.email, + // subject: 'Confirm your Email', + // template: './confirmation', + // context: { + // name: user.name, + // url, + // }, + // }) + // } +} diff --git a/src/shared/redis/cache.service.ts b/src/shared/redis/cache.service.ts new file mode 100644 index 0000000..90c8707 --- /dev/null +++ b/src/shared/redis/cache.service.ts @@ -0,0 +1,68 @@ +import { CACHE_MANAGER } from '@nestjs/cache-manager' +import { Inject, Injectable } from '@nestjs/common' +import { Emitter } from '@socket.io/redis-emitter' +import { Cache } from 'cache-manager' +import type { Redis } from 'ioredis' + +import { RedisIoAdapterKey } from '~/common/adapters/socket.adapter' + +import { API_CACHE_PREFIX } from '~/constants/cache.constant' +import { getRedisKey } from '~/utils/redis.util' + +// 获取器 +export type TCacheKey = string +export type TCacheResult = Promise + +@Injectable() +export class CacheService { + private cache!: Cache + + private ioRedis!: Redis + constructor(@Inject(CACHE_MANAGER) cache: Cache) { + this.cache = cache + } + + private get redisClient(): Redis { + // eslint-disable-next-line ts/ban-ts-comment + // @ts-expect-error + return this.cache.store.client + } + + public get(key: TCacheKey): TCacheResult { + return this.cache.get(key) + } + + public set(key: TCacheKey, value: any, milliseconds: number) { + return this.cache.set(key, value, milliseconds) + } + + public getClient() { + return this.redisClient + } + + private _emitter: Emitter + + public get emitter(): Emitter { + if (this._emitter) + return this._emitter + + this._emitter = new Emitter(this.redisClient, { + key: RedisIoAdapterKey, + }) + + return this._emitter + } + + public async cleanCatch() { + const redis = this.getClient() + const keys: string[] = await redis.keys(`${API_CACHE_PREFIX}*`) + await Promise.all(keys.map(key => redis.del(key))) + } + + public async cleanAllRedisKey() { + const redis = this.getClient() + const keys: string[] = await redis.keys(getRedisKey('*')) + + await Promise.all(keys.map(key => redis.del(key))) + } +} diff --git a/src/shared/redis/redis-subpub.ts b/src/shared/redis/redis-subpub.ts new file mode 100644 index 0000000..7634753 --- /dev/null +++ b/src/shared/redis/redis-subpub.ts @@ -0,0 +1,68 @@ +import { Logger } from '@nestjs/common' +import IORedis from 'ioredis' +import type { Redis, RedisOptions } from 'ioredis' + +export class RedisSubPub { + public pubClient: Redis + public subClient: Redis + constructor( + private redisConfig: RedisOptions, + private channelPrefix: string = 'm-shop-channel#', + ) { + this.init() + } + + public init() { + const redisOptions: RedisOptions = { + host: this.redisConfig.host, + port: this.redisConfig.port, + } + + if (this.redisConfig.password) + redisOptions.password = this.redisConfig.password + + const pubClient = new IORedis(redisOptions) + const subClient = pubClient.duplicate() + this.pubClient = pubClient + this.subClient = subClient + } + + public async publish(event: string, data: any) { + const channel = this.channelPrefix + event + const _data = JSON.stringify(data) + if (event !== 'log') + Logger.debug(`发布事件:${channel} <- ${_data}`, RedisSubPub.name) + + await this.pubClient.publish(channel, _data) + } + + private ctc = new WeakMap void>() + + public async subscribe(event: string, callback: (data: any) => void) { + const myChannel = this.channelPrefix + event + this.subClient.subscribe(myChannel) + + const cb = (channel, message) => { + if (channel === myChannel) { + if (event !== 'log') + Logger.debug(`接收事件:${channel} -> ${message}`, RedisSubPub.name) + + callback(JSON.parse(message)) + } + } + + this.ctc.set(callback, cb) + this.subClient.on('message', cb) + } + + public async unsubscribe(event: string, callback: (data: any) => void) { + const channel = this.channelPrefix + event + this.subClient.unsubscribe(channel) + const cb = this.ctc.get(callback) + if (cb) { + this.subClient.off('message', cb) + + this.ctc.delete(callback) + } + } +} diff --git a/src/shared/redis/redis.constant.ts b/src/shared/redis/redis.constant.ts new file mode 100644 index 0000000..9b82a3b --- /dev/null +++ b/src/shared/redis/redis.constant.ts @@ -0,0 +1 @@ +export const REDIS_PUBSUB = Symbol('REDIS_PUBSUB') diff --git a/src/shared/redis/redis.module.ts b/src/shared/redis/redis.module.ts new file mode 100644 index 0000000..5c19851 --- /dev/null +++ b/src/shared/redis/redis.module.ts @@ -0,0 +1,60 @@ +import { RedisModule as NestRedisModule } from '@liaoliaots/nestjs-redis' +import { CacheModule } from '@nestjs/cache-manager' +import { Global, Module, Provider } from '@nestjs/common' +import { ConfigModule, ConfigService } from '@nestjs/config' + +import { redisStore } from 'cache-manager-ioredis-yet' +import { RedisOptions } from 'ioredis' + +import { ConfigKeyPaths, IRedisConfig } from '~/config' + +import { CacheService } from './cache.service' +import { RedisSubPub } from './redis-subpub' +import { REDIS_PUBSUB } from './redis.constant' +import { RedisPubSubService } from './subpub.service' + +const providers: Provider[] = [ + CacheService, + { + provide: REDIS_PUBSUB, + useFactory: (configService: ConfigService) => { + const redisOptions: RedisOptions = configService.get('redis') + return new RedisSubPub(redisOptions) + }, + inject: [ConfigService], + }, + RedisPubSubService, +] + +@Global() +@Module({ + imports: [ + // cache + CacheModule.registerAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + const redisOptions: RedisOptions = configService.get('redis') + + return { + isGlobal: true, + store: redisStore, + isCacheableValue: () => true, + ...redisOptions, + } + }, + inject: [ConfigService], + }), + // redis + NestRedisModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + readyLog: true, + config: configService.get('redis'), + }), + inject: [ConfigService], + }), + ], + providers, + exports: [...providers, CacheModule], +}) +export class RedisModule {} diff --git a/src/shared/redis/subpub.service.ts b/src/shared/redis/subpub.service.ts new file mode 100644 index 0000000..2ba347f --- /dev/null +++ b/src/shared/redis/subpub.service.ts @@ -0,0 +1,21 @@ +import { Inject, Injectable } from '@nestjs/common' + +import { RedisSubPub } from './redis-subpub' +import { REDIS_PUBSUB } from './redis.constant' + +@Injectable() +export class RedisPubSubService { + constructor(@Inject(REDIS_PUBSUB) private readonly redisSubPub: RedisSubPub) {} + + public async publish(event: string, data: any) { + return this.redisSubPub.publish(event, data) + } + + public async subscribe(event: string, callback: (data: any) => void) { + return this.redisSubPub.subscribe(event, callback) + } + + public async unsubscribe(event: string, callback: (data: any) => void) { + return this.redisSubPub.unsubscribe(event, callback) + } +} diff --git a/src/shared/shared.module.ts b/src/shared/shared.module.ts new file mode 100644 index 0000000..59cf92e --- /dev/null +++ b/src/shared/shared.module.ts @@ -0,0 +1,49 @@ +import { HttpModule } from '@nestjs/axios' +import { Global, Module } from '@nestjs/common' +import { EventEmitterModule } from '@nestjs/event-emitter' +import { ScheduleModule } from '@nestjs/schedule' +import { ThrottlerModule } from '@nestjs/throttler' + +import { isDev } from '~/global/env' + +import { HelperModule } from './helper/helper.module' +import { LoggerModule } from './logger/logger.module' +import { MailerModule } from './mailer/mailer.module' + +import { RedisModule } from './redis/redis.module' + +@Global() +@Module({ + imports: [ + // logger + LoggerModule.forRoot(), + // http + HttpModule, + // schedule + ScheduleModule.forRoot(), + // rate limit + ThrottlerModule.forRoot([ + { + limit: 3, + ttl: 60000, + }, + ]), + EventEmitterModule.forRoot({ + wildcard: true, + delimiter: '.', + newListener: false, + removeListener: false, + maxListeners: 20, + verboseMemoryLeak: isDev, + ignoreErrors: false, + }), + // redis + RedisModule, + // mailer + MailerModule, + // helper + HelperModule, + ], + exports: [HttpModule, MailerModule, RedisModule, HelperModule], +}) +export class SharedModule {} diff --git a/src/socket/base.gateway.ts b/src/socket/base.gateway.ts new file mode 100644 index 0000000..a664892 --- /dev/null +++ b/src/socket/base.gateway.ts @@ -0,0 +1,33 @@ +import type { Socket } from 'socket.io' + +import { BusinessEvents } from './business-event.constant' + +export abstract class BaseGateway { + public gatewayMessageFormat( + type: BusinessEvents, + message: any, + code?: number, + ) { + return { + type, + data: message, + code, + } + } + + handleDisconnect(client: Socket) { + client.send( + this.gatewayMessageFormat(BusinessEvents.GATEWAY_CONNECT, 'WebSocket 断开'), + ) + } + + handleConnect(client: Socket) { + client.send( + this.gatewayMessageFormat(BusinessEvents.GATEWAY_CONNECT, 'WebSocket 已连接'), + ) + } +} + +export abstract class BroadcastBaseGateway extends BaseGateway { + broadcast(event: BusinessEvents, data: any) {} +} diff --git a/src/socket/business-event.constant.ts b/src/socket/business-event.constant.ts new file mode 100644 index 0000000..1864d6c --- /dev/null +++ b/src/socket/business-event.constant.ts @@ -0,0 +1,11 @@ +export enum BusinessEvents { + GATEWAY_CONNECT = 'GATEWAY_CONNECT', + GATEWAY_DISCONNECT = 'GATEWAY_DISCONNECT', + + AUTH_FAILED = 'AUTH_FAILED', + + // 用户上线事件 + USER_ONLINE = 'USER_ONLINE', + USER_OFFLINE = 'USER_OFFLINE', + USER_KICK = 'USER_KICK', +} diff --git a/src/socket/events/admin.gateway.ts b/src/socket/events/admin.gateway.ts new file mode 100644 index 0000000..60f930a --- /dev/null +++ b/src/socket/events/admin.gateway.ts @@ -0,0 +1,37 @@ +import { JwtService } from '@nestjs/jwt' +import { + GatewayMetadata, + OnGatewayConnection, + OnGatewayDisconnect, + WebSocketGateway, + WebSocketServer, +} from '@nestjs/websockets' + +import { Server } from 'socket.io' + +import { AuthService } from '~/modules/auth/auth.service' +import { CacheService } from '~/shared/redis/cache.service' + +import { createAuthGateway } from '../shared/auth.gateway' + +const AuthGateway = createAuthGateway({ namespace: 'admin' }) + +@WebSocketGateway({ namespace: 'admin' }) +export class AdminEventsGateway + extends AuthGateway + implements OnGatewayConnection, OnGatewayDisconnect { + constructor( + protected readonly jwtService: JwtService, + protected readonly authService: AuthService, + private readonly cacheService: CacheService, + ) { + super(jwtService, authService, cacheService) + } + + @WebSocketServer() + protected _server: Server + + get server() { + return this._server + } +} diff --git a/src/socket/events/web.gateway.ts b/src/socket/events/web.gateway.ts new file mode 100644 index 0000000..94b83a3 --- /dev/null +++ b/src/socket/events/web.gateway.ts @@ -0,0 +1,36 @@ +import { JwtService } from '@nestjs/jwt' +import { + GatewayMetadata, + OnGatewayConnection, + OnGatewayDisconnect, + WebSocketGateway, + WebSocketServer, +} from '@nestjs/websockets' + +import { Server } from 'socket.io' + +import { TokenService } from '~/modules/auth/services/token.service' +import { CacheService } from '~/shared/redis/cache.service' + +import { createAuthGateway } from '../shared/auth.gateway' + +const AuthGateway = createAuthGateway({ namespace: 'web' }) +@WebSocketGateway({ namespace: 'web' }) +export class WebEventsGateway + extends AuthGateway + implements OnGatewayConnection, OnGatewayDisconnect { + constructor( + protected readonly jwtService: JwtService, + protected readonly tokenService: TokenService, + private readonly cacheService: CacheService, + ) { + super(jwtService, tokenService, cacheService) + } + + @WebSocketServer() + protected _server: Server + + get server() { + return this._server + } +} diff --git a/src/socket/shared/auth.gateway.ts b/src/socket/shared/auth.gateway.ts new file mode 100644 index 0000000..8285338 --- /dev/null +++ b/src/socket/shared/auth.gateway.ts @@ -0,0 +1,120 @@ +import { } from '@nestjs/common' +import { OnEvent } from '@nestjs/event-emitter' +import { JwtService } from '@nestjs/jwt' +import type { + OnGatewayConnection, + OnGatewayDisconnect, +} from '@nestjs/websockets' +import { WebSocketServer } from '@nestjs/websockets' +import { Namespace } from 'socket.io' +import type { Socket } from 'socket.io' + +import { EventBusEvents } from '~/constants/event-bus.constant' + +import { TokenService } from '~/modules/auth/services/token.service' +import { CacheService } from '~/shared/redis/cache.service' + +import { BroadcastBaseGateway } from '../base.gateway' +import { BusinessEvents } from '../business-event.constant' + +export interface AuthGatewayOptions { + namespace: string +} + +// eslint-disable-next-line ts/ban-ts-comment +// @ts-expect-error +export interface IAuthGateway extends OnGatewayConnection, OnGatewayDisconnect, BroadcastBaseGateway {} + +export function createAuthGateway(options: AuthGatewayOptions): new (...args: any[]) => IAuthGateway { + const { namespace } = options + + class AuthGateway extends BroadcastBaseGateway implements IAuthGateway { + constructor( + protected readonly jwtService: JwtService, + protected readonly tokenService: TokenService, + private readonly cacheService: CacheService, + ) { + super() + } + + @WebSocketServer() + protected namespace: Namespace + + async authFailed(client: Socket) { + client.send( + this.gatewayMessageFormat(BusinessEvents.AUTH_FAILED, '认证失败'), + ) + client.disconnect() + } + + async authToken(token: string): Promise { + if (typeof token !== 'string') + return false + + const validJwt = async () => { + try { + const ok = await this.jwtService.verify(token) + + if (!ok) + return false + } + catch { + return false + } + // is not crash, is verify + return true + } + + return await validJwt() + } + + async handleConnection(client: Socket) { + const token + = client.handshake.query.token + || client.handshake.headers.authorization + || client.handshake.headers.Authorization + if (!token) + return this.authFailed(client) + + if (!(await this.authToken(token as string))) + return this.authFailed(client) + + super.handleConnect(client) + + const sid = client.id + this.tokenSocketIdMap.set(token.toString(), sid) + } + + handleDisconnect(client: Socket) { + super.handleDisconnect(client) + } + + tokenSocketIdMap = new Map() + + @OnEvent(EventBusEvents.TokenExpired) + handleTokenExpired(token: string) { + // consola.debug(`token expired: ${token}`) + + const server = this.namespace.server + const sid = this.tokenSocketIdMap.get(token) + if (!sid) + return false + + const socket = server.of(`/${namespace}`).sockets.get(sid) + if (socket) { + socket.disconnect() + super.handleDisconnect(socket) + return true + } + return false + } + + override broadcast(event: BusinessEvents, data: any) { + this.cacheService.emitter + .of(`/${namespace}`) + .emit('message', this.gatewayMessageFormat(event, data)) + } + } + + return AuthGateway +} diff --git a/src/socket/socket.module.ts b/src/socket/socket.module.ts new file mode 100644 index 0000000..a1a1de8 --- /dev/null +++ b/src/socket/socket.module.ts @@ -0,0 +1,16 @@ +import { Module, Provider, forwardRef } from '@nestjs/common' + +import { AuthModule } from '../modules/auth/auth.module' +import { SystemModule } from '../modules/system/system.module' + +import { AdminEventsGateway } from './events/admin.gateway' +import { WebEventsGateway } from './events/web.gateway' + +const providers: Provider[] = [AdminEventsGateway, WebEventsGateway] + +@Module({ + imports: [forwardRef(() => SystemModule), AuthModule], + providers, + exports: [...providers], +}) +export class SocketModule {} diff --git a/src/utils/captcha.util.ts b/src/utils/captcha.util.ts new file mode 100644 index 0000000..4f416bc --- /dev/null +++ b/src/utils/captcha.util.ts @@ -0,0 +1,19 @@ +import svgCaptcha from 'svg-captcha' + +export function createCaptcha() { + return svgCaptcha.createMathExpr({ + size: 4, + ignoreChars: '0o1iIl', + noise: 2, + color: true, + background: '#eee', + fontSize: 50, + width: 110, + height: 38, + }) +} + +export function createMathExpr() { + const options = {} + return svgCaptcha.createMathExpr(options) +} diff --git a/src/utils/crypto.util.ts b/src/utils/crypto.util.ts new file mode 100644 index 0000000..143938b --- /dev/null +++ b/src/utils/crypto.util.ts @@ -0,0 +1,30 @@ +import CryptoJS from 'crypto-js' + +const key = CryptoJS.enc.Utf8.parse('buqiyuanabcdefe9bc') +const iv = CryptoJS.enc.Utf8.parse('0123456789buqiyuan') + +export function aesEncrypt(data) { + if (!data) + return data + const enc = CryptoJS.AES.encrypt(data, key, { + iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }) + return enc.toString() +} + +export function aesDecrypt(data) { + if (!data) + return data + const dec = CryptoJS.AES.decrypt(data, key, { + iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }) + return dec.toString(CryptoJS.enc.Utf8) +} + +export function md5(str: string) { + return CryptoJS.MD5(str).toString() +} diff --git a/src/utils/date.util.ts b/src/utils/date.util.ts new file mode 100644 index 0000000..54a15d1 --- /dev/null +++ b/src/utils/date.util.ts @@ -0,0 +1,23 @@ +import dayjs from 'dayjs' +import { isDate } from 'lodash' + +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' +const DATE_FORMAT = 'YYYY-MM-DD' + +export function formatToDateTime( + date: string | number | Date | dayjs.Dayjs | null | undefined = undefined, + format = DATE_TIME_FORMAT, +): string { + return dayjs(date).format(format) +} + +export function formatToDate( + date: string | number | Date | dayjs.Dayjs | null | undefined = undefined, + format = DATE_FORMAT, +): string { + return dayjs(date).format(format) +} + +export function isDateObject(obj: unknown): boolean { + return isDate(obj) || dayjs.isDayjs(obj) +} diff --git a/src/utils/file.util.ts b/src/utils/file.util.ts new file mode 100644 index 0000000..6d6ba48 --- /dev/null +++ b/src/utils/file.util.ts @@ -0,0 +1,89 @@ +import fs from 'node:fs' +import path from 'node:path' + +import { MultipartFile } from '@fastify/multipart' + +import dayjs from 'dayjs' + +enum Type { + IMAGE = '图片', + TXT = '文档', + MUSIC = '音乐', + VIDEO = '视频', + OTHER = '其他', +} + +export function getFileType(extName: string) { + const documents = 'txt doc pdf ppt pps xlsx xls docx' + const music = 'mp3 wav wma mpa ram ra aac aif m4a' + const video = 'avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg' + const image + = 'bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg' + if (image.includes(extName)) + return Type.IMAGE + + if (documents.includes(extName)) + return Type.TXT + + if (music.includes(extName)) + return Type.MUSIC + + if (video.includes(extName)) + return Type.VIDEO + + return Type.OTHER +} + +export function getName(fileName: string) { + if (fileName.includes('.')) + return fileName.split('.')[0] + + return fileName +} + +export function getExtname(fileName: string) { + return path.extname(fileName).replace('.', '') +} + +export function getSize(bytes: number, decimals = 2) { + if (bytes === 0) + return '0 Bytes' + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}` +} + +export function fileRename(fileName: string) { + const name = fileName.split('.')[0] + const extName = path.extname(fileName) + const time = dayjs().format('YYYYMMDDHHmmSSS') + return `${name}-${time}${extName}` +} + +export function getFilePath(name: string) { + return `/upload/${name}` +} + +export function saveLocalFile(buffer: Buffer, name: string) { + const filePath = path.join(__dirname, '../../', 'public/upload', name) + const writeStream = fs.createWriteStream(filePath) + writeStream.write(buffer) +} + +export async function saveFile(file: MultipartFile, name: string) { + const filePath = path.join(__dirname, '../../', 'public/upload', name) + const writeStream = fs.createWriteStream(filePath) + const buffer = await file.toBuffer() + writeStream.write(buffer) +} + +export async function deleteFile(name: string) { + fs.unlink(path.join(__dirname, '../../', 'public', name), () => { + // console.log(error); + }) +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..b889238 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,11 @@ +export * from './captcha.util' +export * from './crypto.util' +export * from './date.util' +export * from './file.util' +export * from './ip.util' +export * from './is.util' +export * from './list2tree.util' +export * from './permission.util' +export * from './redis.util' +export * from './schedule.util' +export * from './tool.util' diff --git a/src/utils/ip.util.ts b/src/utils/ip.util.ts new file mode 100644 index 0000000..8c6ce06 --- /dev/null +++ b/src/utils/ip.util.ts @@ -0,0 +1,66 @@ +/** + * @module utils/ip + * @description IP utility functions + */ +import type { IncomingMessage } from 'node:http' + +import axios from 'axios' +import type { FastifyRequest } from 'fastify' + +/* 判断IP是不是内网 */ +function isLAN(ip: string) { + ip.toLowerCase() + if (ip === 'localhost') + return true + let a_ip = 0 + if (ip === '') + return false + const aNum = ip.split('.') + if (aNum.length !== 4) + return false + a_ip += Number.parseInt(aNum[0]) << 24 + a_ip += Number.parseInt(aNum[1]) << 16 + a_ip += Number.parseInt(aNum[2]) << 8 + a_ip += Number.parseInt(aNum[3]) << 0 + a_ip = (a_ip >> 16) & 0xFFFF + return ( + a_ip >> 8 === 0x7F + || a_ip >> 8 === 0xA + || a_ip === 0xC0A8 + || (a_ip >= 0xAC10 && a_ip <= 0xAC1F) + ) +} + +export function getIp(request: FastifyRequest | IncomingMessage) { + const req = request as any + + let ip: string + = request.headers['x-forwarded-for'] + || request.headers['X-Forwarded-For'] + || request.headers['X-Real-IP'] + || request.headers['x-real-ip'] + || req?.ip + || req?.raw?.connection?.remoteAddress + || req?.raw?.socket?.remoteAddress + || undefined + if (ip && ip.split(',').length > 0) + ip = ip.split(',')[0] + + return ip +} + +export async function getIpAddress(ip: string) { + if (isLAN(ip)) + return '内网IP' + try { + let { data } = await axios.get( + `https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`, + { responseType: 'arraybuffer' }, + ) + data = new TextDecoder('gbk').decode(data) + data = JSON.parse(data) + return data.addr.trim().split(' ').at(0) + } catch (error) { + return '第三方接口请求失败' + } +} diff --git a/src/utils/is.util.ts b/src/utils/is.util.ts new file mode 100644 index 0000000..ce9e048 --- /dev/null +++ b/src/utils/is.util.ts @@ -0,0 +1,4 @@ +export function isExternal(path: string): boolean { + const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/ + return reg.test(path) +} diff --git a/src/utils/list2tree.util.ts b/src/utils/list2tree.util.ts new file mode 100644 index 0000000..663a9f4 --- /dev/null +++ b/src/utils/list2tree.util.ts @@ -0,0 +1,86 @@ +export type TreeNode = T & { + id: number + parentId: number + children?: TreeNode[] +} + +export type ListNode = T & { + id: number + parentId: number +} + +export function list2Tree( + items: T, + parentId: number | null = null, +): TreeNode[] { + return items + .filter(item => item.parentId === parentId) + .map((item) => { + const children = list2Tree(items, item.id) + return { + ...item, + ...(children.length ? { children } : null), + } + }) +} + +/** + * 过滤树,返回列表数据 + * @param treeData + * @param key 用于过滤的字段 + * @param value 用于过滤的值 + * @returns + */ +export function filterTree2List(treeData, key, value) { + const filterChildrenTree = (resTree, treeItem) => { + if (treeItem[key].includes(value)) { + resTree.push(treeItem) + return resTree + } + if (Array.isArray(treeItem.children)) { + const children = treeItem.children.reduce(filterChildrenTree, []) + + const data = { ...treeItem, children } + + if (children.length) + resTree.push({ ...data }) + } + return resTree + } + return treeData.reduce(filterChildrenTree, []) +} + +/** + * 过滤树,并保留原有的结构 + * @param treeData + * @param predicate + * @returns + */ +export function filterTree( + treeData: TreeNode[], + predicate: (data: T) => boolean, +): TreeNode[] { + function filter(treeData: TreeNode[]): TreeNode[] { + if (!treeData?.length) + return treeData + + return treeData.filter((data) => { + if (!predicate(data)) + return false + + data.children = filter(data.children) + return true + }) + } + + return filter(treeData) || [] +} + +export function deleteEmptyChildren(arr: any) { + arr?.forEach((node) => { + if (node.children?.length === 0) + delete node.children + else + deleteEmptyChildren(node.children) + }) +} diff --git a/src/utils/permission.util.ts b/src/utils/permission.util.ts new file mode 100644 index 0000000..6aa34ce --- /dev/null +++ b/src/utils/permission.util.ts @@ -0,0 +1,158 @@ +import { ForbiddenException } from '@nestjs/common' + +import { envBoolean } from '~/global/env' +import { MenuEntity } from '~/modules/system/menu/menu.entity' +import { isExternal } from '~/utils/is.util' + +function createRoute(menu: MenuEntity, _isRoot) { + const commonMeta = { + title: menu.name, + icon: menu.icon, + isExt: menu.isExt, + extOpenMode: menu.extOpenMode, + type: menu.type, + orderNo: menu.orderNo, + show: menu.show, + activeMenu: menu.activeMenu, + status: menu.status, + keepAlive: menu.keepAlive, + } + + if (isExternal(menu.path)) { + return { + id: menu.id, + path: menu.path, + // component: 'IFrame', + name: menu.name, + meta: { ...commonMeta }, + } + } + + // 目录 + if (menu.type === 0) { + return { + id: menu.id, + path: menu.path, + component: menu.component, + name: menu.name, + meta: { ...commonMeta }, + } + } + + return { + id: menu.id, + path: menu.path, + name: menu.name, + component: menu.component, + meta: { + ...commonMeta, + }, + } +} + +function filterAsyncRoutes(menus: MenuEntity[], parentRoute) { + const res = [] + + menus.forEach((menu) => { + if (menu.type === 2 || !menu.status) { + // 如果是权限或禁用直接跳过 + return + } + // 根级别菜单渲染 + let realRoute + if (!parentRoute && !menu.parentId && menu.type === 1) { + // 根菜单 + realRoute = createRoute(menu, true) + } + else if (!parentRoute && !menu.parentId && menu.type === 0) { + // 目录 + const childRoutes = filterAsyncRoutes(menus, menu) + realRoute = createRoute(menu, true) + if (childRoutes && childRoutes.length > 0) { + realRoute.redirect = childRoutes[0].path + realRoute.children = childRoutes + } + } + else if ( + parentRoute + && parentRoute.id === menu.parentId + && menu.type === 1 + ) { + // 子菜单 + realRoute = createRoute(menu, false) + } + else if ( + parentRoute + && parentRoute.id === menu.parentId + && menu.type === 0 + ) { + // 如果还是目录,继续递归 + const childRoute = filterAsyncRoutes(menus, menu) + realRoute = createRoute(menu, false) + if (childRoute && childRoute.length > 0) { + realRoute.redirect = childRoute[0].path + realRoute.children = childRoute + } + } + // add curent route + if (realRoute) + res.push(realRoute) + }) + return res +} + +export function generatorRouters(menus: MenuEntity[]) { + return filterAsyncRoutes(menus, null) +} + +// 获取所有菜单以及权限 +function filterMenuToTable(menus: MenuEntity[], parentMenu) { + const res = [] + menus.forEach((menu) => { + // 根级别菜单渲染 + let realMenu + if (!parentMenu && !menu.parentId && menu.type === 1) { + // 根菜单,查找该跟菜单下子菜单,因为可能会包含权限 + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu + } + else if (!parentMenu && !menu.parentId && menu.type === 0) { + // 根目录 + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu + } + else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 1) { + // 子菜单下继续找是否有子菜单 + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu + } + else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 0) { + // 如果还是目录,继续递归 + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu + } + else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 2) { + realMenu = { ...menu } + } + // add curent route + if (realMenu) { + realMenu.pid = menu.id + res.push(realMenu) + } + }) + return res +} + +export function generatorMenu(menu: MenuEntity[]) { + return filterMenuToTable(menu, null) +} + +/** 检测是否为演示环境, 如果为演示环境,则拒绝该操作 */ +export function checkIsDemoMode() { + if (envBoolean('IS_DEMO')) + throw new ForbiddenException('演示模式下不允许操作') +} diff --git a/src/utils/redis.util.ts b/src/utils/redis.util.ts new file mode 100644 index 0000000..c09380c --- /dev/null +++ b/src/utils/redis.util.ts @@ -0,0 +1,10 @@ +import type { RedisKeys } from '~/constants/cache.constant' + +type Prefix = 'm-shop' +const prefix = 'm-shop' + +export function getRedisKey(key: T, ...concatKeys: string[]): `${Prefix}:${T}${string | ''}` { + return `${prefix}:${key}${ + concatKeys && concatKeys.length ? `:${concatKeys.join('_')}` : '' + }` +} diff --git a/src/utils/schedule.util.ts b/src/utils/schedule.util.ts new file mode 100644 index 0000000..184fede --- /dev/null +++ b/src/utils/schedule.util.ts @@ -0,0 +1,99 @@ +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) + +export function scheduleMicrotask(callback: () => void) { + sleep(0).then(callback) +} + +type NotifyCallback = () => void + +type NotifyFunction = (callback: () => void) => void + +type BatchNotifyFunction = (callback: () => void) => void + +export function createNotifyManager() { + let queue: NotifyCallback[] = [] + let transactions = 0 + let notifyFn: NotifyFunction = (callback) => { + callback() + } + let batchNotifyFn: BatchNotifyFunction = (callback: () => void) => { + callback() + } + + const flush = (): void => { + const originalQueue = queue + queue = [] + if (originalQueue.length) { + scheduleMicrotask(() => { + batchNotifyFn(() => { + originalQueue.forEach((callback) => { + notifyFn(callback) + }) + }) + }) + } + } + + const batch = (callback: () => T): T => { + let result + transactions++ + try { + result = callback() + } + finally { + transactions-- + if (!transactions) + flush() + } + return result + } + + const schedule = (callback: NotifyCallback): void => { + if (transactions) { + queue.push(callback) + } + else { + scheduleMicrotask(() => { + notifyFn(callback) + }) + } + } + + /** + * All calls to the wrapped function will be batched. + */ + const batchCalls = (callback: T): T => { + return ((...args: any[]) => { + schedule(() => { + callback(...args) + }) + }) as any + } + + /** + * Use this method to set a custom notify function. + * This can be used to for example wrap notifications with `React.act` while running tests. + */ + const setNotifyFunction = (fn: NotifyFunction) => { + notifyFn = fn + } + + /** + * Use this method to set a custom function to batch notifications together into a single tick. + * By default React Query will use the batch function provided by ReactDOM or React Native. + */ + const setBatchNotifyFunction = (fn: BatchNotifyFunction) => { + batchNotifyFn = fn + } + + return { + batch, + batchCalls, + schedule, + setNotifyFunction, + setBatchNotifyFunction, + } as const +} + +// SINGLETON +export const scheduleManager = createNotifyManager() diff --git a/src/utils/tool.util.ts b/src/utils/tool.util.ts new file mode 100644 index 0000000..9982cf5 --- /dev/null +++ b/src/utils/tool.util.ts @@ -0,0 +1,60 @@ +import { customAlphabet, nanoid } from 'nanoid' + +import { md5 } from './crypto.util' + +export function getAvatar(mail: string | undefined) { + if (!mail) + return '' + + return `https://cravatar.cn/avatar/${md5(mail)}?d=retro` +} + +export function generateUUID(size: number = 21): string { + return nanoid(size) +} + +export function generateShortUUID(): string { + return nanoid(10) +} + +/** + * 生成一个随机的值 + */ +export function generateRandomValue( + length: number, + placeholder = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM', +): string { + const customNanoid = customAlphabet(placeholder, length) + return customNanoid() +} + +/** + * 生成一个随机的值 + */ +export function randomValue( + size = 16, + dict = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict', +): string { + let id = '' + let i = size + const len = dict.length + while (i--) id += dict[(Math.random() * len) | 0] + return id +} + +export const hashString = function (str, seed = 0) { + let h1 = 0xDEADBEEF ^ seed + let h2 = 0x41C6CE57 ^ seed + for (let i = 0, ch; i < str.length; i++) { + ch = str.charCodeAt(i) + h1 = Math.imul(h1 ^ ch, 2654435761) + h2 = Math.imul(h2 ^ ch, 1597334677) + } + h1 + = Math.imul(h1 ^ (h1 >>> 16), 2246822507) + ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909) + h2 + = Math.imul(h2 ^ (h2 >>> 16), 2246822507) + ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909) + return 4294967296 * (2097151 & h2) + (h1 >>> 0) +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..e1fc515 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "scripts", "test", "dist", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c146e4a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "incremental": true, + "target": "ES2022", + "lib": ["ESNext"], + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "baseUrl": "./", + "module": "commonjs", + "paths": { + "~/*": ["src/*"] + }, + "strictBindCallApply": false, + "strictNullChecks": false, + "noFallthroughCasesInSwitch": false, + "noImplicitAny": false, + "declaration": true, + "outDir": "./dist", + "removeComments": true, + "sourceMap": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": false, + "skipLibCheck": true + }, + "exclude": ["node_modules", "scripts", "dist"] +} diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 0000000..2e5535e --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,21 @@ +declare global { + interface IAuthUser { + uid: number; + pv: number; + exp?: number; + iat?: number; + roles?: string[]; + } + + export interface IBaseResponse { + message: string; + code: number; + data?: T; + } + + export interface IListRespData { + items: T[]; + } +} + +export {}; diff --git a/types/module.d.ts b/types/module.d.ts new file mode 100644 index 0000000..b6cd022 --- /dev/null +++ b/types/module.d.ts @@ -0,0 +1,7 @@ +import 'fastify'; + +declare module 'fastify' { + interface FastifyRequest { + user?: IAuthUser; + } +} diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 0000000..812c77e --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,104 @@ +/** 提取Promise返回值 */ +type UnboxPromise> = T extends Promise + ? U + : never + +/** 将联合类型转为交叉类型 */ +declare type UnionToIntersection = ( + U extends any ? (k: U) => void : never +) extends (k: infer I) => void + ? I + : never + +/** eg: type result = StringToUnion<'abc'> 结果:'a'|'b'|'c' */ +type StringToUnion = S extends `${infer S1}${infer S2}` + ? S1 | StringToUnion + : never + +/** 字符串替换,类似js的字符串replace方法 */ +type Replace< + Str extends string, + From extends string, + To extends string, +> = Str extends `${infer Left}${From}${infer Right}` + ? `${Left}${To}${Right}` + : Str + +/** 字符串替换,类似js的字符串replaceAll方法 */ +type ReplaceAll< + Str extends string, + From extends string, + To extends string, +> = Str extends `${infer Left}${From}${infer Right}` + ? Replace, From, To> + : Str + +/** eg: type result = CamelCase<'foo-bar-baz'>, 结果:fooBarBaz */ +type CamelCase = S extends `${infer S1}-${infer S2}` + ? S2 extends Capitalize + ? `${S1}-${CamelCase}` + : `${S1}${CamelCase>}` + : S + +/** eg: type result = StringToArray<'abc'>, 结果:['a', 'b', 'c'] */ +type StringToArray< + S extends string, + T extends any[] = [], +> = S extends `${infer S1}${infer S2}` ? StringToArray : T + +/** `RequiredKeys`是用来获取所有必填字段,其中这些必填字段组合成一个联合类型 */ +type RequiredKeys = { + [P in keyof T]: T extends Record ? P : never; +}[keyof T] + +/** `OptionalKeys`是用来获取所有可选字段,其中这些可选字段组合成一个联合类型 */ +type OptionalKeys = { + [P in keyof T]: object extends Pick ? P : never; +}[keyof T] + +/** `GetRequired`是用来获取一个类型中,所有必填键及其类型所组成的一个新类型的 */ +type GetRequired = { + [P in RequiredKeys]-?: T[P]; +} + +/** `GetOptional`是用来获取一个类型中,所有可选键及其类型所组成的一个新类型的 */ +type GetOptional = { + [P in OptionalKeys]?: T[P]; +} + +/** type result1 = Includes<[1, 2, 3, 4], '4'> 结果: false; type result2 = Includes<[1, 2, 3, 4], 4> 结果: true */ +type Includes = K extends T[number] ? true : false + +/** eg:type result = MyConcat<[1, 2], [3, 4]> 结果:[1, 2, 3, 4] */ +type MyConcat = [...T, ...U] +/** eg: type result1 = MyPush<[1, 2, 3], 4> 结果:[1, 2, 3, 4] */ +type MyPush = [...T, K] +/** eg: type result3 = MyPop<[1, 2, 3]> 结果:[1, 2] */ +type MyPop = T extends [...infer L, infer R] ? L : never; // eslint-disable-line + +type PropType = string extends Path + ? unknown + : Path extends keyof T + ? T[Path] + : Path extends `${infer K}.${infer R}` + ? K extends keyof T + ? PropType + : unknown + : unknown + +/** + * NestedKeyOf + * Get all the possible paths of an object + * @example + * type Keys = NestedKeyOf<{ a: { b: { c: string } }> + * // 'a' | 'a.b' | 'a.b.c' + */ +type NestedKeyOf = { + [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object + ? `${Key}` | `${Key}.${NestedKeyOf}` + : `${Key}`; +}[keyof ObjectType & (string | number)] + + type RecordNamePaths = { + [K in NestedKeyOf]: PropType + } diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..90de8ed --- /dev/null +++ b/vercel.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "installCommand": "pnpm install", + "buildCommand": "pnpm build", + // 由于项目中使用了路径别名,暂时无法简单的部署到 vercel: https://github.com/vercel/vercel/issues/2832 + "builds": [ + { + "src": "src/main.ts", + "use": "@vercel/node" + } + ], + "routes": [ + { + "src": "/(.*)", + "dest": "src/main.ts", + "methods": ["GET", "POST", "PUT", "PATCH", "DELETE"] + } + ], + "env": { + "NODE_ENV": "development" + } +} diff --git a/wait-for-it.sh b/wait-for-it.sh new file mode 100644 index 0000000..3974640 --- /dev/null +++ b/wait-for-it.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi \ No newline at end of file