diff --git a/.env b/.env new file mode 100644 index 0000000..abb00c8 --- /dev/null +++ b/.env @@ -0,0 +1,15 @@ +# 服务端口 +PORT=3000 +# MySQL数据库配置 +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=123456 +DB_NAME=corp_website +# JWT鉴权固定配置 +JWT_SECRET=corp_website_secret_key_2026ai +JWT_EXPIRES_IN=7d +# 文件上传目录 +UPLOAD_ROOT=./uploads +# 运行环境 +NODE_ENV=development diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ef2683c --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# 复制为 .env 并按需修改 +PORT=3001 +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=root +DB_NAME=corp_website +JWT_SECRET=corp_website_secret_key_2026ai +JWT_EXPIRES_IN=7d +UPLOAD_ROOT=./uploads +NODE_ENV=development diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml new file mode 100644 index 0000000..e38522a --- /dev/null +++ b/.gitea/workflows/main.yml @@ -0,0 +1,108 @@ +name: main + +on: + push: + branches: ["main"] + workflow_dispatch: + +jobs: + build-and-push: + name: Build and push to Aliyun ACR + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: https://gitee.com/zsqai/checkout@v4 + + # 准备环境变量文件:将 .env.production 复制为 .env + - name: Prepare .env file for production + run: cp .env.production .env + + - name: Set up Docker Buildx + uses: https://gitee.com/zsqai/setup-buildx-action@v3 + + - name: Login to Aliyun Container Registry + uses: https://gitee.com/zsqai/login-action@v3 + with: + registry: ${{ vars.ALIYUN_REGISTRY }} + username: ${{ vars.ALIYUN_USERNAME }} + password: ${{ secrets.ALIYUN_PASSWORD }} + + - name: Build and push Docker image + uses: https://gitee.com/zsqai/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ vars.ALIYUN_REGISTRY }}/${{ vars.ALIYUN_NAMESPACE }}/${{ vars.ALIYUN_REPO }}:latest + ${{ vars.ALIYUN_REGISTRY }}/${{ vars.ALIYUN_NAMESPACE }}/${{ vars.ALIYUN_REPO }}:${{ github.sha }} + + deploy: + name: Deploy to server + runs-on: ubuntu-latest + needs: build-and-push + environment: + name: production + url: http://${{ vars.HOST }}:8084 + steps: + - name: Deploy via SSH + uses: https://gitee.com/zsqai/ssh-action@v1.0.3 + with: + host: ${{ vars.HOST }} + username: root + password: ${{ secrets.DEV_HOST_PASSWORD }} + port: 22 + script_stop: true + script: | + set -e + echo "=== 开始部署 $(date) ===" + + # 登录阿里云镜像仓库 + docker login --username=${{ vars.ALIYUN_USERNAME }} \ + --password=${{ secrets.ALIYUN_PASSWORD }} \ + ${{ vars.ALIYUN_REGISTRY }} + + # 拉取最新镜像 + docker pull ${{ vars.ALIYUN_REGISTRY }}/${{ vars.ALIYUN_NAMESPACE }}/${{ vars.ALIYUN_REPO }}:latest + + # 停止并删除旧容器 + docker stop ai-personage-api 2>/dev/null || true + docker rm ai-personage-api 2>/dev/null || true + + # 启动新容器(不再需要 -e 参数,因为环境变量已打包在镜像内) + docker run -d \ + --name ai-personage-api \ + --restart always \ + -p 8084:3002 \ + ${{ vars.ALIYUN_REGISTRY }}/${{ vars.ALIYUN_NAMESPACE }}/${{ vars.ALIYUN_REPO }}:latest + + # 清理旧镜像 + docker image prune -f + + # 检查容器状态 + sleep 5 + if docker ps --format '{{.Names}}' | grep -q "^ai-personage-api$"; then + echo "✓ 容器启动成功" + docker logs --tail 20 ai-personage-api + else + echo "✗ 容器启动失败" + docker logs ai-personage-api + exit 1 + fi + + notify: + name: Send notification + runs-on: ubuntu-latest + needs: [build-and-push, deploy] + if: always() + steps: + - name: Deployment result + run: | + if [ "${{ needs.deploy.result }}" == "success" ]; then + echo "✓ 部署成功" + echo "分支: ${{ github.ref_name }}" + echo "提交: ${{ github.sha }}" + echo "时间: $(date)" + else + echo "✗ 部署失败,请检查 CI 最终日志" + exit 1 + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5d1630 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# ---------- 系统 / 编辑器 ---------- +.DS_Store +Thumbs.db +.idea/ +.vscode/ +*.swp +*.swo + +# ---------- Node / pnpm ---------- +node_modules/ +.pnpm-store/ +pnpm-debug.log* +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnp.* +.pnp.js + +# ---------- 构建产物 ---------- +dist/ +build/ +out/ +.next/ +.turbo/ +*.tsbuildinfo + +# ---------- 环境变量(敏感信息不入库) ---------- +# 后端主配置文件按文档要求生成 .env,但强烈建议提交时改为 .env.example +server/.env +client/.env.local + +# ---------- 上传文件(运行时产物) ---------- +server/uploads/ + +# ---------- 日志 ---------- +*.log +logs/ + +# ---------- 测试覆盖率 ---------- +coverage/ + +# ---------- 缓存 ---------- +.cache/ +.eslintcache diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8b0e83d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "semi": true, + "printWidth": 100, + "tabWidth": 2 +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..31a08bf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# 构建阶段 +FROM crpi-jlsdxetsdmy4ckxh.cn-shenzhen.personal.cr.aliyuncs.com/zsq_proxy/node:20-alpine AS build +WORKDIR /app + +# 添加构建参数 +ARG BUILD_VERSION +ARG BUILD_TIME +ARG CACHE_BUST + +ENV BUILD_VERSION=$BUILD_VERSION +ENV BUILD_TIME=$BUILD_TIME + +# 强制打破 Docker 缓存 +RUN echo "Cache bust: ${CACHE_BUST} - ${BUILD_VERSION} - ${BUILD_TIME}" + +# 安装 pnpm +RUN npm install -g pnpm && \ + pnpm config set registry https://registry.npmmirror.com + +COPY package*.json ./ +# 如果项目有 pnpm-lock.yaml,取消注释下面一行 +# COPY pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +COPY . . + +# 强制清理 .next +RUN rm -rf .next + +# 构建 +RUN pnpm build + +# 部署阶段 +FROM crpi-jlsdxetsdmy4ckxh.cn-shenzhen.personal.cr.aliyuncs.com/zsq_proxy/node:20-alpine AS app +WORKDIR /app + +ENV NODE_ENV=production +ENV NODE_OPTIONS="--max-old-space-size=4096" + +COPY --from=build /app/.next ./.next +COPY --from=build /app/public ./public +COPY --from=build /app/package.json ./package.json +COPY --from=build /app/node_modules ./node_modules + +EXPOSE 3003 + +CMD ["pnpm", "start"] \ No newline at end of file diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..bec28ce --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "corp-official-website-server", + "version": "1.0.0", + "description": "corp-official-website NestJS 10 backend", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix" + }, + "dependencies": { + "@nestjs/common": "^10.2.10", + "@nestjs/config": "^3.1.1", + "@nestjs/core": "^10.2.10", + "@nestjs/jwt": "^10.2.0", + "@nestjs/passport": "^10.0.2", + "@nestjs/platform-express": "^10.2.10", + "@nestjs/serve-static": "^4.0.0", + "@nestjs/swagger": "^7.1.16", + "@nestjs/typeorm": "^10.0.1", + "bcrypt": "^5.1.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "multer": "^1.4.5-lts.1", + "mysql2": "^3.6.5", + "nest-winston": "^1.9.4", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1", + "typeorm": "^0.3.17", + "winston": "^3.11.0" + }, + "devDependencies": { + "@nestjs/cli": "^10.2.1", + "@nestjs/schematics": "^10.0.2", + "@nestjs/testing": "^10.2.10", + "@types/bcrypt": "^5.0.2", + "@types/express": "^4.17.21", + "@types/multer": "^1.4.11", + "@types/node": "^20.9.2", + "@types/passport-jwt": "^3.0.13", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint": "^8.54.0", + "prettier": "^3.1.0", + "source-map-support": "^0.5.21", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.2.2" + }, + "pnpm": { + "onlyBuiltDependencies": ["bcrypt", "@nestjs/core"] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..19c7577 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5175 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@nestjs/common': + specifier: ^10.2.10 + version: 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/config': + specifier: ^3.1.1 + version: 3.3.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2) + '@nestjs/core': + specifier: ^10.2.10 + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)) + '@nestjs/passport': + specifier: ^10.0.2 + version: 10.0.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0) + '@nestjs/platform-express': + specifier: ^10.2.10 + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22) + '@nestjs/serve-static': + specifier: ^4.0.0 + version: 4.0.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(express@4.22.1) + '@nestjs/swagger': + specifier: ^7.1.16 + version: 7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) + '@nestjs/typeorm': + specifier: ^10.0.1 + version: 10.0.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.30(mysql2@3.22.5(@types/node@20.19.43))(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3))) + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.0 + version: 0.14.4 + multer: + specifier: ^1.4.5-lts.1 + version: 1.4.5-lts.2 + mysql2: + specifier: ^3.6.5 + version: 3.22.5(@types/node@20.19.43) + nest-winston: + specifier: ^1.9.4 + version: 1.10.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(winston@3.19.0) + passport: + specifier: ^0.6.0 + version: 0.6.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + reflect-metadata: + specifier: ^0.1.13 + version: 0.1.14 + rxjs: + specifier: ^7.8.1 + version: 7.8.2 + typeorm: + specifier: ^0.3.17 + version: 0.3.30(mysql2@3.22.5(@types/node@20.19.43))(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)) + winston: + specifier: ^3.11.0 + version: 3.19.0 + devDependencies: + '@nestjs/cli': + specifier: ^10.2.1 + version: 10.4.9 + '@nestjs/schematics': + specifier: ^10.0.2 + version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3) + '@nestjs/testing': + specifier: ^10.2.10 + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22) + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 + '@types/express': + specifier: ^4.17.21 + version: 4.17.25 + '@types/multer': + specifier: ^1.4.11 + version: 1.4.13 + '@types/node': + specifier: ^20.9.2 + version: 20.19.43 + '@types/passport-jwt': + specifier: ^3.0.13 + version: 3.0.13 + '@typescript-eslint/eslint-plugin': + specifier: ^6.14.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.14.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + eslint: + specifier: ^8.54.0 + version: 8.57.1 + prettier: + specifier: ^3.1.0 + version: 3.8.4 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + ts-loader: + specifier: ^9.5.1 + version: 9.6.1(typescript@5.9.3)(webpack@5.97.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.19.43)(typescript@5.9.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.2.2 + version: 5.9.3 + +packages: + + '@angular-devkit/core@17.3.11': + resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@17.3.11': + resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@17.3.11': + resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@dabh/diagnostics@2.0.8': + resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@ljharb/through@2.3.14': + resolution: {integrity: sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==} + engines: {node: '>= 0.4'} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + + '@microsoft/tsdoc@0.15.1': + resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + + '@nestjs/cli@10.4.9': + resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} + engines: {node: '>= 16.14'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + + '@nestjs/common@10.4.22': + resolution: {integrity: sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==} + 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 + + '@nestjs/config@3.3.0': + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 + + '@nestjs/core@10.4.22': + resolution: {integrity: sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==} + 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 + + '@nestjs/jwt@10.2.0': + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@nestjs/mapped-types@2.0.5': + 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 + + '@nestjs/passport@10.0.3': + 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 + + '@nestjs/platform-express@10.4.22': + resolution: {integrity: sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + + '@nestjs/schematics@10.2.3': + resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} + peerDependencies: + typescript: '>=4.8.2' + + '@nestjs/serve-static@4.0.2': + resolution: {integrity: sha512-cT0vdWN5ar7jDI2NKbhf4LcwJzU4vS5sVpMkVrHuyLcltbrz6JdGi1TfIMMatP2pNiq5Ie/uUdPSFDVaZX/URQ==} + peerDependencies: + '@fastify/static': ^6.5.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + express: ^4.18.1 + fastify: ^4.7.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + express: + optional: true + fastify: + optional: true + + '@nestjs/swagger@7.4.2': + resolution: {integrity: sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/testing@10.4.22': + resolution: {integrity: sha512-HO9aPus3bAedAC+jKVAA8jTdaj4fs5M9fing4giHrcYV2txe9CvC1l1WAjwQ9RDhEHdugjY4y+FZA/U/YqPZrA==} + 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 + + '@nestjs/typeorm@10.0.2': + 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 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nuxtjs/opencollective@0.3.2': + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@so-ric/colorspace@1.1.6': + resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} + + '@sqltools/formatter@1.2.5': + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + + '@tokenizer/inflate@0.2.7': + resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@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/bcrypt@5.0.2': + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/jsonwebtoken@9.0.5': + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/multer@1.4.13': + resolution: {integrity: sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==} + + '@types/node@20.19.43': + resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} + + '@types/passport-jwt@3.0.13': + resolution: {integrity: sha512-fjHaC6Bv8EpMMqzTnHP32SXlZGaNfBPC/Po5dmRGYi2Ky7ljXPbGnOy+SxZqa6iZvFgVhoJ1915Re3m93zmcfA==} + + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + + '@types/passport@1.0.17': + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@types/validator@13.15.10': + resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + + '@typescript-eslint/eslint-plugin@6.21.0': + 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 + + '@typescript-eslint/parser@6.21.0': + 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 + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + 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 + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.37: + resolution: {integrity: sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==} + engines: {node: '>=6.0.0'} + hasBin: true + + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} + + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.4: + resolution: {integrity: sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-convert@3.1.3: + resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} + engines: {node: '>=14.6'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-name@2.1.0: + resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} + engines: {node: '>=12.20'} + + color-string@2.1.4: + resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} + engines: {node: '>=18'} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + color@5.0.3: + resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} + engines: {node: '>=18'} + + 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'} + + comment-json@4.2.5: + resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + engines: {node: '>= 6'} + + 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} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + 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-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.375: + resolution: {integrity: sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==} + + 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==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.24.0: + resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==} + engines: {node: '>=10.13.0'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + 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} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-type@20.4.1: + resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fork-ts-checker-webpack-plugin@9.0.2: + resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + 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] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + 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'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + + inquirer@9.2.15: + resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} + engines: {node: '>=18'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + 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==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@3.2.3: + resolution: {integrity: sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + libphonenumber-js@1.13.6: + resolution: {integrity: sha512-NdB6O6QvlGMCoG003m0YIKG2+Xw7DjmCZhmc1RH+K6HncADUbRf8TZeLegxBBN1VFyPHcNpPTKpIhYLXzJVy1Q==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} + engines: {node: '>=6.11.5'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + 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==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru.min@1.1.4: + resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + + magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + 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'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@1.4.5-lts.2: + resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==} + engines: {node: '>= 6.0.0'} + deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version. + + multer@2.0.2: + resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + engines: {node: '>= 10.16.0'} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + 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} + + mysql2@3.22.5: + resolution: {integrity: sha512-95uZ2TrPWAZdwpB3vvvDbmEMcNG8yIeNCyu6GUcr/QnWEE/wXm7+mhOCsdQfWQDTV7qYT/PDUZ4U4UPP4AsXqQ==} + engines: {node: '>= 8.0'} + peerDependencies: + '@types/node': '>= 8' + + named-placeholders@1.1.6: + resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==} + engines: {node: '>=8.0.0'} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + 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==} + + nest-winston@1.10.2: + resolution: {integrity: sha512-Z9IzL/nekBOF/TEwBHUJDiDPMaXUcFquUQOFavIRet6xF0EbuWnOzslyN/ksgzG+fITNgXhMdrL/POp9SdaFxA==} + peerDependencies: + '@nestjs/common': ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + winston: ^3.0.0 + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + 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 + + node-releases@2.0.47: + resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} + engines: {node: '>=18'} + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.6.0: + resolution: {integrity: sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==} + engines: {node: '>= 0.4.0'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + + path-to-regexp@0.2.5: + resolution: {integrity: sha512-l6qtdDPIkmAmzEO6egquYDfqQGPMRNGjYtrU13HAXb3YSRrt7HSb1sJY0pKp6o2bAa86tSB6iwaW2JbthPKr7Q==} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.1: + resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} + engines: {node: '>=12'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.4: + resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + engines: {node: '>=14'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect-metadata@0.1.14: + resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + 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-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + 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'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + 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'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + sql-escaper@1.3.3: + resolution: {integrity: sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==} + engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'} + + sql-highlight@6.1.0: + resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} + engines: {node: '>=14'} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + swagger-ui-dist@5.17.14: + resolution: {integrity: sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + terser-webpack-plugin@5.6.1: + resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true + uglify-js: + optional: true + + terser@5.48.0: + resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + engines: {node: '>=10'} + hasBin: true + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} + engines: {node: '>=14.16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-loader@9.6.1: + resolution: {integrity: sha512-8FMHnmxtpncUAu0ZjkqpXnOTlwc9eY95esH8WVN94guTPPdkg2ofVdiVM5j8L2lmjiGerXd56zXb/D2JyVQPLg==} + engines: {node: '>=12.0.0'} + peerDependencies: + loader-utils: '*' + typescript: '*' + webpack: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + loader-utils: + optional: true + + ts-node@10.9.2: + 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 + + tsconfig-paths-webpack-plugin@4.2.0: + resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + 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'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typeorm@0.3.30: + resolution: {integrity: sha512-8T35PzjefOdqc2ZR9mwLQj0pUGp6lQhMbK2EvVMwJVJWlaoHm0v/Q6dThNOZkFchD+0yMg8gwjKM28ePiLSXSQ==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + '@sap/hana-client': ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + 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 + + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + 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==} + + validator@13.15.35: + resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + watchpack@2.5.2: + resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + 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'} + + webpack-sources@3.5.0: + resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==} + engines: {node: '>=10.13.0'} + + webpack@5.97.1: + resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.19.0: + resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} + engines: {node: '>= 12.0.0'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + 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'} + +snapshots: + + '@angular-devkit/core@17.3.11(chokidar@3.6.0)': + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + jsonc-parser: 3.2.1 + picomatch: 4.0.1 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 3.6.0 + + '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + ansi-colors: 4.1.3 + inquirer: 9.2.15 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + jsonc-parser: 3.2.1 + magic-string: 0.30.8 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + + '@borewit/text-codec@0.2.2': {} + + '@colors/colors@1.5.0': + optional: true + + '@colors/colors@1.6.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@dabh/diagnostics@2.0.8': + dependencies: + '@so-ric/colorspace': 1.1.6 + enabled: 2.0.0 + kuler: 2.0.0 + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.2.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@ljharb/through@2.3.14': + dependencies: + call-bind: 1.0.9 + + '@lukeed/csprng@1.1.0': {} + + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.1.2 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.8.4 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@microsoft/tsdoc@0.15.1': {} + + '@nestjs/cli@10.4.9': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) + '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1) + glob: 10.4.5 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.2.0 + typescript: 5.7.2 + webpack: 5.97.1 + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + - webpack-cli + + '@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + dependencies: + file-type: 20.4.1 + iterare: 1.2.1 + reflect-metadata: 0.1.14 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.4 + transitivePeerDependencies: + - supports-color + + '@nestjs/config@3.3.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + lodash: 4.17.21 + rxjs: 7.8.2 + + '@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nuxtjs/opencollective': 0.3.2 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 3.3.0 + reflect-metadata: 0.1.14 + rxjs: 7.8.2 + tslib: 2.8.1 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22) + transitivePeerDependencies: + - encoding + + '@nestjs/jwt@10.2.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + + '@nestjs/mapped-types@2.0.5(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + reflect-metadata: 0.1.14 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.4 + + '@nestjs/passport@10.0.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(passport@0.6.0)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + passport: 0.6.0 + + '@nestjs/platform-express@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + body-parser: 1.20.4 + cors: 2.8.5 + express: 4.22.1 + multer: 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.7.2)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.7.2 + transitivePeerDependencies: + - chokidar + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.9.3)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - chokidar + + '@nestjs/serve-static@4.0.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(express@4.22.1)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + path-to-regexp: 0.2.5 + optionalDependencies: + express: 4.22.1 + + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)': + dependencies: + '@microsoft/tsdoc': 0.15.1 + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/mapped-types': 2.0.5(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14) + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 3.3.0 + reflect-metadata: 0.1.14 + swagger-ui-dist: 5.17.14 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.4 + + '@nestjs/testing@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22)': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + tslib: 2.8.1 + optionalDependencies: + '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22) + + '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.30(mysql2@3.22.5(@types/node@20.19.43))(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)))': + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.1.14)(rxjs@7.8.2) + reflect-metadata: 0.1.14 + rxjs: 7.8.2 + typeorm: 0.3.30(mysql2@3.22.5(@types/node@20.19.43))(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)) + uuid: 9.0.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nuxtjs/opencollective@0.3.2': + dependencies: + chalk: 4.1.2 + consola: 2.15.3 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@so-ric/colorspace@1.1.6': + dependencies: + color: 5.0.3 + text-hex: 1.0.0 + + '@sqltools/formatter@1.2.5': {} + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.3 + fflate: 0.8.3 + token-types: 6.1.2 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/bcrypt@5.0.2': + dependencies: + '@types/node': 20.19.43 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.43 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.43 + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.9 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.9': {} + + '@types/express-serve-static-core@4.19.8': + dependencies: + '@types/node': 20.19.43 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.25': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.15.1 + '@types/serve-static': 1.15.10 + + '@types/http-errors@2.0.5': {} + + '@types/json-schema@7.0.15': {} + + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 20.19.43 + + '@types/jsonwebtoken@9.0.5': + dependencies: + '@types/node': 20.19.43 + + '@types/mime@1.3.5': {} + + '@types/ms@2.1.0': {} + + '@types/multer@1.4.13': + dependencies: + '@types/express': 4.17.25 + + '@types/node@20.19.43': + dependencies: + undici-types: 6.21.0 + + '@types/passport-jwt@3.0.13': + dependencies: + '@types/express': 4.17.25 + '@types/jsonwebtoken': 9.0.10 + '@types/passport-strategy': 0.2.38 + + '@types/passport-strategy@0.2.38': + dependencies: + '@types/express': 4.17.25 + '@types/passport': 1.0.17 + + '@types/passport@1.0.17': + dependencies: + '@types/express': 4.17.25 + + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + + '@types/semver@7.7.1': {} + + '@types/send@0.17.6': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.19.43 + + '@types/send@1.2.1': + dependencies: + '@types/node': 20.19.43 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.43 + '@types/send': 0.17.6 + + '@types/triple-beam@1.3.5': {} + + '@types/validator@13.15.10': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.8.4 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.8.4 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + eslint: 8.57.1 + semver: 7.8.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.1': {} + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + abbrev@1.1.1: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ajv-formats@2.1.1(ajv@8.12.0): + optionalDependencies: + ajv: 8.12.0 + + ajv-formats@2.1.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-keywords@3.5.2(ajv@6.15.0): + dependencies: + ajv: 6.15.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + fast-deep-equal: 3.1.3 + + ajv@6.15.0: + 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 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + ansis@4.3.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + app-root-path@3.1.0: {} + + append-field@1.0.0: {} + + aproba@2.1.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@4.1.3: {} + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + array-timsort@1.0.3: {} + + array-union@2.1.0: {} + + async@3.2.6: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-ssl-profiles@1.1.2: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.37: {} + + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.15: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.37 + caniuse-lite: 1.0.30001799 + electron-to-chromium: 1.5.375 + node-releases: 2.0.47 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001799: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chardet@0.7.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + 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 + + chownr@2.0.0: {} + + chrome-trace-event@1.0.4: {} + + class-transformer@0.5.1: {} + + class-validator@0.14.4: + dependencies: + '@types/validator': 13.15.10 + libphonenumber-js: 1.13.6 + validator: 13.15.35 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@3.0.0: {} + + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-convert@3.1.3: + dependencies: + color-name: 2.1.0 + + color-name@1.1.4: {} + + color-name@2.1.0: {} + + color-string@2.1.4: + dependencies: + color-name: 2.1.0 + + color-support@1.1.3: {} + + color@5.0.3: + dependencies: + color-convert: 3.1.3 + color-string: 2.1.4 + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-json@4.2.5: + 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 + + concat-map@0.0.1: {} + + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + consola@2.15.3: {} + + console-control-strings@1.1.0: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + core-util-is@1.0.3: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@5.7.2): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.2.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.7.2 + + create-require@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dayjs@1.11.21: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + dedent@1.7.2: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + delegates@1.0.0: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: {} + + diff@4.0.4: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv-expand@10.0.0: {} + + dotenv@16.4.5: {} + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.375: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enabled@2.0.0: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.24.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.1 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + 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.7.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.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.2.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + events@3.3.0: {} + + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.13 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + 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.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.2: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fecha@4.2.3: {} + + fflate@0.8.3: {} + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-type@20.4.1: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.5 + token-types: 6.1.2 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.4.2: {} + + fn.name@1.1.0: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1): + dependencies: + '@babel/code-frame': 7.29.7 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.7.2) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.5 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.8.4 + tapable: 2.3.3 + typescript: 5.7.2 + webpack: 5.97.1 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs-monkey@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gauge@3.0.2: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-own-prop@2.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@8.2.6: + 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.18.1 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + + inquirer@9.2.15: + dependencies: + '@ljharb/through': 2.3.14 + ansi-escapes: 4.3.2 + chalk: 5.6.2 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.18.1 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.2 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-callable@1.2.7: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-property@1.0.2: {} + + is-stream@2.0.1: {} + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + is-unicode-supported@0.1.0: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterare@1.2.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.19.43 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.2.1: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.3 + 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.8.4 + + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + 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.8.4 + + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.3: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kuler@2.0.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + libphonenumber-js@1.13.6: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.2: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + 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.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash@4.17.21: {} + + lodash@4.18.1: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + logform@2.7.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.5.0 + triple-beam: 1.4.1 + + long@5.3.2: {} + + lru-cache@10.4.3: {} + + lru.min@1.1.4: {} + + magic-string@0.30.8: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + make-error@1.3.6: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.15 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.1.1 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.1 + + minimist@1.2.8: {} + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.3: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multer@1.4.5-lts.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + multer@2.0.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + mute-stream@0.0.8: {} + + mute-stream@1.0.0: {} + + mysql2@3.22.5(@types/node@20.19.43): + dependencies: + '@types/node': 20.19.43 + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.2 + long: 5.3.2 + lru.min: 1.1.4 + named-placeholders: 1.1.6 + sql-escaper: 1.3.3 + + named-placeholders@1.1.6: + dependencies: + lru.min: 1.1.4 + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + neo-async@2.6.2: {} + + nest-winston@1.10.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2))(winston@3.19.0): + dependencies: + '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.1.14)(rxjs@7.8.2) + fast-safe-stringify: 2.1.1 + winston: 3.19.0 + + node-abort-controller@3.1.1: {} + + node-addon-api@5.1.0: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.18.1 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.47: {} + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-path@3.0.0: {} + + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + 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 + + os-tmpdir@1.0.2: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.7 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.3 + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.6.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-to-regexp@0.1.13: {} + + path-to-regexp@0.2.5: {} + + path-to-regexp@3.3.0: {} + + path-type@4.0.0: {} + + pause@0.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.1: {} + + pluralize@8.0.0: {} + + possible-typed-array-names@1.1.0: {} + + prelude-ls@1.2.1: {} + + prettier@3.8.4: {} + + process-nextick-args@2.0.1: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qs@6.14.2: + dependencies: + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readable-stream@2.3.8: + 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 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + reflect-metadata@0.1.14: {} + + reflect-metadata@0.2.2: {} + + repeat-string@1.6.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-async@2.4.1: {} + + run-async@3.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.15.0 + ajv-keywords: 3.5.2(ajv@6.15.0) + + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) + + semver@6.3.1: {} + + semver@7.8.4: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + source-map@0.7.6: {} + + sql-escaper@1.3.3: {} + + sql-highlight@6.1.0: {} + + stack-trace@0.0.10: {} + + statuses@2.0.2: {} + + streamsearch@1.1.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + swagger-ui-dist@5.17.14: {} + + symbol-observable@4.0.0: {} + + tapable@2.3.3: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + terser-webpack-plugin@5.6.1(webpack@5.97.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.48.0 + webpack: 5.97.1 + + terser@5.48.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.17.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + text-hex@1.0.0: {} + + text-table@0.2.0: {} + + through@2.3.8: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + token-types@6.1.2: + dependencies: + '@borewit/text-codec': 0.2.2 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + tr46@0.0.3: {} + + tree-kill@1.2.2: {} + + triple-beam@1.4.1: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-loader@9.6.1(typescript@5.9.3)(webpack@5.97.1): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.24.0 + micromatch: 4.0.8 + semver: 7.8.4 + source-map: 0.7.6 + typescript: 5.9.3 + webpack: 5.97.1 + + ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.43 + acorn: 8.17.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths-webpack-plugin@4.2.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.24.0 + tapable: 2.3.3 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typedarray@0.0.6: {} + + typeorm@0.3.30(mysql2@3.22.5(@types/node@20.19.43))(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.9.3)): + dependencies: + '@sqltools/formatter': 1.2.5 + ansis: 4.3.1 + app-root-path: 3.1.0 + buffer: 6.0.3 + dayjs: 1.11.21 + debug: 4.4.3 + dedent: 1.7.2 + dotenv: 16.6.1 + glob: 10.5.0 + reflect-metadata: 0.2.2 + sha.js: 2.4.12 + sql-highlight: 6.1.0 + tslib: 2.8.1 + uuid: 11.1.1 + yargs: 17.7.2 + optionalDependencies: + mysql2: 3.22.5(@types/node@20.19.43) + ts-node: 10.9.2(@types/node@20.19.43)(typescript@5.9.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + typescript@5.7.2: {} + + typescript@5.9.3: {} + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + uint8array-extras@1.5.0: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@11.1.1: {} + + uuid@9.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + validator@13.15.35: {} + + vary@1.1.2: {} + + watchpack@2.5.2: + dependencies: + graceful-fs: 4.2.11 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.5.0: {} + + webpack@5.97.1: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.9 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.17.0 + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.24.0 + es-module-lexer: 1.7.0 + 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.2 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.1(webpack@5.97.1) + watchpack: 2.5.2 + webpack-sources: 3.5.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.19.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.8 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@4.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + 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: {} + + yocto-queue@0.1.0: {} diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..c5c0af7 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,117 @@ +import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { APP_GUARD } from '@nestjs/core'; +import { JwtModule } from '@nestjs/jwt'; +import { ServeStaticModule } from '@nestjs/serve-static'; + +import configuration from './config/configuration'; + +import { JwtAuthGuard, RolesGuard } from './common/guards'; +import { UploadMiddleware } from './common/middlewares'; + +import { Admin } from './entities/admin.entity'; +import { Banner } from './entities/banner.entity'; +import { Manual } from './entities/manual.entity'; +import { Message } from './entities/message.entity'; +import { NewsCategory } from './entities/news-category.entity'; +import { News } from './entities/news.entity'; +import { ProductCategory } from './entities/product-category.entity'; +import { Product } from './entities/product.entity'; +import { SiteConfig } from './entities/site-config.entity'; +import { Team } from './entities/team.entity'; + +import { AuthModule } from './modules/auth/auth.module'; +import { AdminUserModule } from './modules/admin-user/admin-user.module'; +import { BannerModule } from './modules/banner/banner.module'; +import { ManualModule } from './modules/manual/manual.module'; +import { MessageModule } from './modules/message/message.module'; +import { NewsCategoryModule } from './modules/news-category/news-category.module'; +import { NewsModule } from './modules/news/news.module'; +import { ProductCategoryModule } from './modules/product-category/product-category.module'; +import { ProductModule } from './modules/product/product.module'; +import { SiteConfigModule } from './modules/site-config/site-config.module'; +import { TeamModule } from './modules/team/team.module'; +import { UploadModule } from './modules/upload/upload.module'; + +import path from 'path'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [configuration], + }), + TypeOrmModule.forRootAsync({ + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + type: 'mysql', + host: config.get('database.host'), + port: config.get('database.port'), + username: config.get('database.user'), + password: config.get('database.password'), + database: config.get('database.name'), + autoLoadEntities: true, + synchronize: false, + timezone: '+08:00', + charset: 'utf8mb4', + logging: config.get('nodeEnv') === 'development', + }), + }), + JwtModule.registerAsync({ + global: true, + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + secret: config.get('jwt.secret'), + signOptions: { expiresIn: config.get('jwt.expiresIn') }, + }), + }), + ServeStaticModule.forRoot({ + rootPath: path.resolve( + process.cwd(), + (process.env.UPLOAD_ROOT ?? './uploads').replace(/^\.\/?/, ''), + ), + serveRoot: '/uploads', + serveStaticOptions: { index: false }, + }), + TypeOrmModule.forFeature([ + Admin, + Banner, + Manual, + Message, + NewsCategory, + News, + ProductCategory, + Product, + SiteConfig, + Team, + ]), + AuthModule, + AdminUserModule, + BannerModule, + ManualModule, + MessageModule, + NewsCategoryModule, + NewsModule, + ProductCategoryModule, + ProductModule, + SiteConfigModule, + TeamModule, + UploadModule, + ], + providers: [ + // JwtAuthGuard 依赖 JwtService + Reflector,必须用 DI 注册 + { provide: APP_GUARD, useClass: JwtAuthGuard }, + // RolesGuard 读取 @Roles() 元数据校验角色权限 + { provide: APP_GUARD, useClass: RolesGuard }, + ], +}) +export class AppModule implements NestModule { + configure(consumer: MiddlewareConsumer): void { + // middleware 在 global prefix 应用之前注册,路径用不带前缀的内部路径 + consumer.apply(UploadMiddleware).forRoutes({ + path: 'admin/upload', + method: RequestMethod.POST, + }); + } +} diff --git a/src/common/decorators/current-admin.decorator.ts b/src/common/decorators/current-admin.decorator.ts new file mode 100644 index 0000000..2f024b6 --- /dev/null +++ b/src/common/decorators/current-admin.decorator.ts @@ -0,0 +1,22 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** + * 当前登录管理员信息(由 JwtAuthGuard 解析后挂到 req.admin) + */ +export interface CurrentAdminPayload { + id: number; + username: string; + nickname: string; + role: 'super_admin' | 'normal'; +} + +export const CurrentAdmin = createParamDecorator( + (data: keyof CurrentAdminPayload | undefined, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + const admin: CurrentAdminPayload | undefined = request.admin; + if (!admin) { + return undefined; + } + return data ? admin[data] : admin; + }, +); diff --git a/src/common/decorators/index.ts b/src/common/decorators/index.ts new file mode 100644 index 0000000..578ddca --- /dev/null +++ b/src/common/decorators/index.ts @@ -0,0 +1,3 @@ +export * from './current-admin.decorator'; +export * from './public.decorator'; +export * from './roles.decorator'; diff --git a/src/common/decorators/public.decorator.ts b/src/common/decorators/public.decorator.ts new file mode 100644 index 0000000..41e13de --- /dev/null +++ b/src/common/decorators/public.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; + +/** + * 标记接口为公开(无需 JWT)。 + * 与 JwtAuthGuard 配合:守卫读取 IS_PUBLIC 元数据后跳过校验。 + */ +export const IS_PUBLIC_KEY = 'isPublic'; +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/src/common/decorators/roles.decorator.ts b/src/common/decorators/roles.decorator.ts new file mode 100644 index 0000000..d7fdb0f --- /dev/null +++ b/src/common/decorators/roles.decorator.ts @@ -0,0 +1,13 @@ +import { SetMetadata } from '@nestjs/common'; + +/** 管理员角色类型 */ +export type AdminRole = 'super_admin' | 'normal'; + +/** 元数据 key:标记接口所需角色 */ +export const ROLES_KEY = 'roles'; + +/** + * 标记接口所需的角色。配合 RolesGuard 使用: + * @Roles('super_admin') 仅超级管理员可访问 + */ +export const Roles = (...roles: AdminRole[]) => SetMetadata(ROLES_KEY, roles); diff --git a/src/common/dto/api-response.dto.ts b/src/common/dto/api-response.dto.ts new file mode 100644 index 0000000..2300518 --- /dev/null +++ b/src/common/dto/api-response.dto.ts @@ -0,0 +1,39 @@ +/** + * 统一响应体 + * 成功:{ code:200, msg:'操作成功', data:{} } + * 错误:{ code:401/400/403/500, msg:'错误提示', data:null } + */ +export class ApiResponse { + code: number; + msg: string; + data: T | null; + + constructor(code: number, msg: string, data: T | null = null) { + this.code = code; + this.msg = msg; + this.data = data; + } + + static success(data: T, msg = '操作成功'): ApiResponse { + return new ApiResponse(200, msg, data); + } + + static error(code: number, msg: string): ApiResponse { + return new ApiResponse(code, msg, null); + } +} + +/** 分页查询结果通用结构 */ +export interface PaginatedResult { + list: T[]; + total: number; + page: number; + pageSize: number; +} + +/** 通用分页 DTO 基类 */ +export class PaginationDto { + page: number; + pageSize: number; + keyword?: string; +} diff --git a/src/common/filters/all-exceptions.filter.ts b/src/common/filters/all-exceptions.filter.ts new file mode 100644 index 0000000..e3da2dd --- /dev/null +++ b/src/common/filters/all-exceptions.filter.ts @@ -0,0 +1,101 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + HttpStatus, + Logger, +} from '@nestjs/common'; +import { Request, Response } from 'express'; +import { QueryFailedError } from 'typeorm'; + +/** MySQL 驱动抛出的错误对象结构(部分字段) */ +interface MysqlDriverError { + code?: string; + errno?: number; + sqlMessage?: string; + sqlState?: string; +} + +/** + * 将 MySQL 驱动错误码翻译为面向用户的中文提示。 + * 不暴露原始 SQL / 英文消息给前端。 + */ +function describeDbError(err: MysqlDriverError): string { + switch (err.code) { + case 'ER_BAD_FIELD_ERROR': + return '数据库字段缺失,请联系管理员执行最新的数据库升级脚本'; + case 'ER_NO_SUCH_TABLE': + return '数据库表不存在,请联系管理员核对数据库结构'; + case 'ER_DUP_ENTRY': + return '数据重复,请检查唯一字段(如登录账号)'; + case 'ER_ROW_IS_REFERENCED_2': + case 'ER_ROW_IS_REFERENCED': + return '该记录被其他数据引用,无法删除或修改'; + case 'ER_DATA_TOO_LONG': + return '输入内容过长,请精简后重试'; + case 'ER_BAD_NULL_ERROR': + return '缺少必填字段,请补全后重试'; + case 'ER_LOCK_WAIT_TIMEOUT': + case 'ER_LOCK_DEADLOCK': + return '数据库繁忙,请稍后重试'; + default: + return '服务器内部错误,请稍后重试'; + } +} + +/** + * 全局异常过滤器 + * 把所有异常统一包装成 { code, msg, data:null } 结构。 + * 所有面向用户的提示均为中文,原始英文错误仅输出到服务端日志。 + */ +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + private readonly logger = new Logger(AllExceptionsFilter.name); + + catch(exception: unknown, host: ArgumentsHost): void { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + const isHttp = exception instanceof HttpException; + const status = isHttp + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + let message = '服务器内部错误'; + if (isHttp) { + const res = exception.getResponse(); + if (typeof res === 'string') { + message = res; + } else if (typeof res === 'object' && res !== null) { + const r = res as { message?: unknown }; + message = Array.isArray(r.message) + ? r.message.join(';') + : String(r.message ?? message); + } + } else if (exception instanceof QueryFailedError) { + // TypeORM 查询失败:底层 MySQL 错误统一翻译为中文 + const driverErr = ( + exception as { driverError?: MysqlDriverError } + ).driverError; + message = describeDbError( + driverErr ?? (exception as unknown as MysqlDriverError), + ); + this.logger.error(exception.stack); + } else if (exception instanceof Error) { + // 其他未识别错误:不直接暴露英文 message,统一中文兜底 + message = '服务器内部错误,请稍后重试'; + this.logger.error(exception.stack); + } + + const code = status; + response.status(status).json({ + code, + msg: message, + data: null, + path: request.url, + timestamp: new Date().toISOString(), + }); + } +} diff --git a/src/common/filters/index.ts b/src/common/filters/index.ts new file mode 100644 index 0000000..03c9c30 --- /dev/null +++ b/src/common/filters/index.ts @@ -0,0 +1 @@ +export * from './all-exceptions.filter'; diff --git a/src/common/guards/index.ts b/src/common/guards/index.ts new file mode 100644 index 0000000..e174be2 --- /dev/null +++ b/src/common/guards/index.ts @@ -0,0 +1,2 @@ +export * from './jwt-auth.guard'; +export * from './roles.guard'; diff --git a/src/common/guards/jwt-auth.guard.ts b/src/common/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..fcada09 --- /dev/null +++ b/src/common/guards/jwt-auth.guard.ts @@ -0,0 +1,65 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { JwtService } from '@nestjs/jwt'; +import { Request } from 'express'; +import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; + +const DEFAULT_JWT_SECRET = 'corp_website_secret_key_2026ai'; + +/** + * JWT 全局守卫 + * - 通过 @Public() 装饰的接口跳过校验(前台公开接口) + * - 其余接口要求请求头携带 Authorization: Bearer + */ +@Injectable() +export class JwtAuthGuard implements CanActivate { + constructor( + private readonly reflector: Reflector, + private readonly jwtService: JwtService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (isPublic) { + return true; + } + + const request = context.switchToHttp().getRequest(); + const token = this.extractToken(request); + if (!token) { + throw new UnauthorizedException('未登录或登录已过期'); + } + + try { + const payload = await this.jwtService.verifyAsync<{ + id: number; + username: string; + nickname: string; + role: 'super_admin' | 'normal'; + }>(token, { + secret: process.env.JWT_SECRET ?? DEFAULT_JWT_SECRET, + }); + request.admin = payload; + } catch { + throw new UnauthorizedException('登录凭证无效'); + } + return true; + } + + private extractToken(request: Request): string | null { + const authHeader = request.headers.authorization ?? ''; + const [type, token] = authHeader.split(' '); + if (type === 'Bearer' && token) { + return token.trim(); + } + return null; + } +} diff --git a/src/common/guards/roles.guard.ts b/src/common/guards/roles.guard.ts new file mode 100644 index 0000000..f36e5fb --- /dev/null +++ b/src/common/guards/roles.guard.ts @@ -0,0 +1,35 @@ +import { + CanActivate, + ExecutionContext, + ForbiddenException, + Injectable, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { ROLES_KEY, type AdminRole } from '../decorators/roles.decorator'; + +/** + * 角色守卫(全局注册,由 @Roles() 元数据驱动) + * - 未声明 @Roles() 的接口:放行(仅需登录) + * - 声明 @Roles('super_admin') 的接口:要求 request.admin.role === 'super_admin' + */ +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private readonly reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const required = this.reflector.getAllAndOverride( + ROLES_KEY, + [context.getHandler(), context.getClass()], + ); + if (!required || required.length === 0) { + return true; + } + + const request = context.switchToHttp().getRequest(); + const role: AdminRole | undefined = request.admin?.role; + if (!role || !required.includes(role)) { + throw new ForbiddenException('权限不足,需要超级管理员账号'); + } + return true; + } +} diff --git a/src/common/interceptors/index.ts b/src/common/interceptors/index.ts new file mode 100644 index 0000000..0546468 --- /dev/null +++ b/src/common/interceptors/index.ts @@ -0,0 +1 @@ +export * from './transform.interceptor'; diff --git a/src/common/interceptors/transform.interceptor.ts b/src/common/interceptors/transform.interceptor.ts new file mode 100644 index 0000000..4626221 --- /dev/null +++ b/src/common/interceptors/transform.interceptor.ts @@ -0,0 +1,33 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApiResponse } from '../dto/api-response.dto'; + +/** + * 全局统一响应拦截器 + * 把所有 controller 返回值包装成 { code, msg, data } 结构 + */ +@Injectable() +export class TransformInterceptor + implements NestInterceptor> +{ + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable> { + return next.handle().pipe( + map((data) => { + // 如果已经是 ApiResponse,则原样返回 + if (data instanceof ApiResponse) { + return data; + } + return ApiResponse.success(data) as ApiResponse; + }), + ); + } +} diff --git a/src/common/middlewares/index.ts b/src/common/middlewares/index.ts new file mode 100644 index 0000000..c861c6a --- /dev/null +++ b/src/common/middlewares/index.ts @@ -0,0 +1 @@ +export * from './upload.middleware'; diff --git a/src/common/middlewares/upload.middleware.ts b/src/common/middlewares/upload.middleware.ts new file mode 100644 index 0000000..b3d3bd0 --- /dev/null +++ b/src/common/middlewares/upload.middleware.ts @@ -0,0 +1,63 @@ +import { + Injectable, + NestMiddleware, + BadRequestException, +} from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; +import multer, { Multer } from 'multer'; +import path from 'path'; +import fs from 'fs'; + +const ALLOWED_MIME = ['image/jpeg', 'image/png', 'image/webp']; +const MAX_SIZE = 2 * 1024 * 1024; // 2M + +/** + * 文件上传中间件(按日期分文件夹) + * 单图字段名固定:file + */ +@Injectable() +export class UploadMiddleware implements NestMiddleware { + private readonly upload: Multer; + + constructor() { + const root = process.env.UPLOAD_ROOT ?? './uploads'; + this.upload = multer({ + storage: multer.diskStorage({ + destination: (_req, _file, cb) => { + const date = new Date(); + const dir = path.join( + process.cwd(), + root.replace(/^\.\/?/, ''), + `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}`, + ); + fs.mkdirSync(dir, { recursive: true }); + cb(null, dir); + }, + filename: (_req, file, cb) => { + const ext = path.extname(file.originalname).toLowerCase(); + const name = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`; + cb(null, name); + }, + }), + fileFilter: (_req, file, cb) => { + if (ALLOWED_MIME.includes(file.mimetype)) { + cb(null, true); + } else { + // FileFilterCallback 重载:错误时只传 Error,不传 acceptFile + cb(new Error('仅支持 jpg/png/jpeg/webp 格式图片')); + } + }, + limits: { fileSize: MAX_SIZE }, + }); + } + + use(req: Request, _res: Response, next: NextFunction): void { + this.upload.single('file')(req, _res, (err) => { + if (err) { + next(new BadRequestException(err.message ?? '文件上传失败')); + return; + } + next(); + }); + } +} diff --git a/src/config/configuration.ts b/src/config/configuration.ts new file mode 100644 index 0000000..5d262f0 --- /dev/null +++ b/src/config/configuration.ts @@ -0,0 +1,47 @@ +/** + * 全局环境变量解析 + * 所有配置项从这里统一读取,禁止在业务代码中直接 process.env + */ +export interface AppConfig { + /** 服务端口 */ + port: number; + /** 运行环境 */ + nodeEnv: string; + /** 文件上传根目录 */ + uploadRoot: string; + /** 数据库配置 */ + database: { + host: string; + port: number; + user: string; + password: string; + name: string; + }; + /** JWT 配置 */ + jwt: { + secret: string; + expiresIn: string; + }; +} + +export default (): AppConfig => { + const port = parseInt(process.env.PORT ?? '3001', 10); + const dbPort = parseInt(process.env.DB_PORT ?? '3306', 10); + + return { + port: Number.isNaN(port) ? 3001 : port, + nodeEnv: process.env.NODE_ENV ?? 'development', + uploadRoot: process.env.UPLOAD_ROOT ?? './uploads', + database: { + host: process.env.DB_HOST ?? '127.0.0.1', + port: Number.isNaN(dbPort) ? 3306 : dbPort, + user: process.env.DB_USER ?? 'root', + password: process.env.DB_PASSWORD ?? '', + name: process.env.DB_NAME ?? 'corp_website', + }, + jwt: { + secret: process.env.JWT_SECRET ?? 'corp_website_secret_key_2026ai', + expiresIn: process.env.JWT_EXPIRES_IN ?? '7d', + }, + }; +}; diff --git a/src/config/swagger.config.ts b/src/config/swagger.config.ts new file mode 100644 index 0000000..0bdc1b3 --- /dev/null +++ b/src/config/swagger.config.ts @@ -0,0 +1,22 @@ +import { DocumentBuilder } from '@nestjs/swagger'; + +/** + * Swagger 全局配置 + * 访问地址:http://localhost:3001/api-docs + */ +export const swaggerOptions = new DocumentBuilder() + .setTitle('企业官方网站 接口文档') + .setDescription('Next14 + Nest10 + JWT + MySQL8 全栈项目后端 API') + .setVersion('1.0.0') + .addBearerAuth( + { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'Authorization', + description: '请输入 JWT Token,格式:Bearer xxxxx', + in: 'header', + }, + 'admin-token', + ) + .build(); diff --git a/src/entities/admin.entity.ts b/src/entities/admin.entity.ts new file mode 100644 index 0000000..0ce6fcc --- /dev/null +++ b/src/entities/admin.entity.ts @@ -0,0 +1,43 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 后台管理员表 admin + */ +@Entity('admin') +export class Admin { + /** 主键ID */ + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 登录账号 */ + @Column({ type: 'varchar', length: 50, comment: '登录账号' }) + username: string; + + /** 加密密码(bcrypt) */ + @Column({ type: 'varchar', length: 100, comment: '加密密码', select: false }) + password: string; + + /** 管理员名称 */ + @Column({ type: 'varchar', length: 50, comment: '管理员名称' }) + nickname: string; + + /** 头像地址 */ + @Column({ type: 'varchar', length: 255, default: '', comment: '头像地址' }) + avatar: string; + + /** 角色:super_admin 超级管理员 / normal 普通管理员 */ + @Column({ type: 'varchar', length: 20, default: 'normal', comment: '角色' }) + role: 'super_admin' | 'normal'; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/banner.entity.ts b/src/entities/banner.entity.ts new file mode 100644 index 0000000..69b8a46 --- /dev/null +++ b/src/entities/banner.entity.ts @@ -0,0 +1,42 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 首页轮播图 banner + */ +@Entity('banner') +export class Banner { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 轮播标题 */ + @Column({ type: 'varchar', length: 100, comment: '轮播标题' }) + title: string; + + /** 图片地址 */ + @Column({ type: 'varchar', length: 255, comment: '图片地址' }) + image: string; + + /** 跳转链接 */ + @Column({ type: 'varchar', length: 255, default: '', comment: '跳转链接' }) + link: string; + + /** 排序值(越小越靠前) */ + @Column({ type: 'int', default: 0, comment: '排序值' }) + sort: number; + + /** 是否展示 1是 0否 */ + @Column({ type: 'tinyint', name: 'is_show', default: 1, comment: '是否展示 1是0否' }) + isShow: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/index.ts b/src/entities/index.ts new file mode 100644 index 0000000..83fade1 --- /dev/null +++ b/src/entities/index.ts @@ -0,0 +1,9 @@ +export * from './admin.entity'; +export * from './banner.entity'; +export * from './message.entity'; +export * from './news-category.entity'; +export * from './news.entity'; +export * from './product-category.entity'; +export * from './product.entity'; +export * from './site-config.entity'; +export * from './team.entity'; diff --git a/src/entities/manual.entity.ts b/src/entities/manual.entity.ts new file mode 100644 index 0000000..6f8d2e4 --- /dev/null +++ b/src/entities/manual.entity.ts @@ -0,0 +1,78 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 使用手册表 manual + * - 自引用 parentId 实现多级树形结构 + * - type=0 为目录节点(无 content),type=1 为文档节点(有 content) + */ +@Entity('manual') +export class Manual { + /** 主键ID */ + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 父节点ID,NULL 表示根节点 */ + @Column({ + name: 'parent_id', + type: 'int', + unsigned: true, + nullable: true, + comment: '父节点ID,NULL=根节点', + }) + parentId: number | null; + + /** 标题 */ + @Column({ type: 'varchar', length: 200, comment: '标题' }) + title: string; + + /** 节点类型:0=目录 1=文档 */ + @Column({ + type: 'tinyint', + default: 1, + comment: '类型:0=目录节点 1=文档节点', + }) + type: number; + + /** 文档正文(富文本 HTML 或 Markdown;目录节点为 null) */ + @Column({ + type: 'longtext', + nullable: true, + comment: '文档正文(目录节点为空)', + }) + content: string | null; + + /** 正文格式:html=富文本 markdown=Markdown */ + @Column({ + name: 'content_format', + type: 'varchar', + length: 10, + default: 'html', + comment: '正文格式:html=富文本 markdown=Markdown', + }) + contentFormat: 'html' | 'markdown'; + + /** 排序值,越小越靠前 */ + @Column({ type: 'int', default: 0, comment: '排序值' }) + sort: number; + + /** 是否显示:1显示 0隐藏 */ + @Column({ + name: 'is_show', + type: 'tinyint', + default: 1, + comment: '是否显示 1是0否', + }) + isShow: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/message.entity.ts b/src/entities/message.entity.ts new file mode 100644 index 0000000..c80eeac --- /dev/null +++ b/src/entities/message.entity.ts @@ -0,0 +1,38 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, +} from 'typeorm'; + +/** + * 客户留言 message + */ +@Entity('message') +export class Message { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 访客姓名 */ + @Column({ type: 'varchar', length: 50, comment: '访客姓名' }) + name: string; + + /** 联系电话 */ + @Column({ type: 'varchar', length: 20, comment: '联系电话' }) + phone: string; + + /** 邮箱 */ + @Column({ type: 'varchar', length: 100, default: '', comment: '邮箱' }) + email: string; + + /** 留言内容 */ + @Column({ type: 'text', comment: '留言内容' }) + content: string; + + /** 0未读 1已读 */ + @Column({ type: 'tinyint', name: 'is_read', default: 0, comment: '0未读1已读' }) + isRead: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; +} diff --git a/src/entities/news-category.entity.ts b/src/entities/news-category.entity.ts new file mode 100644 index 0000000..bc2998a --- /dev/null +++ b/src/entities/news-category.entity.ts @@ -0,0 +1,31 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 新闻分类 news_category + */ +@Entity('news_category') +export class NewsCategory { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + @Column({ type: 'varchar', length: 100, comment: '分类名称' }) + name: string; + + @Column({ type: 'int', default: 0, comment: '排序' }) + sort: number; + + @Column({ type: 'tinyint', name: 'is_show', default: 1 }) + isShow: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/news.entity.ts b/src/entities/news.entity.ts new file mode 100644 index 0000000..c971195 --- /dev/null +++ b/src/entities/news.entity.ts @@ -0,0 +1,58 @@ +import { + Column, + CreateDateColumn, + Entity, + Index, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { NewsCategory } from './news-category.entity'; + +/** + * 新闻资讯 news + */ +@Entity('news') +@Index('idx_category', ['categoryId']) +export class News { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + @Column({ type: 'int', unsigned: true, name: 'category_id' }) + categoryId: number; + + @ManyToOne(() => NewsCategory) + @JoinColumn({ name: 'category_id', referencedColumnName: 'id' }) + category?: NewsCategory; + + /** 新闻标题 */ + @Column({ type: 'varchar', length: 200, comment: '新闻标题' }) + title: string; + + /** 封面图 */ + @Column({ type: 'varchar', length: 255, default: '', comment: '封面图' }) + cover: string; + + /** 简介 */ + @Column({ type: 'varchar', length: 500, default: '', comment: '简介' }) + intro: string; + + /** 新闻正文富文本 */ + @Column({ type: 'longtext', comment: '新闻正文富文本' }) + content: string; + + /** 是否置顶 */ + @Column({ type: 'tinyint', name: 'is_top', default: 0, comment: '是否置顶' }) + isTop: number; + + /** 状态:1发布 0草稿 */ + @Column({ type: 'tinyint', default: 1, comment: '1发布 0草稿' }) + status: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/product-category.entity.ts b/src/entities/product-category.entity.ts new file mode 100644 index 0000000..512dd84 --- /dev/null +++ b/src/entities/product-category.entity.ts @@ -0,0 +1,34 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 产品分类 product_category + */ +@Entity('product_category') +export class ProductCategory { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 分类名称 */ + @Column({ type: 'varchar', length: 100, comment: '分类名称' }) + name: string; + + /** 排序值 */ + @Column({ type: 'int', default: 0, comment: '排序' }) + sort: number; + + /** 是否展示 */ + @Column({ type: 'tinyint', name: 'is_show', default: 1 }) + isShow: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/product.entity.ts b/src/entities/product.entity.ts new file mode 100644 index 0000000..38b8e70 --- /dev/null +++ b/src/entities/product.entity.ts @@ -0,0 +1,60 @@ +import { + Column, + CreateDateColumn, + Entity, + Index, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { ProductCategory } from './product-category.entity'; + +/** + * 产品 product + */ +@Entity('product') +@Index('idx_category', ['categoryId']) +export class Product { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 分类ID */ + @Column({ type: 'int', unsigned: true, name: 'category_id', comment: '分类ID' }) + categoryId: number; + + /** 所属分类(仅用于查询关联,不映射物理外键) */ + @ManyToOne(() => ProductCategory) + @JoinColumn({ name: 'category_id', referencedColumnName: 'id' }) + category?: ProductCategory; + + /** 产品名称 */ + @Column({ type: 'varchar', length: 100, comment: '产品名称' }) + name: string; + + /** 封面图 */ + @Column({ type: 'varchar', length: 255, comment: '封面图' }) + cover: string; + + /** 简短描述 */ + @Column({ type: 'text', nullable: true, comment: '简短描述' }) + desc: string | null; + + /** 详情富文本 */ + @Column({ type: 'longtext', nullable: true, comment: '详情富文本' }) + content: string | null; + + /** 排序值 */ + @Column({ type: 'int', default: 0 }) + sort: number; + + /** 上下架 1上架 0下架 */ + @Column({ type: 'tinyint', name: 'is_show', default: 1, comment: '上下架' }) + isShow: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/site-config.entity.ts b/src/entities/site-config.entity.ts new file mode 100644 index 0000000..a18da66 --- /dev/null +++ b/src/entities/site-config.entity.ts @@ -0,0 +1,58 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 网站基础配置 site_config(仅单条数据 id=1) + */ +@Entity('site_config') +export class SiteConfig { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 网站名称 */ + @Column({ name: 'site_name', type: 'varchar', length: 100, comment: '网站名称' }) + siteName: string; + + /** logo 图片 */ + @Column({ type: 'varchar', length: 255, default: '', comment: 'logo图片' }) + logo: string; + + /** 联系电话 */ + @Column({ type: 'varchar', length: 50, default: '', comment: '联系电话' }) + tel: string; + + /** 公司地址 */ + @Column({ type: 'varchar', length: 255, default: '', comment: '公司地址' }) + address: string; + + /** 商务邮箱 */ + @Column({ type: 'varchar', length: 100, default: '', comment: '商务邮箱' }) + email: string; + + /** 底部版权 */ + @Column({ type: 'varchar', length: 255, default: '', comment: '底部版权' }) + copyright: string; + + /** 备案号 */ + @Column({ type: 'varchar', length: 100, default: '', comment: '备案号' }) + icp: string; + + /** 企业简介标题 */ + @Column({ name: 'about_title', type: 'varchar', length: 100, default: '', comment: '企业简介标题' }) + aboutTitle: string; + + /** 企业简介正文 */ + @Column({ name: 'about_content', type: 'longtext', nullable: true, comment: '企业简介正文' }) + aboutContent: string | null; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/entities/team.entity.ts b/src/entities/team.entity.ts new file mode 100644 index 0000000..5a50e68 --- /dev/null +++ b/src/entities/team.entity.ts @@ -0,0 +1,44 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +/** + * 团队成员 team + */ +@Entity('team') +export class Team { + @PrimaryGeneratedColumn({ type: 'int', unsigned: true }) + id: number; + + /** 姓名 */ + @Column({ type: 'varchar', length: 50, comment: '姓名' }) + name: string; + + /** 职位 */ + @Column({ type: 'varchar', length: 100, comment: '职位' }) + position: string; + + /** 头像 */ + @Column({ type: 'varchar', length: 255, comment: '头像' }) + avatar: string; + + /** 个人简介 */ + @Column({ type: 'text', nullable: true, comment: '个人简介' }) + desc: string | null; + + @Column({ type: 'int', default: 0 }) + sort: number; + + @Column({ type: 'tinyint', name: 'is_show', default: 1 }) + isShow: number; + + @CreateDateColumn({ name: 'created_at', type: 'datetime' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'datetime' }) + updatedAt: Date; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..588e516 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,167 @@ +import { NestFactory } from '@nestjs/core'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { ValidationPipe, Logger, type INestApplication } from '@nestjs/common'; +import { SwaggerModule } from '@nestjs/swagger'; +import { WinstonModule } from 'nest-winston'; +import * as winston from 'winston'; +import { DataSource } from 'typeorm'; + +import { AppModule } from './app.module'; +import { swaggerOptions } from './config/swagger.config'; +import { AllExceptionsFilter } from './common/filters'; +import { TransformInterceptor } from './common/interceptors'; + +/** + * 启动期自动升级:确保 admin 表存在 role 字段(角色权限控制所需)。 + * - 通过 information_schema 检测,缺失时自动 ALTER TABLE 添加 + * - 默认 'normal',并将 id=1 的初始管理员置为 'super_admin' + * - 已有 role 字段时跳过,幂等 + */ +async function ensureAdminRoleColumn(app: INestApplication): Promise { + const dataSource = app.get(DataSource); + const rows = (await dataSource.query( + `SELECT COUNT(*) AS cnt + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'admin' + AND COLUMN_NAME = 'role'`, + )) as Array<{ cnt: number | string }>; + const exists = Number(rows?.[0]?.cnt ?? 0) > 0; + if (exists) return; + + Logger.warn('检测到 admin 表缺少 role 字段,正在自动升级数据库...', 'Migration'); + await dataSource.query( + "ALTER TABLE `admin` ADD COLUMN `role` varchar(20) NOT NULL DEFAULT 'normal' " + + "COMMENT '角色:super_admin 超级管理员 / normal 普通管理员' AFTER `avatar`", + ); + await dataSource.query( + "UPDATE `admin` SET `role` = 'super_admin' WHERE `id` = 1", + ); + Logger.log('admin 表 role 字段已自动升级完成,初始 admin 已置为超级管理员', 'Migration'); +} + +/** + * 启动期自动升级:确保 manual 表存在(使用手册模块)。 + * - 表不存在时自动 CREATE TABLE + * - 已存在则跳过,幂等 + */ +async function ensureManualTable(app: INestApplication): Promise { + const dataSource = app.get(DataSource); + const rows = (await dataSource.query( + `SELECT COUNT(*) AS cnt + FROM information_schema.TABLES + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'manual'`, + )) as Array<{ cnt: number | string }>; + const exists = Number(rows?.[0]?.cnt ?? 0) > 0; + if (exists) return; + + Logger.warn('检测到 manual 表不存在,正在自动创建...', 'Migration'); + await dataSource.query( + `CREATE TABLE \`manual\` ( + \`id\` int unsigned NOT NULL AUTO_INCREMENT, + \`parent_id\` int unsigned DEFAULT NULL COMMENT '父节点ID,NULL=根节点', + \`title\` varchar(200) NOT NULL COMMENT '标题', + \`type\` tinyint(1) NOT NULL DEFAULT 1 COMMENT '类型:0=目录节点 1=文档节点', + \`content\` longtext COMMENT '文档正文(目录节点为空)', + \`content_format\` varchar(10) NOT NULL DEFAULT 'html' COMMENT '正文格式:html=富文本 markdown=Markdown', + \`sort\` int NOT NULL DEFAULT 0 COMMENT '排序值', + \`is_show\` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否显示 1是0否', + \`created_at\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + \`updated_at\` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (\`id\`), + KEY \`idx_parent\` (\`parent_id\`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='使用手册'`, + ); + Logger.log('manual 表已自动创建完成', 'Migration'); +} + +/** + * 启动期自动升级:确保 manual 表存在 content_format 字段(持久化正文格式)。 + * - 仅在 manual 表已存在但缺少该字段时执行 + * - 幂等 + */ +async function ensureManualContentFormatColumn( + app: INestApplication, +): Promise { + const dataSource = app.get(DataSource); + const rows = (await dataSource.query( + `SELECT COUNT(*) AS cnt + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'manual' + AND COLUMN_NAME = 'content_format'`, + )) as Array<{ cnt: number | string }>; + const exists = Number(rows?.[0]?.cnt ?? 0) > 0; + if (exists) return; + + Logger.warn( + '检测到 manual 表缺少 content_format 字段,正在自动升级数据库...', + 'Migration', + ); + await dataSource.query( + "ALTER TABLE `manual` ADD COLUMN `content_format` varchar(10) NOT NULL DEFAULT 'html' " + + "COMMENT '正文格式:html=富文本 markdown=Markdown' AFTER `content`", + ); + Logger.log( + 'manual 表 content_format 字段已自动升级完成', + 'Migration', + ); +} + +async function bootstrap(): Promise { + const app = await NestFactory.create(AppModule, { + logger: WinstonModule.createLogger({ + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.timestamp(), + winston.format.colorize(), + winston.format.printf(({ level, message, timestamp }) => { + return `${timestamp} ${level}: ${message}`; + }), + ), + }), + ], + }), + }); + + app.setGlobalPrefix('api'); + + // 全局管道(DTO + class-validator) + // 注意:app.module.ts 的 APP_PIPE/APP_FILTER/APP_INTERCEPTOR 已删除, + // 这里统一注册避免重复执行;APP_GUARD(JwtAuthGuard 需要 DI)保留在 app.module.ts。 + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + forbidNonWhitelisted: false, + }), + ); + + // 全局过滤器 + 响应拦截器 + app.useGlobalFilters(new AllExceptionsFilter()); + app.useGlobalInterceptors(new TransformInterceptor()); + + // 跨域放行(前端 3000 -> 后端 3001) + app.enableCors({ + origin: true, + credentials: true, + }); + + // Swagger 文档 + const document = SwaggerModule.createDocument(app, swaggerOptions); + SwaggerModule.setup('api-docs', app, document); + + // 启动期自动迁移:补齐 admin.role 字段、确保 manual 表存在 + await ensureAdminRoleColumn(app); + await ensureManualTable(app); + await ensureManualContentFormatColumn(app); + + const port = parseInt(process.env.PORT ?? '3001', 10); + await app.listen(port); + Logger.log(`🚀 后端服务已启动: http://localhost:${port}`, 'Bootstrap'); + Logger.log(`📘 Swagger 文档地址: http://localhost:${port}/api-docs`, 'Bootstrap'); +} + +void bootstrap(); diff --git a/src/modules/admin-user/admin-user.controller.ts b/src/modules/admin-user/admin-user.controller.ts new file mode 100644 index 0000000..4f11342 --- /dev/null +++ b/src/modules/admin-user/admin-user.controller.ts @@ -0,0 +1,87 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { CurrentAdmin, Roles } from '@/common/decorators'; +import { Admin } from '@/entities/admin.entity'; +import { AdminUserService } from './admin-user.service'; +import { + CreateAdminUserDto, + QueryAdminUserDto, + ResetPasswordDto, + UpdateAdminUserDto, +} from './dto/admin-user.dto'; + +@ApiTags('管理员账号 AdminUser') +@Controller() +export class AdminUserController { + constructor(private readonly service: AdminUserService) {} + + @ApiBearerAuth('admin-token') + @Get('admin/admin-user') + @ApiOperation({ summary: '后台-管理员账号分页' }) + paginate( + @Query() query: QueryAdminUserDto, + ): Promise> { + return this.service.paginate(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/admin-user/:id') + @ApiOperation({ summary: '后台-管理员账号详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/admin-user') + @Roles('super_admin') + @ApiOperation({ summary: '后台-新增管理员账号(仅超管)' }) + create(@Body() dto: CreateAdminUserDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/admin-user/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-修改管理员账号(名称/头像/角色,仅超管)' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateAdminUserDto, + @CurrentAdmin('id') currentAdminId: number, + ): Promise { + return this.service.update(id, dto, currentAdminId); + } + + @ApiBearerAuth('admin-token') + @Put('admin/admin-user/:id/password') + @Roles('super_admin') + @ApiOperation({ summary: '后台-重置管理员密码(仅超管)' }) + resetPassword( + @Param('id', ParseIntPipe) id: number, + @Body() dto: ResetPasswordDto, + ): Promise { + return this.service.resetPassword(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/admin-user/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除管理员账号(不能删自己,仅超管)' }) + remove( + @Param('id', ParseIntPipe) id: number, + @CurrentAdmin('id') currentAdminId: number, + ): Promise { + return this.service.remove(id, currentAdminId); + } +} diff --git a/src/modules/admin-user/admin-user.module.ts b/src/modules/admin-user/admin-user.module.ts new file mode 100644 index 0000000..9831aee --- /dev/null +++ b/src/modules/admin-user/admin-user.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Admin } from '@/entities/admin.entity'; +import { AdminUserController } from './admin-user.controller'; +import { AdminUserService } from './admin-user.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Admin])], + controllers: [AdminUserController], + providers: [AdminUserService], +}) +export class AdminUserModule {} diff --git a/src/modules/admin-user/admin-user.service.ts b/src/modules/admin-user/admin-user.service.ts new file mode 100644 index 0000000..cdcef15 --- /dev/null +++ b/src/modules/admin-user/admin-user.service.ts @@ -0,0 +1,107 @@ +import { + BadRequestException, + ConflictException, + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { Admin } from '@/entities/admin.entity'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import type { AdminRole } from '@/common/decorators'; +import { hashPassword } from '@/utils/crypto.util'; +import { + CreateAdminUserDto, + QueryAdminUserDto, + ResetPasswordDto, + UpdateAdminUserDto, +} from './dto/admin-user.dto'; + +@Injectable() +export class AdminUserService { + constructor( + @InjectRepository(Admin) + private readonly repo: Repository, + ) {} + + async paginate(query: QueryAdminUserDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 10; + const qb = this.repo.createQueryBuilder('a').orderBy('a.id', 'DESC'); + if (query.keyword) { + qb.andWhere('(a.username LIKE :kw OR a.nickname LIKE :kw)', { + kw: `%${query.keyword}%`, + }); + } + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('管理员不存在'); + return item; + } + + async create(dto: CreateAdminUserDto): Promise { + const exists = await this.repo.findOne({ + where: { username: dto.username }, + }); + if (exists) { + throw new ConflictException('登录账号已存在'); + } + const hashed = await hashPassword(dto.password); + const entity = this.repo.create({ + username: dto.username, + password: hashed, + nickname: dto.nickname, + avatar: dto.avatar ?? '', + role: dto.role ?? 'normal', + }); + return this.repo.save(entity); + } + + /** + * 修改管理员账号(名称/头像/角色) + * - 不允许超管把自己降级为普通(避免系统没有超管) + */ + async update( + id: number, + dto: UpdateAdminUserDto, + currentAdminId: number, + ): Promise { + const item = await this.detail(id); + item.nickname = dto.nickname; + item.avatar = dto.avatar ?? ''; + if (dto.role && dto.role !== item.role) { + if ( + id === currentAdminId && + item.role === 'super_admin' && + dto.role !== 'super_admin' + ) { + throw new ForbiddenException('不能将自己降级为普通管理员'); + } + item.role = dto.role; + } + await this.repo.save(item); + } + + async resetPassword(id: number, dto: ResetPasswordDto): Promise { + const item = await this.detail(id); + item.password = await hashPassword(dto.newPassword); + await this.repo.save(item); + } + + async remove(id: number, currentAdminId: number): Promise { + if (id === currentAdminId) { + throw new BadRequestException('不能删除当前登录的管理员账号'); + } + await this.detail(id); + await this.repo.delete(id); + } +} diff --git a/src/modules/admin-user/dto/admin-user.dto.ts b/src/modules/admin-user/dto/admin-user.dto.ts new file mode 100644 index 0000000..29dbd1d --- /dev/null +++ b/src/modules/admin-user/dto/admin-user.dto.ts @@ -0,0 +1,107 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsIn, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, + MinLength, +} from 'class-validator'; +import type { AdminRole } from '@/common/decorators'; + +export class CreateAdminUserDto { + @ApiProperty({ + description: '登录账号(手机号/邮箱/自定义)', + example: 'admin', + }) + @IsString() + @IsNotEmpty({ message: '登录账号不能为空' }) + @MinLength(2, { message: '账号至少 2 个字符' }) + @MaxLength(50, { message: '账号最多 50 个字符' }) + username: string; + + @ApiProperty({ description: '初始密码(至少 6 位)', example: '123456' }) + @IsString() + @IsNotEmpty({ message: '密码不能为空' }) + @MinLength(6, { message: '密码至少 6 位' }) + @MaxLength(50, { message: '密码最多 50 个字符' }) + password: string; + + @ApiProperty({ description: '管理员名称' }) + @IsString() + @IsNotEmpty({ message: '名称不能为空' }) + @MaxLength(50) + nickname: string; + + @ApiPropertyOptional({ description: '头像地址' }) + @IsString() + @IsOptional() + avatar?: string; + + @ApiPropertyOptional({ + description: '角色:super_admin 超级管理员 / normal 普通管理员', + default: 'normal', + enum: ['super_admin', 'normal'], + }) + @IsString() + @IsIn(['super_admin', 'normal'], { message: '角色取值不合法' }) + @IsOptional() + role?: AdminRole; +} + +export class UpdateAdminUserDto { + @ApiProperty({ description: '管理员名称' }) + @IsString() + @IsNotEmpty({ message: '名称不能为空' }) + @MaxLength(50) + nickname: string; + + @ApiPropertyOptional({ description: '头像地址' }) + @IsString() + @IsOptional() + avatar?: string; + + @ApiPropertyOptional({ + description: '角色:super_admin 超级管理员 / normal 普通管理员', + enum: ['super_admin', 'normal'], + }) + @IsString() + @IsIn(['super_admin', 'normal'], { message: '角色取值不合法' }) + @IsOptional() + role?: AdminRole; +} + +export class ResetPasswordDto { + @ApiProperty({ description: '新密码(至少 6 位)' }) + @IsString() + @IsNotEmpty({ message: '新密码不能为空' }) + @MinLength(6, { message: '新密码至少 6 位' }) + @MaxLength(50, { message: '新密码最多 50 个字符' }) + newPassword: string; +} + +export class QueryAdminUserDto { + @ApiPropertyOptional({ description: '页码', default: 1 }) + @IsInt() + @Min(1) + @IsOptional() + @Type(() => Number) + page?: number; + + @ApiPropertyOptional({ description: '每页数量', default: 10 }) + @IsInt() + @Min(1) + @Max(100) + @IsOptional() + @Type(() => Number) + pageSize?: number; + + @ApiPropertyOptional({ description: '关键词(账号/名称)' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts new file mode 100644 index 0000000..b15fd68 --- /dev/null +++ b/src/modules/auth/auth.controller.ts @@ -0,0 +1,40 @@ +import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { AuthService } from './auth.service'; +import { + ChangePasswordDto, + LoginDto, + AdminLoginResult, +} from './dto/login.dto'; +import { CurrentAdmin, Public } from '@/common/decorators'; + +@ApiTags('管理员鉴权 Auth') +@Controller('admin/auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Public() + @Post('login') + @ApiOperation({ summary: '管理员登录', description: '无需 token,返回 JWT' }) + login(@Body() dto: LoginDto): Promise { + return this.authService.login(dto); + } + + @ApiBearerAuth('admin-token') + @Get('profile') + @ApiOperation({ summary: '获取当前登录管理员信息' }) + profile(@CurrentAdmin('id') adminId: number) { + return this.authService.getProfile(adminId); + } + + @ApiBearerAuth('admin-token') + @Post('change-password') + @ApiOperation({ summary: '修改当前管理员密码' }) + changePassword( + @CurrentAdmin('id') adminId: number, + @Body() dto: ChangePasswordDto, + ): Promise { + return this.authService.changePassword(adminId, dto); + } +} diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts new file mode 100644 index 0000000..9edd1db --- /dev/null +++ b/src/modules/auth/auth.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Admin } from '@/entities/admin.entity'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; + +@Module({ + // JwtModule 已在 AppModule 以 global: true 注册,无需再 import + imports: [TypeOrmModule.forFeature([Admin])], + controllers: [AuthController], + providers: [AuthService], + exports: [AuthService], +}) +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..5abb904 --- /dev/null +++ b/src/modules/auth/auth.service.ts @@ -0,0 +1,109 @@ +import { + BadRequestException, + Injectable, + NotFoundException, + UnauthorizedException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { JwtService } from '@nestjs/jwt'; + +import { Admin } from '@/entities/admin.entity'; +import { comparePassword, hashPassword } from '@/utils/crypto.util'; +import { + AdminLoginResult, + ChangePasswordDto, + LoginDto, +} from './dto/login.dto'; + +/** 默认初始密码(init_db.sql 中管理员 password 为空,首次启动会自动加密写入此密码) */ +const DEFAULT_INIT_PASSWORD = '123456'; + +@Injectable() +export class AuthService { + constructor( + @InjectRepository(Admin) + private readonly adminRepo: Repository, + private readonly jwtService: JwtService, + ) {} + + /** + * 管理员登录 + * 兼容 init_db.sql 写入的空密码:首次登录会自动 bcrypt 加密 123456 + */ + async login(dto: LoginDto): Promise { + const admin = await this.adminRepo + .createQueryBuilder('a') + .addSelect('a.password') + .where('a.username = :username', { username: dto.username }) + .getOne(); + if (!admin) { + throw new UnauthorizedException('账号或密码错误'); + } + + // 初始空密码 → 自动加密默认密码 + if (!admin.password) { + admin.password = await hashPassword(DEFAULT_INIT_PASSWORD); + await this.adminRepo.update(admin.id, { password: admin.password }); + } + + const ok = await comparePassword(dto.password, admin.password); + if (!ok) { + throw new UnauthorizedException('账号或密码错误'); + } + + const token = await this.jwtService.signAsync({ + id: admin.id, + username: admin.username, + nickname: admin.nickname, + role: admin.role, + }); + + return { + token, + admin: { + id: admin.id, + username: admin.username, + nickname: admin.nickname, + avatar: admin.avatar ?? '', + role: admin.role, + }, + }; + } + + /** 获取当前登录管理员信息 */ + async getProfile(adminId: number): Promise { + return this.adminRepo.findOne({ where: { id: adminId } }); + } + + /** 修改密码 */ + async changePassword( + adminId: number, + dto: ChangePasswordDto, + ): Promise { + if (dto.oldPassword === dto.newPassword) { + throw new BadRequestException('新密码不能与原密码相同'); + } + const admin = await this.adminRepo + .createQueryBuilder('a') + .addSelect('a.password') + .where('a.id = :id', { id: adminId }) + .getOne(); + if (!admin) { + throw new NotFoundException('管理员不存在'); + } + + if (!admin.password) { + admin.password = await hashPassword(DEFAULT_INIT_PASSWORD); + await this.adminRepo.update(admin.id, { password: admin.password }); + } + + const ok = await comparePassword(dto.oldPassword, admin.password); + if (!ok) { + throw new BadRequestException('原密码错误'); + } + + const hashed = await hashPassword(dto.newPassword); + await this.adminRepo.update(admin.id, { password: hashed }); + } +} diff --git a/src/modules/auth/dto/login.dto.ts b/src/modules/auth/dto/login.dto.ts new file mode 100644 index 0000000..df0216c --- /dev/null +++ b/src/modules/auth/dto/login.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, MinLength, MaxLength } from 'class-validator'; + +export class LoginDto { + @ApiProperty({ description: '登录账号', example: 'admin' }) + @IsString() + @MinLength(2, { message: '账号至少 2 个字符' }) + @MaxLength(50, { message: '账号最多 50 个字符' }) + username: string; + + @ApiProperty({ description: '登录密码', example: '123456' }) + @IsString() + @MinLength(6, { message: '密码至少 6 个字符' }) + @MaxLength(50, { message: '密码最多 50 个字符' }) + password: string; +} + +export class ChangePasswordDto { + @ApiProperty({ description: '原密码' }) + @IsString() + @MinLength(6) + oldPassword: string; + + @ApiProperty({ description: '新密码(至少 6 位)' }) + @IsString() + @MinLength(6, { message: '新密码至少 6 位' }) + newPassword: string; +} + +export interface AdminLoginResult { + token: string; + admin: { + id: number; + username: string; + nickname: string; + avatar: string; + role: 'super_admin' | 'normal'; + }; +} diff --git a/src/modules/banner/banner.controller.ts b/src/modules/banner/banner.controller.ts new file mode 100644 index 0000000..89208f7 --- /dev/null +++ b/src/modules/banner/banner.controller.ts @@ -0,0 +1,76 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { BannerService } from './banner.service'; +import { + CreateBannerDto, + QueryBannerDto, + UpdateBannerDto, +} from './dto/banner.dto'; +import { Public, Roles } from '@/common/decorators'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { Banner } from '@/entities/banner.entity'; + +@ApiTags('轮播图 Banner') +@Controller() +export class BannerController { + constructor(private readonly service: BannerService) {} + + // ---------- 前台公开 ---------- + @Public() + @Get('public/banner') + @ApiOperation({ summary: '前台-轮播图列表' }) + publicList(): Promise { + return this.service.findPublicList(); + } + + // ---------- 后台 ---------- + @ApiBearerAuth('admin-token') + @Get('admin/banner') + @ApiOperation({ summary: '后台-轮播图分页' }) + paginate(@Query() query: QueryBannerDto): Promise> { + return this.service.paginate(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/banner/:id') + @ApiOperation({ summary: '后台-轮播图详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/banner') + @ApiOperation({ summary: '后台-新增轮播图' }) + create(@Body() dto: CreateBannerDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/banner/:id') + @ApiOperation({ summary: '后台-修改轮播图' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateBannerDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/banner/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除轮播图(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/banner/banner.module.ts b/src/modules/banner/banner.module.ts new file mode 100644 index 0000000..7ab4130 --- /dev/null +++ b/src/modules/banner/banner.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Banner } from '@/entities/banner.entity'; +import { BannerController } from './banner.controller'; +import { BannerService } from './banner.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Banner])], + controllers: [BannerController], + providers: [BannerService], +}) +export class BannerModule {} diff --git a/src/modules/banner/banner.service.ts b/src/modules/banner/banner.service.ts new file mode 100644 index 0000000..f988b77 --- /dev/null +++ b/src/modules/banner/banner.service.ts @@ -0,0 +1,79 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { Banner } from '@/entities/banner.entity'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { + CreateBannerDto, + QueryBannerDto, + UpdateBannerDto, +} from './dto/banner.dto'; + +@Injectable() +export class BannerService { + constructor( + @InjectRepository(Banner) + private readonly repo: Repository, + ) {} + + // ---------- 前台公开 ---------- + /** 前台展示列表(仅 is_show=1,按 sort 升序) */ + async findPublicList(): Promise { + return this.repo.find({ + where: { isShow: 1 }, + order: { sort: 'ASC', id: 'DESC' }, + }); + } + + // ---------- 后台 ---------- + async paginate(query: QueryBannerDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 10; + const qb = this.repo.createQueryBuilder('b').orderBy('b.sort', 'ASC').addOrderBy('b.id', 'DESC'); + if (query.keyword) { + qb.andWhere('b.title LIKE :kw', { kw: `%${query.keyword}%` }); + } + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) { + throw new NotFoundException('轮播图不存在'); + } + return item; + } + + async create(dto: CreateBannerDto): Promise { + const entity = this.repo.create({ + title: dto.title, + image: dto.image, + link: dto.link ?? '', + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + return this.repo.save(entity); + } + + async update(id: number, dto: UpdateBannerDto): Promise { + const item = await this.detail(id); + Object.assign(item, { + title: dto.title, + image: dto.image, + link: dto.link ?? '', + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + await this.detail(id); + await this.repo.delete(id); + } +} diff --git a/src/modules/banner/dto/banner.dto.ts b/src/modules/banner/dto/banner.dto.ts new file mode 100644 index 0000000..535ae18 --- /dev/null +++ b/src/modules/banner/dto/banner.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateBannerDto { + @ApiProperty({ description: '轮播标题' }) + @IsString() + @IsNotEmpty({ message: '标题不能为空' }) + @MaxLength(100) + title: string; + + @ApiProperty({ description: '图片地址' }) + @IsString() + @IsNotEmpty({ message: '图片不能为空' }) + image: string; + + @ApiPropertyOptional({ description: '跳转链接', default: '' }) + @IsString() + @IsOptional() + @MaxLength(255) + link?: string; + + @ApiPropertyOptional({ description: '排序值', default: 0 }) + @IsInt() + @Min(0) + @Max(99999) + @IsOptional() + @Type(() => Number) + sort?: number; + + @ApiPropertyOptional({ description: '是否展示 1是0否', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isShow?: number; +} + +export class UpdateBannerDto extends CreateBannerDto {} + +export class QueryBannerDto { + @ApiPropertyOptional({ description: '页码', default: 1 }) + @IsInt() + @Min(1) + @IsOptional() + @Type(() => Number) + page?: number; + + @ApiPropertyOptional({ description: '每页数量', default: 10 }) + @IsInt() + @Min(1) + @Max(100) + @IsOptional() + @Type(() => Number) + pageSize?: number; + + @ApiPropertyOptional({ description: '关键词(标题)' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/manual/dto/manual.dto.ts b/src/modules/manual/dto/manual.dto.ts new file mode 100644 index 0000000..e102a5e --- /dev/null +++ b/src/modules/manual/dto/manual.dto.ts @@ -0,0 +1,76 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsIn, + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateManualDto { + @ApiPropertyOptional({ description: '父节点ID,NULL=根节点' }) + @IsInt() + @Min(1) + @IsOptional() + @Type(() => Number) + parentId?: number | null; + + @ApiProperty({ description: '标题' }) + @IsString() + @IsNotEmpty({ message: '标题不能为空' }) + @MaxLength(200) + title: string; + + @ApiPropertyOptional({ + description: '类型:0=目录节点 1=文档节点', + default: 1, + }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + type?: number; + + @ApiPropertyOptional({ description: '正文(富文本/Markdown,目录节点为空)' }) + @IsString() + @IsOptional() + content?: string | null; + + @ApiPropertyOptional({ + description: '正文格式:html=富文本 markdown=Markdown', + default: 'html', + }) + @IsIn(['html', 'markdown']) + @IsOptional() + contentFormat?: 'html' | 'markdown'; + + @ApiPropertyOptional({ description: '排序值', default: 0 }) + @IsInt() + @Min(0) + @Max(99999) + @IsOptional() + @Type(() => Number) + sort?: number; + + @ApiPropertyOptional({ description: '是否显示 1是0否', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isShow?: number; +} + +export class UpdateManualDto extends CreateManualDto {} + +export class QueryManualDto { + @ApiPropertyOptional({ description: '关键词(标题)' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/manual/manual.controller.ts b/src/modules/manual/manual.controller.ts new file mode 100644 index 0000000..61574a4 --- /dev/null +++ b/src/modules/manual/manual.controller.ts @@ -0,0 +1,91 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { Public, Roles } from '@/common/decorators'; +import { Manual } from '@/entities/manual.entity'; +import { ManualService, type ManualTreeNode } from './manual.service'; +import { + CreateManualDto, + QueryManualDto, + UpdateManualDto, +} from './dto/manual.dto'; + +@ApiTags('使用手册 Manual') +@Controller() +export class ManualController { + constructor(private readonly service: ManualService) {} + + // ---------- 前台 ---------- + @Public() + @Get('public/manual/tree') + @ApiOperation({ summary: '前台-使用手册树形菜单' }) + publicTree(): Promise { + return this.service.findPublicTree(); + } + + @Public() + @Get('public/manual/:id') + @ApiOperation({ summary: '前台-使用手册文档详情' }) + publicDetail( + @Param('id', ParseIntPipe) id: number, + ): Promise { + return this.service.findPublicDetail(id); + } + + // ---------- 后台 ---------- + @ApiBearerAuth('admin-token') + @Get('admin/manual') + @ApiOperation({ summary: '后台-使用手册全部节点(扁平)' }) + list(@Query() query: QueryManualDto): Promise { + return this.service.findAll(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/manual/tree') + @ApiOperation({ summary: '后台-使用手册树形结构' }) + tree(): Promise { + return this.service.findAdminTree(); + } + + @ApiBearerAuth('admin-token') + @Get('admin/manual/:id') + @ApiOperation({ summary: '后台-使用手册节点详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/manual') + @ApiOperation({ summary: '后台-新增使用手册节点' }) + create(@Body() dto: CreateManualDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/manual/:id') + @ApiOperation({ summary: '后台-修改使用手册节点' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateManualDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/manual/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除使用手册节点(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/manual/manual.module.ts b/src/modules/manual/manual.module.ts new file mode 100644 index 0000000..fecf28e --- /dev/null +++ b/src/modules/manual/manual.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Manual } from '@/entities/manual.entity'; +import { ManualController } from './manual.controller'; +import { ManualService } from './manual.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Manual])], + controllers: [ManualController], + providers: [ManualService], +}) +export class ManualModule {} diff --git a/src/modules/manual/manual.service.ts b/src/modules/manual/manual.service.ts new file mode 100644 index 0000000..ce80096 --- /dev/null +++ b/src/modules/manual/manual.service.ts @@ -0,0 +1,200 @@ +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, Like } from 'typeorm'; +import { Manual } from '@/entities/manual.entity'; +import { + CreateManualDto, + QueryManualDto, + UpdateManualDto, +} from './dto/manual.dto'; + +/** 树节点(不含正文,用于菜单渲染) */ +export interface ManualTreeNode { + id: number; + parentId: number | null; + title: string; + type: number; + sort: number; + isShow: number; + children: ManualTreeNode[]; +} + +@Injectable() +export class ManualService { + constructor( + @InjectRepository(Manual) + private readonly repo: Repository, + ) {} + + // ---------- 前台 ---------- + + /** 前台-获取可见的树形菜单(不含正文) */ + async findPublicTree(): Promise { + const nodes = await this.repo.find({ + where: { isShow: 1 }, + order: { sort: 'ASC', id: 'ASC' }, + }); + return this.buildTree(nodes); + } + + /** 前台-获取文档详情(含正文) */ + async findPublicDetail(id: number): Promise { + const item = await this.repo.findOne({ where: { id, isShow: 1 } }); + if (!item) throw new NotFoundException('文档不存在或已下架'); + return item; + } + + // ---------- 后台 ---------- + + /** 后台-全部节点(扁平,含 parent 信息,供列表/下拉用) */ + async findAll(query?: QueryManualDto): Promise { + const where = query?.keyword + ? { title: Like(`%${query.keyword}%`) } + : undefined; + return this.repo.find({ + where, + order: { sort: 'ASC', id: 'ASC' }, + }); + } + + /** 后台-获取所有节点的树形结构 */ + async findAdminTree(): Promise { + const nodes = await this.repo.find({ + order: { sort: 'ASC', id: 'ASC' }, + }); + return this.buildTree(nodes); + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('节点不存在'); + return item; + } + + async create(dto: CreateManualDto): Promise { + if (dto.parentId) { + const parent = await this.repo.findOne({ + where: { id: dto.parentId }, + }); + if (!parent) { + throw new BadRequestException('父节点不存在'); + } + } + const type = dto.type ?? 1; + return this.repo.save( + this.repo.create({ + parentId: dto.parentId ?? null, + title: dto.title, + type, + content: type === 0 ? null : dto.content ?? null, + contentFormat: + type === 0 ? 'html' : (dto.contentFormat ?? 'html'), + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }), + ); + } + + async update(id: number, dto: UpdateManualDto): Promise { + const item = await this.detail(id); + + // 校验父节点 + let newParentId: number | null = dto.parentId ?? null; + if (newParentId) { + if (newParentId === id) { + throw new BadRequestException('不能将自身设为父节点'); + } + const parent = await this.repo.findOne({ + where: { id: newParentId }, + }); + if (!parent) { + throw new BadRequestException('父节点不存在'); + } + // 简单环路检测:父节点不能是自身的子孙 + if (await this.isDescendant(id, newParentId)) { + throw new BadRequestException('不能将节点移动到其子节点下'); + } + } + + const type = dto.type ?? item.type; + Object.assign(item, { + parentId: newParentId, + title: dto.title, + type, + content: type === 0 ? null : dto.content ?? null, + contentFormat: + type === 0 + ? 'html' + : (dto.contentFormat ?? item.contentFormat ?? 'html'), + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + const item = await this.detail(id); + const childCount = await this.repo.count({ + where: { parentId: id }, + }); + if (childCount > 0) { + throw new BadRequestException('该节点下还有子节点,请先删除子节点'); + } + await this.repo.remove(item); + } + + // ---------- 工具 ---------- + + /** 判断 targetId 是否是 nodeId 的子孙(用于环路检测) */ + private async isDescendant( + nodeId: number, + targetId: number, + ): Promise { + const stack = [nodeId]; + const visited = new Set(); + while (stack.length > 0) { + const cur = stack.pop()!; + if (visited.has(cur)) continue; + visited.add(cur); + const children = await this.repo.find({ + where: { parentId: cur }, + select: ['id'], + }); + for (const c of children) { + if (c.id === targetId) return true; + stack.push(c.id); + } + } + return false; + } + + /** 扁平节点列表构建为树 */ + private buildTree(nodes: Manual[]): ManualTreeNode[] { + const map = new Map(); + const roots: ManualTreeNode[] = []; + for (const n of nodes) { + map.set(n.id, { + id: n.id, + parentId: n.parentId, + title: n.title, + type: n.type, + sort: n.sort, + isShow: n.isShow, + children: [], + }); + } + for (const n of nodes) { + const node = map.get(n.id)!; + if (n.parentId && map.has(n.parentId)) { + map.get(n.parentId)!.children.push(node); + } else { + roots.push(node); + } + } + return roots; + } +} diff --git a/src/modules/message/dto/message.dto.ts b/src/modules/message/dto/message.dto.ts new file mode 100644 index 0000000..a11977f --- /dev/null +++ b/src/modules/message/dto/message.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + IsEmail, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateMessageDto { + @ApiProperty({ description: '访客姓名' }) + @IsString() + @IsNotEmpty({ message: '姓名不能为空' }) + @MaxLength(50) + name: string; + + @ApiProperty({ description: '联系电话' }) + @IsString() + @IsNotEmpty({ message: '电话不能为空' }) + @MaxLength(20) + phone: string; + + @ApiPropertyOptional({ description: '邮箱' }) + @IsEmail({}, { message: '邮箱格式不正确' }) + @IsOptional() + @MaxLength(100) + email?: string; + + @ApiProperty({ description: '留言内容' }) + @IsString() + @IsNotEmpty({ message: '留言内容不能为空' }) + content: string; +} + +export class QueryMessageDto { + @ApiPropertyOptional({ description: '页码', default: 1 }) + @IsInt() + @Min(1) + @IsOptional() + @Type(() => Number) + page?: number; + + @ApiPropertyOptional({ description: '每页数量', default: 10 }) + @IsInt() + @Min(1) + @Max(100) + @IsOptional() + @Type(() => Number) + pageSize?: number; + + @ApiPropertyOptional({ description: '是否已读 0未读 1已读' }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isRead?: number; + + @ApiPropertyOptional({ description: '关键词' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/message/message.controller.ts b/src/modules/message/message.controller.ts new file mode 100644 index 0000000..043ffc4 --- /dev/null +++ b/src/modules/message/message.controller.ts @@ -0,0 +1,56 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { Public, Roles } from '@/common/decorators'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { Message } from '@/entities/message.entity'; +import { MessageService } from './message.service'; +import { + CreateMessageDto, + QueryMessageDto, +} from './dto/message.dto'; + +@ApiTags('留言 Message') +@Controller() +export class MessageController { + constructor(private readonly service: MessageService) {} + + @Public() + @Post('public/message') + @ApiOperation({ summary: '前台-访客提交留言' }) + submit(@Body() dto: CreateMessageDto): Promise { + return this.service.submit(dto); + } + + @ApiBearerAuth('admin-token') + @Get('admin/message') + @ApiOperation({ summary: '后台-留言分页' }) + paginate(@Query() query: QueryMessageDto): Promise> { + return this.service.paginate(query); + } + + @ApiBearerAuth('admin-token') + @Put('admin/message/:id/read') + @ApiOperation({ summary: '后台-留言标记已读' }) + markRead(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.markRead(id); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/message/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除留言(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/message/message.module.ts b/src/modules/message/message.module.ts new file mode 100644 index 0000000..a0ba3ad --- /dev/null +++ b/src/modules/message/message.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Message } from '@/entities/message.entity'; +import { MessageController } from './message.controller'; +import { MessageService } from './message.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Message])], + controllers: [MessageController], + providers: [MessageService], +}) +export class MessageModule {} diff --git a/src/modules/message/message.service.ts b/src/modules/message/message.service.ts new file mode 100644 index 0000000..046aee5 --- /dev/null +++ b/src/modules/message/message.service.ts @@ -0,0 +1,67 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { Message } from '@/entities/message.entity'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { + CreateMessageDto, + QueryMessageDto, +} from './dto/message.dto'; + +@Injectable() +export class MessageService { + constructor( + @InjectRepository(Message) + private readonly repo: Repository, + ) {} + + /** 前台访客提交留言 */ + async submit(dto: CreateMessageDto): Promise { + return this.repo.save( + this.repo.create({ + name: dto.name, + phone: dto.phone, + email: dto.email ?? '', + content: dto.content, + }), + ); + } + + /** 后台分页 */ + async paginate(query: QueryMessageDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 10; + const qb = this.repo + .createQueryBuilder('m') + .orderBy('m.is_read', 'ASC') + .addOrderBy('m.id', 'DESC'); + if (typeof query.isRead === 'number') { + qb.andWhere('m.is_read = :ir', { ir: query.isRead }); + } + if (query.keyword) { + qb.andWhere( + '(m.name LIKE :kw OR m.phone LIKE :kw OR m.content LIKE :kw)', + { kw: `%${query.keyword}%` }, + ); + } + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async markRead(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('留言不存在'); + item.isRead = 1; + await this.repo.save(item); + } + + async remove(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('留言不存在'); + await this.repo.delete(id); + } +} diff --git a/src/modules/news-category/dto/news-category.dto.ts b/src/modules/news-category/dto/news-category.dto.ts new file mode 100644 index 0000000..0ba2ad1 --- /dev/null +++ b/src/modules/news-category/dto/news-category.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateNewsCategoryDto { + @ApiProperty({ description: '分类名称' }) + @IsString() + @IsNotEmpty({ message: '分类名称不能为空' }) + @MaxLength(100) + name: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsInt() + @Min(0) + @Max(99999) + @IsOptional() + @Type(() => Number) + sort?: number; + + @ApiPropertyOptional({ description: '是否展示 1是0否', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isShow?: number; +} + +export class UpdateNewsCategoryDto extends CreateNewsCategoryDto {} + +export class QueryNewsCategoryDto { + @ApiPropertyOptional({ description: '关键词' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/news-category/news-category.controller.ts b/src/modules/news-category/news-category.controller.ts new file mode 100644 index 0000000..4d7ee12 --- /dev/null +++ b/src/modules/news-category/news-category.controller.ts @@ -0,0 +1,72 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Public, Roles } from '@/common/decorators'; +import { NewsCategory } from '@/entities/news-category.entity'; +import { NewsCategoryService } from './news-category.service'; +import { + CreateNewsCategoryDto, + QueryNewsCategoryDto, + UpdateNewsCategoryDto, +} from './dto/news-category.dto'; + +@ApiTags('新闻分类 NewsCategory') +@Controller() +export class NewsCategoryController { + constructor(private readonly service: NewsCategoryService) {} + + @Public() + @Get('public/news-category') + @ApiOperation({ summary: '前台-新闻分类列表' }) + publicList(): Promise { + return this.service.findPublicList(); + } + + @ApiBearerAuth('admin-token') + @Get('admin/news-category') + @ApiOperation({ summary: '后台-新闻分类全部列表' }) + list(@Query() query: QueryNewsCategoryDto): Promise { + return this.service.findAll(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/news-category/:id') + @ApiOperation({ summary: '后台-新闻分类详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/news-category') + @ApiOperation({ summary: '后台-新增新闻分类' }) + create(@Body() dto: CreateNewsCategoryDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/news-category/:id') + @ApiOperation({ summary: '后台-修改新闻分类' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateNewsCategoryDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/news-category/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除新闻分类(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/news-category/news-category.module.ts b/src/modules/news-category/news-category.module.ts new file mode 100644 index 0000000..456a5a3 --- /dev/null +++ b/src/modules/news-category/news-category.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { NewsCategory } from '@/entities/news-category.entity'; +import { NewsCategoryController } from './news-category.controller'; +import { NewsCategoryService } from './news-category.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([NewsCategory])], + controllers: [NewsCategoryController], + providers: [NewsCategoryService], + exports: [NewsCategoryService], +}) +export class NewsCategoryModule {} diff --git a/src/modules/news-category/news-category.service.ts b/src/modules/news-category/news-category.service.ts new file mode 100644 index 0000000..4ee3a37 --- /dev/null +++ b/src/modules/news-category/news-category.service.ts @@ -0,0 +1,66 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { NewsCategory } from '@/entities/news-category.entity'; +import { + CreateNewsCategoryDto, + QueryNewsCategoryDto, + UpdateNewsCategoryDto, +} from './dto/news-category.dto'; + +@Injectable() +export class NewsCategoryService { + constructor( + @InjectRepository(NewsCategory) + private readonly repo: Repository, + ) {} + + async findPublicList(): Promise { + return this.repo.find({ + where: { isShow: 1 }, + order: { sort: 'ASC', id: 'DESC' }, + }); + } + + async findAll(query?: QueryNewsCategoryDto): Promise { + const qb = this.repo + .createQueryBuilder('c') + .orderBy('c.sort', 'ASC') + .addOrderBy('c.id', 'DESC'); + if (query?.keyword) { + qb.andWhere('c.name LIKE :kw', { kw: `%${query.keyword}%` }); + } + return qb.getMany(); + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('分类不存在'); + return item; + } + + async create(dto: CreateNewsCategoryDto): Promise { + return this.repo.save( + this.repo.create({ + name: dto.name, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }), + ); + } + + async update(id: number, dto: UpdateNewsCategoryDto): Promise { + const item = await this.detail(id); + Object.assign(item, { + name: dto.name, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + await this.detail(id); + await this.repo.delete(id); + } +} diff --git a/src/modules/news/dto/news.dto.ts b/src/modules/news/dto/news.dto.ts new file mode 100644 index 0000000..c361f46 --- /dev/null +++ b/src/modules/news/dto/news.dto.ts @@ -0,0 +1,87 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateNewsDto { + @ApiProperty({ description: '分类ID' }) + @IsInt() + @Min(1) + @Type(() => Number) + categoryId: number; + + @ApiProperty({ description: '新闻标题' }) + @IsString() + @IsNotEmpty({ message: '标题不能为空' }) + @MaxLength(200) + title: string; + + @ApiPropertyOptional({ description: '封面图' }) + @IsString() + @IsOptional() + cover?: string; + + @ApiPropertyOptional({ description: '简介' }) + @IsString() + @IsOptional() + @MaxLength(500) + intro?: string; + + @ApiProperty({ description: '正文富文本' }) + @IsString() + @IsNotEmpty({ message: '正文不能为空' }) + content: string; + + @ApiPropertyOptional({ description: '是否置顶 1是0否', default: 0 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isTop?: number; + + @ApiPropertyOptional({ description: '状态 1发布 0草稿', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + status?: number; +} + +export class UpdateNewsDto extends CreateNewsDto {} + +export class QueryNewsDto { + @ApiPropertyOptional({ description: '页码', default: 1 }) + @IsInt() + @Min(1) + @IsOptional() + @Type(() => Number) + page?: number; + + @ApiPropertyOptional({ description: '每页数量', default: 10 }) + @IsInt() + @Min(1) + @Max(100) + @IsOptional() + @Type(() => Number) + pageSize?: number; + + @ApiPropertyOptional({ description: '分类ID' }) + @IsInt() + @IsOptional() + @Type(() => Number) + categoryId?: number; + + @ApiPropertyOptional({ description: '关键词(标题)' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/news/news.controller.ts b/src/modules/news/news.controller.ts new file mode 100644 index 0000000..2186941 --- /dev/null +++ b/src/modules/news/news.controller.ts @@ -0,0 +1,77 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { Public, Roles } from '@/common/decorators'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { News } from '@/entities/news.entity'; +import { NewsService } from './news.service'; +import { CreateNewsDto, QueryNewsDto, UpdateNewsDto } from './dto/news.dto'; + +@ApiTags('新闻 News') +@Controller() +export class NewsController { + constructor(private readonly service: NewsService) {} + + @Public() + @Get('public/news') + @ApiOperation({ summary: '前台-新闻分页' }) + publicPaginate(@Query() query: QueryNewsDto): Promise> { + return this.service.publicPaginate(query); + } + + @Public() + @Get('public/news/:id') + @ApiOperation({ summary: '前台-新闻详情' }) + publicDetail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.publicDetail(id); + } + + @ApiBearerAuth('admin-token') + @Get('admin/news') + @ApiOperation({ summary: '后台-新闻分页' }) + paginate(@Query() query: QueryNewsDto): Promise> { + return this.service.paginate(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/news/:id') + @ApiOperation({ summary: '后台-新闻详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/news') + @ApiOperation({ summary: '后台-新增新闻' }) + create(@Body() dto: CreateNewsDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/news/:id') + @ApiOperation({ summary: '后台-修改新闻' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateNewsDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/news/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除新闻(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/news/news.module.ts b/src/modules/news/news.module.ts new file mode 100644 index 0000000..33eeccb --- /dev/null +++ b/src/modules/news/news.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { News } from '@/entities/news.entity'; +import { NewsCategory } from '@/entities/news-category.entity'; +import { NewsController } from './news.controller'; +import { NewsService } from './news.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([News, NewsCategory])], + controllers: [NewsController], + providers: [NewsService], +}) +export class NewsModule {} diff --git a/src/modules/news/news.service.ts b/src/modules/news/news.service.ts new file mode 100644 index 0000000..521debd --- /dev/null +++ b/src/modules/news/news.service.ts @@ -0,0 +1,105 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { News } from '@/entities/news.entity'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { CreateNewsDto, QueryNewsDto, UpdateNewsDto } from './dto/news.dto'; + +@Injectable() +export class NewsService { + constructor( + @InjectRepository(News) + private readonly repo: Repository, + ) {} + + // ---------- 前台 ---------- + async publicPaginate(query: QueryNewsDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 10; + const qb = this.buildQb(query).andWhere('n.status = 1'); + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async publicDetail(id: number): Promise { + const item = await this.repo.findOne({ + where: { id, status: 1 }, + relations: ['category'], + }); + if (!item) throw new NotFoundException('新闻不存在或未发布'); + return item; + } + + // ---------- 后台 ---------- + async paginate(query: QueryNewsDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 10; + const qb = this.buildQb(query); + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ + where: { id }, + relations: ['category'], + }); + if (!item) throw new NotFoundException('新闻不存在'); + return item; + } + + async create(dto: CreateNewsDto): Promise { + return this.repo.save( + this.repo.create({ + categoryId: dto.categoryId, + title: dto.title, + cover: dto.cover ?? '', + intro: dto.intro ?? '', + content: dto.content, + isTop: dto.isTop ?? 0, + status: dto.status ?? 1, + }), + ); + } + + async update(id: number, dto: UpdateNewsDto): Promise { + const item = await this.detail(id); + Object.assign(item, { + categoryId: dto.categoryId, + title: dto.title, + cover: dto.cover ?? '', + intro: dto.intro ?? '', + content: dto.content, + isTop: dto.isTop ?? 0, + status: dto.status ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + await this.detail(id); + await this.repo.delete(id); + } + + private buildQb(query: QueryNewsDto) { + const qb = this.repo + .createQueryBuilder('n') + .leftJoinAndSelect('n.category', 'c') + .orderBy('n.isTop', 'DESC') + .addOrderBy('n.id', 'DESC'); + if (query.categoryId) { + qb.andWhere('n.category_id = :cid', { cid: query.categoryId }); + } + if (query.keyword) { + qb.andWhere('n.title LIKE :kw', { kw: `%${query.keyword}%` }); + } + return qb; + } +} diff --git a/src/modules/product-category/dto/product-category.dto.ts b/src/modules/product-category/dto/product-category.dto.ts new file mode 100644 index 0000000..72001e3 --- /dev/null +++ b/src/modules/product-category/dto/product-category.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateProductCategoryDto { + @ApiProperty({ description: '分类名称' }) + @IsString() + @IsNotEmpty({ message: '分类名称不能为空' }) + @MaxLength(100) + name: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsInt() + @Min(0) + @Max(99999) + @IsOptional() + @Type(() => Number) + sort?: number; + + @ApiPropertyOptional({ description: '是否展示 1是0否', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isShow?: number; +} + +export class UpdateProductCategoryDto extends CreateProductCategoryDto {} + +export class QueryProductCategoryDto { + @ApiPropertyOptional({ description: '关键词' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/product-category/product-category.controller.ts b/src/modules/product-category/product-category.controller.ts new file mode 100644 index 0000000..8520dfa --- /dev/null +++ b/src/modules/product-category/product-category.controller.ts @@ -0,0 +1,72 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Public, Roles } from '@/common/decorators'; +import { ProductCategory } from '@/entities/product-category.entity'; +import { ProductCategoryService } from './product-category.service'; +import { + CreateProductCategoryDto, + QueryProductCategoryDto, + UpdateProductCategoryDto, +} from './dto/product-category.dto'; + +@ApiTags('产品分类 ProductCategory') +@Controller() +export class ProductCategoryController { + constructor(private readonly service: ProductCategoryService) {} + + @Public() + @Get('public/product-category') + @ApiOperation({ summary: '前台-产品分类列表' }) + publicList(): Promise { + return this.service.findPublicList(); + } + + @ApiBearerAuth('admin-token') + @Get('admin/product-category') + @ApiOperation({ summary: '后台-产品分类全部列表' }) + list(@Query() query: QueryProductCategoryDto): Promise { + return this.service.findAll(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/product-category/:id') + @ApiOperation({ summary: '后台-产品分类详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/product-category') + @ApiOperation({ summary: '后台-新增产品分类' }) + create(@Body() dto: CreateProductCategoryDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/product-category/:id') + @ApiOperation({ summary: '后台-修改产品分类' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateProductCategoryDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/product-category/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除产品分类(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/product-category/product-category.module.ts b/src/modules/product-category/product-category.module.ts new file mode 100644 index 0000000..3cd17e3 --- /dev/null +++ b/src/modules/product-category/product-category.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ProductCategory } from '@/entities/product-category.entity'; +import { ProductCategoryController } from './product-category.controller'; +import { ProductCategoryService } from './product-category.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([ProductCategory])], + controllers: [ProductCategoryController], + providers: [ProductCategoryService], + exports: [ProductCategoryService], +}) +export class ProductCategoryModule {} diff --git a/src/modules/product-category/product-category.service.ts b/src/modules/product-category/product-category.service.ts new file mode 100644 index 0000000..7ed9c77 --- /dev/null +++ b/src/modules/product-category/product-category.service.ts @@ -0,0 +1,65 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { ProductCategory } from '@/entities/product-category.entity'; +import { + CreateProductCategoryDto, + QueryProductCategoryDto, + UpdateProductCategoryDto, +} from './dto/product-category.dto'; + +@Injectable() +export class ProductCategoryService { + constructor( + @InjectRepository(ProductCategory) + private readonly repo: Repository, + ) {} + + async findPublicList(): Promise { + return this.repo.find({ + where: { isShow: 1 }, + order: { sort: 'ASC', id: 'DESC' }, + }); + } + + async findAll(query?: QueryProductCategoryDto): Promise { + const qb = this.repo + .createQueryBuilder('c') + .orderBy('c.sort', 'ASC') + .addOrderBy('c.id', 'DESC'); + if (query?.keyword) { + qb.andWhere('c.name LIKE :kw', { kw: `%${query.keyword}%` }); + } + return qb.getMany(); + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('分类不存在'); + return item; + } + + async create(dto: CreateProductCategoryDto): Promise { + const entity = this.repo.create({ + name: dto.name, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + return this.repo.save(entity); + } + + async update(id: number, dto: UpdateProductCategoryDto): Promise { + const item = await this.detail(id); + Object.assign(item, { + name: dto.name, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + await this.detail(id); + await this.repo.delete(id); + } +} diff --git a/src/modules/product/dto/product.dto.ts b/src/modules/product/dto/product.dto.ts new file mode 100644 index 0000000..0d9bbbb --- /dev/null +++ b/src/modules/product/dto/product.dto.ts @@ -0,0 +1,86 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateProductDto { + @ApiProperty({ description: '分类ID' }) + @IsInt() + @Min(1) + @Type(() => Number) + categoryId: number; + + @ApiProperty({ description: '产品名称' }) + @IsString() + @IsNotEmpty({ message: '产品名称不能为空' }) + @MaxLength(100) + name: string; + + @ApiProperty({ description: '封面图' }) + @IsString() + @IsNotEmpty({ message: '封面图不能为空' }) + cover: string; + + @ApiPropertyOptional({ description: '简短描述' }) + @IsString() + @IsOptional() + desc?: string; + + @ApiPropertyOptional({ description: '详情富文本' }) + @IsString() + @IsOptional() + content?: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsInt() + @Min(0) + @Max(99999) + @IsOptional() + @Type(() => Number) + sort?: number; + + @ApiPropertyOptional({ description: '上下架 1是0否', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isShow?: number; +} + +export class UpdateProductDto extends CreateProductDto {} + +export class QueryProductDto { + @ApiPropertyOptional({ description: '页码', default: 1 }) + @IsInt() + @Min(1) + @IsOptional() + @Type(() => Number) + page?: number; + + @ApiPropertyOptional({ description: '每页数量', default: 10 }) + @IsInt() + @Min(1) + @Max(100) + @IsOptional() + @Type(() => Number) + pageSize?: number; + + @ApiPropertyOptional({ description: '分类ID' }) + @IsInt() + @IsOptional() + @Type(() => Number) + categoryId?: number; + + @ApiPropertyOptional({ description: '关键词(名称)' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/product/product.controller.ts b/src/modules/product/product.controller.ts new file mode 100644 index 0000000..a79b220 --- /dev/null +++ b/src/modules/product/product.controller.ts @@ -0,0 +1,83 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { Public, Roles } from '@/common/decorators'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { Product } from '@/entities/product.entity'; +import { ProductService } from './product.service'; +import { + CreateProductDto, + QueryProductDto, + UpdateProductDto, +} from './dto/product.dto'; + +@ApiTags('产品 Product') +@Controller() +export class ProductController { + constructor(private readonly service: ProductService) {} + + // ---------- 前台 ---------- + @Public() + @Get('public/product') + @ApiOperation({ summary: '前台-产品分页' }) + publicPaginate(@Query() query: QueryProductDto): Promise> { + return this.service.publicPaginate(query); + } + + @Public() + @Get('public/product/:id') + @ApiOperation({ summary: '前台-产品详情' }) + publicDetail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.publicDetail(id); + } + + // ---------- 后台 ---------- + @ApiBearerAuth('admin-token') + @Get('admin/product') + @ApiOperation({ summary: '后台-产品分页' }) + paginate(@Query() query: QueryProductDto): Promise> { + return this.service.paginate(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/product/:id') + @ApiOperation({ summary: '后台-产品详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/product') + @ApiOperation({ summary: '后台-新增产品' }) + create(@Body() dto: CreateProductDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/product/:id') + @ApiOperation({ summary: '后台-修改产品' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateProductDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/product/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除产品(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/product/product.module.ts b/src/modules/product/product.module.ts new file mode 100644 index 0000000..9a963bd --- /dev/null +++ b/src/modules/product/product.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Product } from '@/entities/product.entity'; +import { ProductCategory } from '@/entities/product-category.entity'; +import { ProductController } from './product.controller'; +import { ProductService } from './product.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Product, ProductCategory])], + controllers: [ProductController], + providers: [ProductService], +}) +export class ProductModule {} diff --git a/src/modules/product/product.service.ts b/src/modules/product/product.service.ts new file mode 100644 index 0000000..cd84ae3 --- /dev/null +++ b/src/modules/product/product.service.ts @@ -0,0 +1,114 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { Product } from '@/entities/product.entity'; +import { PaginatedResult } from '@/common/dto/api-response.dto'; +import { + CreateProductDto, + QueryProductDto, + UpdateProductDto, +} from './dto/product.dto'; + +@Injectable() +export class ProductService { + constructor( + @InjectRepository(Product) + private readonly repo: Repository, + ) {} + + // ---------- 前台 ---------- + async publicPaginate(query: QueryProductDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 12; + const qb = this.repo + .createQueryBuilder('p') + .leftJoinAndSelect('p.category', 'c') + .where('p.is_show = 1') + .orderBy('p.sort', 'ASC') + .addOrderBy('p.id', 'DESC'); + if (query.categoryId) { + qb.andWhere('p.category_id = :cid', { cid: query.categoryId }); + } + if (query.keyword) { + qb.andWhere('p.name LIKE :kw', { kw: `%${query.keyword}%` }); + } + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async publicDetail(id: number): Promise { + const item = await this.repo.findOne({ + where: { id, isShow: 1 }, + relations: ['category'], + }); + if (!item) throw new NotFoundException('产品不存在或已下架'); + return item; + } + + // ---------- 后台 ---------- + async paginate(query: QueryProductDto): Promise> { + const page = query.page ?? 1; + const pageSize = query.pageSize ?? 10; + const qb = this.repo + .createQueryBuilder('p') + .leftJoinAndSelect('p.category', 'c') + .orderBy('p.sort', 'ASC') + .addOrderBy('p.id', 'DESC'); + if (query.categoryId) { + qb.andWhere('p.category_id = :cid', { cid: query.categoryId }); + } + if (query.keyword) { + qb.andWhere('p.name LIKE :kw', { kw: `%${query.keyword}%` }); + } + const [list, total] = await qb + .skip((page - 1) * pageSize) + .take(pageSize) + .getManyAndCount(); + return { list, total, page, pageSize }; + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ + where: { id }, + relations: ['category'], + }); + if (!item) throw new NotFoundException('产品不存在'); + return item; + } + + async create(dto: CreateProductDto): Promise { + const entity = this.repo.create({ + categoryId: dto.categoryId, + name: dto.name, + cover: dto.cover, + desc: dto.desc ?? null, + content: dto.content ?? null, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + return this.repo.save(entity); + } + + async update(id: number, dto: UpdateProductDto): Promise { + const item = await this.detail(id); + Object.assign(item, { + categoryId: dto.categoryId, + name: dto.name, + cover: dto.cover, + desc: dto.desc ?? null, + content: dto.content ?? null, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + await this.detail(id); + await this.repo.delete(id); + } +} diff --git a/src/modules/site-config/dto/site-config.dto.ts b/src/modules/site-config/dto/site-config.dto.ts new file mode 100644 index 0000000..c258229 --- /dev/null +++ b/src/modules/site-config/dto/site-config.dto.ts @@ -0,0 +1,61 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsOptional, + IsString, + MaxLength, +} from 'class-validator'; + +export class UpdateSiteConfigDto { + @ApiPropertyOptional({ description: '网站名称' }) + @IsString() + @IsOptional() + @MaxLength(100) + siteName?: string; + + @ApiPropertyOptional({ description: 'logo 图片地址' }) + @IsString() + @IsOptional() + @MaxLength(255) + logo?: string; + + @ApiPropertyOptional({ description: '联系电话' }) + @IsString() + @IsOptional() + @MaxLength(50) + tel?: string; + + @ApiPropertyOptional({ description: '公司地址' }) + @IsString() + @IsOptional() + @MaxLength(255) + address?: string; + + @ApiPropertyOptional({ description: '商务邮箱' }) + @IsString() + @IsOptional() + @MaxLength(100) + email?: string; + + @ApiPropertyOptional({ description: '底部版权' }) + @IsString() + @IsOptional() + @MaxLength(255) + copyright?: string; + + @ApiPropertyOptional({ description: '备案号' }) + @IsString() + @IsOptional() + @MaxLength(100) + icp?: string; + + @ApiPropertyOptional({ description: '企业简介标题' }) + @IsString() + @IsOptional() + @MaxLength(100) + aboutTitle?: string; + + @ApiPropertyOptional({ description: '企业简介正文(富文本)' }) + @IsString() + @IsOptional() + aboutContent?: string; +} diff --git a/src/modules/site-config/site-config.controller.ts b/src/modules/site-config/site-config.controller.ts new file mode 100644 index 0000000..c85cbeb --- /dev/null +++ b/src/modules/site-config/site-config.controller.ts @@ -0,0 +1,33 @@ +import { Body, Controller, Get, Put } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Public } from '@/common/decorators'; +import { SiteConfig } from '@/entities/site-config.entity'; +import { SiteConfigService } from './site-config.service'; +import { UpdateSiteConfigDto } from './dto/site-config.dto'; + +@ApiTags('网站配置 SiteConfig') +@Controller() +export class SiteConfigController { + constructor(private readonly service: SiteConfigService) {} + + @Public() + @Get('public/site-config') + @ApiOperation({ summary: '前台-获取网站基础配置' }) + get(): Promise { + return this.service.get(); + } + + @ApiBearerAuth('admin-token') + @Get('admin/site-config') + @ApiOperation({ summary: '后台-获取网站配置' }) + adminGet(): Promise { + return this.service.get(); + } + + @ApiBearerAuth('admin-token') + @Put('admin/site-config') + @ApiOperation({ summary: '后台-修改网站配置' }) + update(@Body() dto: UpdateSiteConfigDto): Promise { + return this.service.update(dto); + } +} diff --git a/src/modules/site-config/site-config.module.ts b/src/modules/site-config/site-config.module.ts new file mode 100644 index 0000000..5823f55 --- /dev/null +++ b/src/modules/site-config/site-config.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SiteConfig } from '@/entities/site-config.entity'; +import { SiteConfigController } from './site-config.controller'; +import { SiteConfigService } from './site-config.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([SiteConfig])], + controllers: [SiteConfigController], + providers: [SiteConfigService], + exports: [SiteConfigService], +}) +export class SiteConfigModule {} diff --git a/src/modules/site-config/site-config.service.ts b/src/modules/site-config/site-config.service.ts new file mode 100644 index 0000000..a9a642c --- /dev/null +++ b/src/modules/site-config/site-config.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { SiteConfig } from '@/entities/site-config.entity'; +import { UpdateSiteConfigDto } from './dto/site-config.dto'; + +const SINGLETON_ID = 1; + +@Injectable() +export class SiteConfigService { + constructor( + @InjectRepository(SiteConfig) + private readonly repo: Repository, + ) {} + + /** + * 获取唯一站点配置;不存在则自动初始化一条 + */ + async get(): Promise { + let one = await this.repo.findOne({ where: { id: SINGLETON_ID } }); + if (!one) { + one = this.repo.create({ + id: SINGLETON_ID, + siteName: '企业官方网站', + logo: '', + tel: '', + address: '', + email: '', + copyright: '', + icp: '', + aboutTitle: '', + aboutContent: null, + }); + one = await this.repo.save(one); + } + return one; + } + + async update(dto: UpdateSiteConfigDto): Promise { + const one = await this.get(); + Object.assign(one, dto); + return this.repo.save(one); + } +} diff --git a/src/modules/team/dto/team.dto.ts b/src/modules/team/dto/team.dto.ts new file mode 100644 index 0000000..9a99cdf --- /dev/null +++ b/src/modules/team/dto/team.dto.ts @@ -0,0 +1,60 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsInt, + IsNotEmpty, + IsOptional, + IsString, + Max, + MaxLength, + Min, +} from 'class-validator'; + +export class CreateTeamDto { + @ApiProperty({ description: '姓名' }) + @IsString() + @IsNotEmpty() + @MaxLength(50) + name: string; + + @ApiProperty({ description: '职位' }) + @IsString() + @IsNotEmpty() + @MaxLength(100) + position: string; + + @ApiProperty({ description: '头像' }) + @IsString() + @IsNotEmpty() + avatar: string; + + @ApiPropertyOptional({ description: '个人简介' }) + @IsString() + @IsOptional() + desc?: string; + + @ApiPropertyOptional({ description: '排序', default: 0 }) + @IsInt() + @Min(0) + @Max(99999) + @IsOptional() + @Type(() => Number) + sort?: number; + + @ApiPropertyOptional({ description: '是否展示 1是0否', default: 1 }) + @IsInt() + @Min(0) + @Max(1) + @IsOptional() + @Type(() => Number) + isShow?: number; +} + +export class UpdateTeamDto extends CreateTeamDto {} + +export class QueryTeamDto { + @ApiPropertyOptional({ description: '关键词(姓名/职位)' }) + @IsString() + @IsOptional() + keyword?: string; +} diff --git a/src/modules/team/team.controller.ts b/src/modules/team/team.controller.ts new file mode 100644 index 0000000..e4f1630 --- /dev/null +++ b/src/modules/team/team.controller.ts @@ -0,0 +1,72 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseIntPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Public, Roles } from '@/common/decorators'; +import { Team } from '@/entities/team.entity'; +import { TeamService } from './team.service'; +import { + CreateTeamDto, + QueryTeamDto, + UpdateTeamDto, +} from './dto/team.dto'; + +@ApiTags('团队 Team') +@Controller() +export class TeamController { + constructor(private readonly service: TeamService) {} + + @Public() + @Get('public/team') + @ApiOperation({ summary: '前台-团队成员列表' }) + publicList(): Promise { + return this.service.findPublicList(); + } + + @ApiBearerAuth('admin-token') + @Get('admin/team') + @ApiOperation({ summary: '后台-团队成员列表' }) + list(@Query() query: QueryTeamDto): Promise { + return this.service.findAll(query); + } + + @ApiBearerAuth('admin-token') + @Get('admin/team/:id') + @ApiOperation({ summary: '后台-团队成员详情' }) + detail(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.detail(id); + } + + @ApiBearerAuth('admin-token') + @Post('admin/team') + @ApiOperation({ summary: '后台-新增团队成员' }) + create(@Body() dto: CreateTeamDto): Promise { + return this.service.create(dto); + } + + @ApiBearerAuth('admin-token') + @Put('admin/team/:id') + @ApiOperation({ summary: '后台-修改团队成员' }) + update( + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateTeamDto, + ): Promise { + return this.service.update(id, dto); + } + + @ApiBearerAuth('admin-token') + @Delete('admin/team/:id') + @Roles('super_admin') + @ApiOperation({ summary: '后台-删除团队成员(仅超管)' }) + remove(@Param('id', ParseIntPipe) id: number): Promise { + return this.service.remove(id); + } +} diff --git a/src/modules/team/team.module.ts b/src/modules/team/team.module.ts new file mode 100644 index 0000000..1b31368 --- /dev/null +++ b/src/modules/team/team.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Team } from '@/entities/team.entity'; +import { TeamController } from './team.controller'; +import { TeamService } from './team.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Team])], + controllers: [TeamController], + providers: [TeamService], +}) +export class TeamModule {} diff --git a/src/modules/team/team.service.ts b/src/modules/team/team.service.ts new file mode 100644 index 0000000..5ccc631 --- /dev/null +++ b/src/modules/team/team.service.ts @@ -0,0 +1,74 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, Like } from 'typeorm'; +import { Team } from '@/entities/team.entity'; +import { + CreateTeamDto, + QueryTeamDto, + UpdateTeamDto, +} from './dto/team.dto'; + +@Injectable() +export class TeamService { + constructor( + @InjectRepository(Team) + private readonly repo: Repository, + ) {} + + async findPublicList(): Promise { + return this.repo.find({ + where: { isShow: 1 }, + order: { sort: 'ASC', id: 'DESC' }, + }); + } + + async findAll(query?: QueryTeamDto): Promise { + const where = query?.keyword + ? [ + { name: Like(`%${query.keyword}%`) }, + { position: Like(`%${query.keyword}%`) }, + ] + : undefined; + return this.repo.find({ + where, + order: { sort: 'ASC', id: 'DESC' }, + }); + } + + async detail(id: number): Promise { + const item = await this.repo.findOne({ where: { id } }); + if (!item) throw new NotFoundException('成员不存在'); + return item; + } + + async create(dto: CreateTeamDto): Promise { + return this.repo.save( + this.repo.create({ + name: dto.name, + position: dto.position, + avatar: dto.avatar, + desc: dto.desc ?? null, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }), + ); + } + + async update(id: number, dto: UpdateTeamDto): Promise { + const item = await this.detail(id); + Object.assign(item, { + name: dto.name, + position: dto.position, + avatar: dto.avatar, + desc: dto.desc ?? null, + sort: dto.sort ?? 0, + isShow: dto.isShow ?? 1, + }); + await this.repo.save(item); + } + + async remove(id: number): Promise { + await this.detail(id); + await this.repo.delete(id); + } +} diff --git a/src/modules/upload/upload.controller.ts b/src/modules/upload/upload.controller.ts new file mode 100644 index 0000000..0cd8cfe --- /dev/null +++ b/src/modules/upload/upload.controller.ts @@ -0,0 +1,36 @@ +import { + Controller, + Post, + Req, + BadRequestException, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Request } from 'express'; +import { filePathToUrl } from '@/utils/file.util'; + +export interface UploadResult { + url: string; + filename: string; +} + +@ApiBearerAuth('admin-token') +@ApiTags('文件上传 Upload') +@Controller('admin/upload') +export class UploadController { + @Post() + @ApiConsumes('multipart/form-data') + @ApiOperation({ + summary: '上传图片', + description: '仅支持 jpg/png/jpeg/webp,单图 2M 上限', + }) + upload(@Req() req: Request): UploadResult { + const file = (req as Request & { file?: Express.Multer.File }).file; + if (!file) { + throw new BadRequestException('请上传文件'); + } + return { + url: filePathToUrl(file.path), + filename: file.filename, + }; + } +} diff --git a/src/modules/upload/upload.module.ts b/src/modules/upload/upload.module.ts new file mode 100644 index 0000000..b002ca9 --- /dev/null +++ b/src/modules/upload/upload.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { UploadController } from './upload.controller'; + +@Module({ + controllers: [UploadController], +}) +export class UploadModule {} diff --git a/src/types/express.d.ts b/src/types/express.d.ts new file mode 100644 index 0000000..b5a0669 --- /dev/null +++ b/src/types/express.d.ts @@ -0,0 +1,10 @@ +import { CurrentAdminPayload } from '@/common/decorators/current-admin.decorator'; + +declare module 'express' { + interface Request { + /** JWT 守卫解析后挂载的当前管理员信息 */ + admin?: CurrentAdminPayload; + /** multer 上传后的文件(仅 /admin/upload 路由) */ + file?: Express.Multer.File; + } +} diff --git a/src/utils/crypto.util.ts b/src/utils/crypto.util.ts new file mode 100644 index 0000000..0b873a5 --- /dev/null +++ b/src/utils/crypto.util.ts @@ -0,0 +1,16 @@ +import * as bcrypt from 'bcrypt'; + +const SALT_ROUNDS = 10; + +/** bcrypt 加密明文密码 */ +export async function hashPassword(plain: string): Promise { + return bcrypt.hash(plain, SALT_ROUNDS); +} + +/** bcrypt 校验密码 */ +export async function comparePassword( + plain: string, + hashed: string, +): Promise { + return bcrypt.compare(plain, hashed); +} diff --git a/src/utils/file.util.ts b/src/utils/file.util.ts new file mode 100644 index 0000000..2eb7a15 --- /dev/null +++ b/src/utils/file.util.ts @@ -0,0 +1,14 @@ +import path from 'path'; + +/** + * 把磁盘绝对路径转换成可访问的相对 URL + * 例如:/Users/xxx/server/uploads/2026/01/abc.jpg -> /uploads/2026/01/abc.jpg + */ +export function filePathToUrl(absPath: string): string { + const root = (process.env.UPLOAD_ROOT ?? './uploads').replace(/^\.\/?/, ''); + const idx = absPath.replace(/\\/g, '/').indexOf(root); + if (idx >= 0) { + return '/' + absPath.replace(/\\/g, '/').slice(idx).replace(/^\/+/, '/'); + } + return '/' + path.basename(absPath); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..23a263b --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './crypto.util'; +export * from './file.util'; diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..64f86c6 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..185f105 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "skipLibCheck": true, + "strictNullChecks": true, + "noImplicitAny": true, + "strict": true, + "strictPropertyInitialization": false, + "noEmitOnError": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/uploads/2026/06/1781692209397_uqobwr.jpg b/uploads/2026/06/1781692209397_uqobwr.jpg new file mode 100644 index 0000000..05e13e6 Binary files /dev/null and b/uploads/2026/06/1781692209397_uqobwr.jpg differ