├── .editorconfig ├── .eslintignore ├── .gitignore ├── .prettierignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── README.md ├── bin └── www ├── db └── init-db.js ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── prettier.config.js └── src ├── app.js ├── config ├── db.js └── index.js ├── controllers ├── auth.js └── system │ ├── dept.js │ ├── dict.js │ ├── dictDetail.js │ ├── job.js │ ├── menu.js │ ├── role.js │ └── user.js ├── middlewares ├── auth.js └── error.js ├── models ├── index.js └── system │ ├── dept.js │ ├── dict.js │ ├── dictDetail.js │ ├── job.js │ ├── menu.js │ ├── role.js │ └── user.js └── routes ├── auth.js ├── index.js └── system ├── dept.js ├── dict.js ├── dictDetail.js ├── job.js ├── menu.js ├── role.js └── user.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # @see: http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = off 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | .husky 4 | 5 | node_modules 6 | pnpm-lock.yaml 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .eslintcache 3 | 4 | .vscode/* 5 | !.vscode/extensions.json 6 | !.vscode/settings.json 7 | !.vscode/launch.json 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/** 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "vscode-icons-team.vscode-icons" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "nodemon", 11 | "runtimeExecutable": "nodemon", 12 | "restart": true, 13 | "console": "integratedTerminal", 14 | "internalConsoleOptions": "neverOpen", 15 | "skipFiles": [ 16 | "/**" 17 | ], 18 | "program": "${workspaceFolder}/src/app.js" 19 | }, 20 | { 21 | "type": "node", 22 | "request": "launch", 23 | "name": "启动程序", 24 | "skipFiles": [ 25 | "/**" 26 | ], 27 | "program": "${workspaceFolder}/src/app.js" 28 | }, 29 | { 30 | "type": "node", 31 | "request": "launch", 32 | "name": "当前文件", 33 | "skipFiles": [ 34 | "/**" 35 | ], 36 | "program": "${file}" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.codeActionsOnSave": { 4 | "source.fixAll.eslint": "explicit" 5 | }, 6 | "eslint.validate": ["javascript"], 7 | "eslint.useFlatConfig": true 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Koa Admin Design 2 | 3 | 基于 Koa 开发的用于 admin-design 的后台管理系统 API 服务。 4 | 5 | ## 技术栈 6 | 7 | - Koa.js - 轻量级 Node.js Web 框架 8 | - Sequelize - ORM 框架 9 | - MySQL - 数据库 10 | - JWT - 认证 11 | - bcrypt - 密码加密 12 | 13 | ## 功能特性 14 | 15 | - 用户认证与授权 16 | - 用户管理 17 | - 角色管理 18 | - 菜单管理 19 | - 部门管理 20 | - 岗位管理 21 | - 字典管理 22 | 23 | ## 安装与运行 24 | 25 | ### 前提条件 26 | 27 | - Node.js (>= 20) 28 | - Pnpm (>= 10) 29 | - MySQL (>= 8) 30 | 31 | ### 项目运行 32 | 33 | ```bash 34 | // 安装依赖 35 | pnpm install 36 | 37 | // 启动开发服务 38 | pnpm run dev -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import http from 'node:http' 3 | import app from '../src/app.js' 4 | import config from '../src/config/index.js' 5 | 6 | // 获取端口 7 | const port = normalizePort(config.port || '3000') 8 | 9 | // 创建HTTP服务器 10 | const server = http.createServer(app.callback()) 11 | 12 | // 监听端口 13 | server.listen(port) 14 | server.on('error', onError) 15 | server.on('listening', onListening) 16 | 17 | // 规范化端口 18 | function normalizePort(val) { 19 | const port = parseInt(val, 10) 20 | 21 | if (isNaN(port)) { 22 | return val 23 | } 24 | 25 | if (port >= 0) { 26 | return port 27 | } 28 | 29 | return false 30 | } 31 | 32 | // 错误处理 33 | function onError(error) { 34 | if (error.syscall !== 'listen') { 35 | throw error 36 | } 37 | 38 | const bind = typeof port === 'string' 39 | ? 'Pipe ' + port 40 | : 'Port ' + port 41 | 42 | // 特定错误消息的友好处理 43 | switch (error.code) { 44 | case 'EACCES': 45 | console.error(bind + ' requires elevated privileges') 46 | process.exit(1) 47 | break 48 | case 'EADDRINUSE': 49 | console.error(bind + ' is already in use') 50 | process.exit(1) 51 | break 52 | default: 53 | throw error 54 | } 55 | } 56 | 57 | // 监听回调 58 | function onListening() { 59 | const addr = server.address() 60 | const bind = typeof addr === 'string' 61 | ? 'pipe ' + addr 62 | : 'port ' + addr.port 63 | console.log('Listening on ' + bind) 64 | } 65 | -------------------------------------------------------------------------------- /db/init-db.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | import db from '../src/models/index.js' 3 | 4 | const { sequelize, User, Role, Dept, Job, Menu, Dict, DictDetail } = db 5 | 6 | // 初始化数据库 7 | async function initDatabase() { 8 | try { 9 | await User.sync({ force: true }) 10 | console.log('数据库初始化成功') 11 | 12 | // 创建部门 13 | const dept = await Dept.create({ 14 | name: '公司总部', 15 | dept_sort: 0, 16 | enabled: true, 17 | create_time: new Date() 18 | }) 19 | 20 | // 创建岗位 21 | const job = await Job.create({ 22 | name: '全栈开发', 23 | enabled: true, 24 | job_sort: 1, 25 | create_time: new Date() 26 | }) 27 | 28 | // 创建角色 29 | const role = await Role.create({ 30 | name: '超级管理员', 31 | level: 1, 32 | description: '系统所有权限', 33 | data_scope: '全部', 34 | create_time: new Date() 35 | }) 36 | 37 | // 创建用户 38 | const admin = await User.create({ 39 | dept_id: dept.dept_id, 40 | username: 'admin', 41 | nick_name: '管理员', 42 | gender: '男', 43 | phone: '13800138000', 44 | email: 'admin@example.com', 45 | avatar_name: 'avatar.jpg', 46 | avatar_path: 'avatar.jpg', 47 | password: bcrypt.hashSync('123456', 10), 48 | is_admin: true, 49 | enabled: true, 50 | create_time: new Date() 51 | }) 52 | 53 | // 创建菜单 54 | const systemMenu = await Menu.create({ 55 | type: 0, 56 | title: '系统管理', 57 | menu_sort: 1, 58 | icon: 'system', 59 | path: '/system', 60 | i_frame: false, 61 | cache: false, 62 | hidden: false, 63 | create_time: new Date() 64 | }) 65 | 66 | // 关联用户和角色 67 | await admin.addRole(role) 68 | 69 | // 关联用户和岗位 70 | await admin.addJob(job) 71 | 72 | // 关联角色和菜单 73 | await role.addMenus([systemMenu]) 74 | 75 | // 创建字典 76 | const dict = await Dict.create({ 77 | name: 'user_status', 78 | description: '用户状态', 79 | create_time: new Date() 80 | }) 81 | 82 | // 创建字典详情 83 | await DictDetail.create({ 84 | dict_id: dict.dict_id, 85 | label: '激活', 86 | value: 'true', 87 | dict_sort: 1, 88 | create_time: new Date() 89 | }) 90 | 91 | await DictDetail.create({ 92 | dict_id: dict.dict_id, 93 | label: '禁用', 94 | value: 'false', 95 | dict_sort: 2, 96 | create_time: new Date() 97 | }) 98 | 99 | console.log('数据库初始化完成') 100 | } catch (error) { 101 | console.log('数据库初始化失败', error) 102 | } finally { 103 | // 关闭数据库连接 104 | await sequelize.close() 105 | } 106 | } 107 | 108 | // 执行初始化 109 | initDatabase() 110 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // 配置文档: https://eslint.nodejs.cn/ 2 | import { defineFlatConfig } from 'eslint-define-config' 3 | import configPrettier from 'eslint-config-prettier' 4 | import pluginPrettier from 'eslint-plugin-prettier' 5 | import js from '@eslint/js' 6 | 7 | /** @type {import('eslint-define-config').FlatESLintConfig} */ 8 | export default defineFlatConfig([ 9 | { 10 | ...js.configs.recommended, 11 | plugins: { 12 | prettier: pluginPrettier 13 | }, 14 | languageOptions: { 15 | globals: { 16 | ...js.configs.recommended.globals, 17 | global: 'writeable', 18 | process: 'readonly', 19 | console: 'readonly' 20 | }, 21 | parserOptions: { 22 | ecmaVersion: 'latest', 23 | sourceType: 'module' 24 | } 25 | }, 26 | rules: { 27 | ...configPrettier.rules, 28 | ...pluginPrettier.configs.recommended.rules, 29 | /* 30 | * Eslint规则配置 31 | * 配置文档: https://eslint.nodejs.cn/docs/latest/rules/ 32 | */ 33 | // 需要 let 或 const 而不是 var 34 | 'no-var': 'error', 35 | // 禁止使用未定义的变量 36 | 'no-undef': 'error', 37 | // 禁止在定义变量之前使用变量 38 | 'no-use-before-define': 'off', 39 | // 声明后永远不会重新分配的变量需要 const 声明 40 | 'prefer-const': 'error', 41 | // 禁止不规则空格 42 | 'no-irregular-whitespace': 'off', 43 | // 禁止使用 debugger 44 | 'no-debugger': 'off', 45 | // 禁止未使用的变量 46 | 'no-unused-vars': [ 47 | 'error', 48 | { 49 | argsIgnorePattern: '^_', 50 | varsIgnorePattern: '^_' 51 | } 52 | ], 53 | // 使用 prettier 插件 54 | 'prettier/prettier': [ 55 | 'error', 56 | { 57 | endOfLine: 'auto' 58 | } 59 | ] 60 | } 61 | } 62 | ]) 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-admin-design", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "The back-end management system based on Koa developed for admin-design", 6 | "author": { 7 | "name": "baimingxuan", 8 | "email": "354372738@qq.com", 9 | "url": "https://github.com/baimingxuan" 10 | }, 11 | "scripts": { 12 | "start": "node bin/www", 13 | "dev": "nodemon bin/www", 14 | "prod": "pm2 start bin/www" 15 | }, 16 | "dependencies": { 17 | "@koa/cors": "^5.0.0", 18 | "bcryptjs": "^3.0.2", 19 | "joi": "^17.13.3", 20 | "jsonwebtoken": "^9.0.2", 21 | "jwt-decode": "^4.0.0", 22 | "koa": "^2.16.0", 23 | "koa-bodyparser": "^4.4.1", 24 | "koa-helmet": "^8.0.1", 25 | "koa-jwt": "^4.0.4", 26 | "koa-logger": "^3.2.1", 27 | "koa-router": "^13.0.1", 28 | "koa-session": "^7.0.2", 29 | "koa-static": "^5.0.0", 30 | "lodash": "^4.17.21", 31 | "log4js": "^6.9.1", 32 | "mysql2": "^3.14.0", 33 | "sequelize": "^6.37.7", 34 | "svg-captcha": "^1.4.0" 35 | }, 36 | "devDependencies": { 37 | "@eslint/js": "^9.6.0", 38 | "eslint": "^9.6.0", 39 | "eslint-config-prettier": "^9.1.0", 40 | "eslint-define-config": "^2.1.0", 41 | "eslint-plugin-prettier": "^5.1.3", 42 | "nodemon": "^3.1.9", 43 | "prettier": "^3.3.2" 44 | }, 45 | "keywords": [ 46 | "koa", 47 | "admin", 48 | "koa-admin-design" 49 | ], 50 | "license": "MIT", 51 | "homepage": "https://github.com/baimingxuan/node-admin-design", 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/baimingxuan/node-admin-design.git" 55 | }, 56 | "bugs": { 57 | "url": "https://github.com/baimingxuan/node-admin-design/issues" 58 | }, 59 | "engines": { 60 | "node": ">=20", 61 | "pnpm": ">=10" 62 | }, 63 | "pnpm": { 64 | "allowedDeprecatedVersions": { 65 | "eslint-define-config": "*", 66 | "are-we-there-yet": "*", 67 | "gauge": "*", 68 | "glob": "*", 69 | "inflight": "*", 70 | "npmlog": "*", 71 | "rimraf": "*" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@koa/cors': 12 | specifier: ^5.0.0 13 | version: 5.0.0 14 | bcryptjs: 15 | specifier: ^3.0.2 16 | version: 3.0.2 17 | joi: 18 | specifier: ^17.13.3 19 | version: 17.13.3 20 | jsonwebtoken: 21 | specifier: ^9.0.2 22 | version: 9.0.2 23 | jwt-decode: 24 | specifier: ^4.0.0 25 | version: 4.0.0 26 | koa: 27 | specifier: ^2.16.0 28 | version: 2.16.0 29 | koa-bodyparser: 30 | specifier: ^4.4.1 31 | version: 4.4.1 32 | koa-helmet: 33 | specifier: ^8.0.1 34 | version: 8.0.1(helmet@8.1.0) 35 | koa-jwt: 36 | specifier: ^4.0.4 37 | version: 4.0.4 38 | koa-logger: 39 | specifier: ^3.2.1 40 | version: 3.2.1 41 | koa-router: 42 | specifier: ^13.0.1 43 | version: 13.0.1 44 | koa-session: 45 | specifier: ^7.0.2 46 | version: 7.0.2 47 | koa-static: 48 | specifier: ^5.0.0 49 | version: 5.0.0 50 | lodash: 51 | specifier: ^4.17.21 52 | version: 4.17.21 53 | log4js: 54 | specifier: ^6.9.1 55 | version: 6.9.1 56 | mysql2: 57 | specifier: ^3.14.0 58 | version: 3.14.0 59 | sequelize: 60 | specifier: ^6.37.7 61 | version: 6.37.7(mysql2@3.14.0) 62 | svg-captcha: 63 | specifier: ^1.4.0 64 | version: 1.4.0 65 | devDependencies: 66 | '@eslint/js': 67 | specifier: ^9.6.0 68 | version: 9.23.0 69 | eslint: 70 | specifier: ^9.6.0 71 | version: 9.23.0 72 | eslint-config-prettier: 73 | specifier: ^9.1.0 74 | version: 9.1.0(eslint@9.23.0) 75 | eslint-define-config: 76 | specifier: ^2.1.0 77 | version: 2.1.0 78 | eslint-plugin-prettier: 79 | specifier: ^5.1.3 80 | version: 5.2.6(eslint-config-prettier@9.1.0(eslint@9.23.0))(eslint@9.23.0)(prettier@3.5.3) 81 | nodemon: 82 | specifier: ^3.1.9 83 | version: 3.1.9 84 | prettier: 85 | specifier: ^3.3.2 86 | version: 3.5.3 87 | 88 | packages: 89 | 90 | '@eslint-community/eslint-utils@4.5.1': 91 | resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} 92 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 93 | peerDependencies: 94 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 95 | 96 | '@eslint-community/regexpp@4.12.1': 97 | resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} 98 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 99 | 100 | '@eslint/config-array@0.19.2': 101 | resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==} 102 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 103 | 104 | '@eslint/config-helpers@0.2.1': 105 | resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} 106 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 107 | 108 | '@eslint/core@0.12.0': 109 | resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} 110 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 111 | 112 | '@eslint/core@0.13.0': 113 | resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} 114 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 115 | 116 | '@eslint/eslintrc@3.3.1': 117 | resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} 118 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 119 | 120 | '@eslint/js@9.23.0': 121 | resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==} 122 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 123 | 124 | '@eslint/object-schema@2.1.6': 125 | resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} 126 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 127 | 128 | '@eslint/plugin-kit@0.2.8': 129 | resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} 130 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 131 | 132 | '@hapi/bourne@3.0.0': 133 | resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} 134 | 135 | '@hapi/hoek@9.3.0': 136 | resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} 137 | 138 | '@hapi/topo@5.1.0': 139 | resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} 140 | 141 | '@humanfs/core@0.19.1': 142 | resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 143 | engines: {node: '>=18.18.0'} 144 | 145 | '@humanfs/node@0.16.6': 146 | resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} 147 | engines: {node: '>=18.18.0'} 148 | 149 | '@humanwhocodes/module-importer@1.0.1': 150 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 151 | engines: {node: '>=12.22'} 152 | 153 | '@humanwhocodes/retry@0.3.1': 154 | resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} 155 | engines: {node: '>=18.18'} 156 | 157 | '@humanwhocodes/retry@0.4.2': 158 | resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} 159 | engines: {node: '>=18.18'} 160 | 161 | '@koa/cors@5.0.0': 162 | resolution: {integrity: sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==} 163 | engines: {node: '>= 14.0.0'} 164 | 165 | '@pkgr/core@0.2.0': 166 | resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==} 167 | engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 168 | 169 | '@sideway/address@4.1.5': 170 | resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} 171 | 172 | '@sideway/formula@3.0.1': 173 | resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} 174 | 175 | '@sideway/pinpoint@2.0.0': 176 | resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} 177 | 178 | '@types/debug@4.1.12': 179 | resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} 180 | 181 | '@types/estree@1.0.7': 182 | resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 183 | 184 | '@types/json-schema@7.0.15': 185 | resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 186 | 187 | '@types/ms@2.1.0': 188 | resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} 189 | 190 | '@types/node@22.14.0': 191 | resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} 192 | 193 | '@types/validator@13.12.3': 194 | resolution: {integrity: sha512-2ipwZ2NydGQJImne+FhNdhgRM37e9lCev99KnqkbFHd94Xn/mErARWI1RSLem1QA19ch5kOhzIZd7e8CA2FI8g==} 195 | 196 | accepts@1.3.8: 197 | resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} 198 | engines: {node: '>= 0.6'} 199 | 200 | acorn-jsx@5.3.2: 201 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 202 | peerDependencies: 203 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 204 | 205 | acorn@8.14.1: 206 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 207 | engines: {node: '>=0.4.0'} 208 | hasBin: true 209 | 210 | aggregate-error@3.1.0: 211 | resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} 212 | engines: {node: '>=8'} 213 | 214 | ajv@6.12.6: 215 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 216 | 217 | ansi-styles@3.2.1: 218 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 219 | engines: {node: '>=4'} 220 | 221 | ansi-styles@4.3.0: 222 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 223 | engines: {node: '>=8'} 224 | 225 | anymatch@3.1.3: 226 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 227 | engines: {node: '>= 8'} 228 | 229 | argparse@2.0.1: 230 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 231 | 232 | aws-ssl-profiles@1.1.2: 233 | resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} 234 | engines: {node: '>= 6.0.0'} 235 | 236 | balanced-match@1.0.2: 237 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 238 | 239 | base64-js@1.5.1: 240 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 241 | 242 | bcryptjs@3.0.2: 243 | resolution: {integrity: sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==} 244 | hasBin: true 245 | 246 | binary-extensions@2.3.0: 247 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 248 | engines: {node: '>=8'} 249 | 250 | brace-expansion@1.1.11: 251 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 252 | 253 | braces@3.0.3: 254 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 255 | engines: {node: '>=8'} 256 | 257 | buffer-equal-constant-time@1.0.1: 258 | resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} 259 | 260 | buffer@5.7.1: 261 | resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 262 | 263 | bytes@3.1.2: 264 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 265 | engines: {node: '>= 0.8'} 266 | 267 | cache-content-type@1.0.1: 268 | resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} 269 | engines: {node: '>= 6.0.0'} 270 | 271 | call-bind-apply-helpers@1.0.2: 272 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 273 | engines: {node: '>= 0.4'} 274 | 275 | call-bound@1.0.4: 276 | resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 277 | engines: {node: '>= 0.4'} 278 | 279 | callsites@3.1.0: 280 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 281 | engines: {node: '>=6'} 282 | 283 | chalk@2.4.2: 284 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 285 | engines: {node: '>=4'} 286 | 287 | chalk@4.1.2: 288 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 289 | engines: {node: '>=10'} 290 | 291 | chokidar@3.6.0: 292 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 293 | engines: {node: '>= 8.10.0'} 294 | 295 | clean-stack@2.2.0: 296 | resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} 297 | engines: {node: '>=6'} 298 | 299 | co-body@6.2.0: 300 | resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==} 301 | engines: {node: '>=8.0.0'} 302 | 303 | co@4.6.0: 304 | resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} 305 | engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} 306 | 307 | color-convert@1.9.3: 308 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 309 | 310 | color-convert@2.0.1: 311 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 312 | engines: {node: '>=7.0.0'} 313 | 314 | color-name@1.1.3: 315 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 316 | 317 | color-name@1.1.4: 318 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 319 | 320 | concat-map@0.0.1: 321 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 322 | 323 | content-disposition@0.5.4: 324 | resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} 325 | engines: {node: '>= 0.6'} 326 | 327 | content-type@1.0.5: 328 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 329 | engines: {node: '>= 0.6'} 330 | 331 | cookies@0.9.1: 332 | resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} 333 | engines: {node: '>= 0.8'} 334 | 335 | copy-to@2.0.1: 336 | resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} 337 | 338 | crc@3.8.0: 339 | resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} 340 | 341 | cross-spawn@7.0.6: 342 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 343 | engines: {node: '>= 8'} 344 | 345 | date-format@4.0.14: 346 | resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} 347 | engines: {node: '>=4.0'} 348 | 349 | debug@3.2.7: 350 | resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} 351 | peerDependencies: 352 | supports-color: '*' 353 | peerDependenciesMeta: 354 | supports-color: 355 | optional: true 356 | 357 | debug@4.4.0: 358 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 359 | engines: {node: '>=6.0'} 360 | peerDependencies: 361 | supports-color: '*' 362 | peerDependenciesMeta: 363 | supports-color: 364 | optional: true 365 | 366 | deep-equal@1.0.1: 367 | resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} 368 | 369 | deep-is@0.1.4: 370 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 371 | 372 | delegates@1.0.0: 373 | resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} 374 | 375 | denque@2.1.0: 376 | resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} 377 | engines: {node: '>=0.10'} 378 | 379 | depd@1.1.2: 380 | resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} 381 | engines: {node: '>= 0.6'} 382 | 383 | depd@2.0.0: 384 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 385 | engines: {node: '>= 0.8'} 386 | 387 | destroy@1.2.0: 388 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 389 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 390 | 391 | dottie@2.0.6: 392 | resolution: {integrity: sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==} 393 | 394 | dunder-proto@1.0.1: 395 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 396 | engines: {node: '>= 0.4'} 397 | 398 | ecdsa-sig-formatter@1.0.11: 399 | resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} 400 | 401 | ee-first@1.1.1: 402 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 403 | 404 | encodeurl@1.0.2: 405 | resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} 406 | engines: {node: '>= 0.8'} 407 | 408 | es-define-property@1.0.1: 409 | resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 410 | engines: {node: '>= 0.4'} 411 | 412 | es-errors@1.3.0: 413 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 414 | engines: {node: '>= 0.4'} 415 | 416 | es-object-atoms@1.1.1: 417 | resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 418 | engines: {node: '>= 0.4'} 419 | 420 | escape-html@1.0.3: 421 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 422 | 423 | escape-string-regexp@1.0.5: 424 | resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 425 | engines: {node: '>=0.8.0'} 426 | 427 | escape-string-regexp@4.0.0: 428 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 429 | engines: {node: '>=10'} 430 | 431 | eslint-config-prettier@9.1.0: 432 | resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} 433 | hasBin: true 434 | peerDependencies: 435 | eslint: '>=7.0.0' 436 | 437 | eslint-define-config@2.1.0: 438 | resolution: {integrity: sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==} 439 | engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>=8.6.0'} 440 | deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. 441 | 442 | eslint-plugin-prettier@5.2.6: 443 | resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} 444 | engines: {node: ^14.18.0 || >=16.0.0} 445 | peerDependencies: 446 | '@types/eslint': '>=8.0.0' 447 | eslint: '>=8.0.0' 448 | eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' 449 | prettier: '>=3.0.0' 450 | peerDependenciesMeta: 451 | '@types/eslint': 452 | optional: true 453 | eslint-config-prettier: 454 | optional: true 455 | 456 | eslint-scope@8.3.0: 457 | resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} 458 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 459 | 460 | eslint-visitor-keys@3.4.3: 461 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 462 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 463 | 464 | eslint-visitor-keys@4.2.0: 465 | resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} 466 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 467 | 468 | eslint@9.23.0: 469 | resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==} 470 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 471 | hasBin: true 472 | peerDependencies: 473 | jiti: '*' 474 | peerDependenciesMeta: 475 | jiti: 476 | optional: true 477 | 478 | espree@10.3.0: 479 | resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} 480 | engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 481 | 482 | esquery@1.6.0: 483 | resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 484 | engines: {node: '>=0.10'} 485 | 486 | esrecurse@4.3.0: 487 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 488 | engines: {node: '>=4.0'} 489 | 490 | estraverse@5.3.0: 491 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 492 | engines: {node: '>=4.0'} 493 | 494 | esutils@2.0.3: 495 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 496 | engines: {node: '>=0.10.0'} 497 | 498 | fast-deep-equal@3.1.3: 499 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 500 | 501 | fast-diff@1.3.0: 502 | resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} 503 | 504 | fast-json-stable-stringify@2.1.0: 505 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 506 | 507 | fast-levenshtein@2.0.6: 508 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 509 | 510 | file-entry-cache@8.0.0: 511 | resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 512 | engines: {node: '>=16.0.0'} 513 | 514 | fill-range@7.1.1: 515 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 516 | engines: {node: '>=8'} 517 | 518 | find-up@5.0.0: 519 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 520 | engines: {node: '>=10'} 521 | 522 | flat-cache@4.0.1: 523 | resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 524 | engines: {node: '>=16'} 525 | 526 | flatted@3.3.3: 527 | resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 528 | 529 | fresh@0.5.2: 530 | resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} 531 | engines: {node: '>= 0.6'} 532 | 533 | fs-extra@8.1.0: 534 | resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} 535 | engines: {node: '>=6 <7 || >=8'} 536 | 537 | fsevents@2.3.3: 538 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 539 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 540 | os: [darwin] 541 | 542 | function-bind@1.1.2: 543 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 544 | 545 | generate-function@2.3.1: 546 | resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} 547 | 548 | get-intrinsic@1.3.0: 549 | resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 550 | engines: {node: '>= 0.4'} 551 | 552 | get-proto@1.0.1: 553 | resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 554 | engines: {node: '>= 0.4'} 555 | 556 | glob-parent@5.1.2: 557 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 558 | engines: {node: '>= 6'} 559 | 560 | glob-parent@6.0.2: 561 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 562 | engines: {node: '>=10.13.0'} 563 | 564 | globals@14.0.0: 565 | resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 566 | engines: {node: '>=18'} 567 | 568 | gopd@1.2.0: 569 | resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 570 | engines: {node: '>= 0.4'} 571 | 572 | graceful-fs@4.2.11: 573 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 574 | 575 | has-flag@3.0.0: 576 | resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 577 | engines: {node: '>=4'} 578 | 579 | has-flag@4.0.0: 580 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 581 | engines: {node: '>=8'} 582 | 583 | has-symbols@1.1.0: 584 | resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 585 | engines: {node: '>= 0.4'} 586 | 587 | has-tostringtag@1.0.2: 588 | resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} 589 | engines: {node: '>= 0.4'} 590 | 591 | hasown@2.0.2: 592 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 593 | engines: {node: '>= 0.4'} 594 | 595 | helmet@8.1.0: 596 | resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} 597 | engines: {node: '>=18.0.0'} 598 | 599 | http-assert@1.5.0: 600 | resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} 601 | engines: {node: '>= 0.8'} 602 | 603 | http-errors@1.6.3: 604 | resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} 605 | engines: {node: '>= 0.6'} 606 | 607 | http-errors@1.8.1: 608 | resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} 609 | engines: {node: '>= 0.6'} 610 | 611 | http-errors@2.0.0: 612 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 613 | engines: {node: '>= 0.8'} 614 | 615 | humanize-number@0.0.2: 616 | resolution: {integrity: sha512-un3ZAcNQGI7RzaWGZzQDH47HETM4Wrj6z6E4TId8Yeq9w5ZKUVB1nrT2jwFheTUjEmqcgTjXDc959jum+ai1kQ==} 617 | 618 | iconv-lite@0.4.24: 619 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 620 | engines: {node: '>=0.10.0'} 621 | 622 | iconv-lite@0.6.3: 623 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 624 | engines: {node: '>=0.10.0'} 625 | 626 | ieee754@1.2.1: 627 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 628 | 629 | ignore-by-default@1.0.1: 630 | resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} 631 | 632 | ignore@5.3.2: 633 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 634 | engines: {node: '>= 4'} 635 | 636 | import-fresh@3.3.1: 637 | resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 638 | engines: {node: '>=6'} 639 | 640 | imurmurhash@0.1.4: 641 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 642 | engines: {node: '>=0.8.19'} 643 | 644 | indent-string@4.0.0: 645 | resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} 646 | engines: {node: '>=8'} 647 | 648 | inflation@2.1.0: 649 | resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} 650 | engines: {node: '>= 0.8.0'} 651 | 652 | inflection@1.13.4: 653 | resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} 654 | engines: {'0': node >= 0.4.0} 655 | 656 | inherits@2.0.3: 657 | resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} 658 | 659 | inherits@2.0.4: 660 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 661 | 662 | is-binary-path@2.1.0: 663 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 664 | engines: {node: '>=8'} 665 | 666 | is-extglob@2.1.1: 667 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 668 | engines: {node: '>=0.10.0'} 669 | 670 | is-generator-function@1.1.0: 671 | resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} 672 | engines: {node: '>= 0.4'} 673 | 674 | is-glob@4.0.3: 675 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 676 | engines: {node: '>=0.10.0'} 677 | 678 | is-number@7.0.0: 679 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 680 | engines: {node: '>=0.12.0'} 681 | 682 | is-property@1.0.2: 683 | resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} 684 | 685 | is-regex@1.2.1: 686 | resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} 687 | engines: {node: '>= 0.4'} 688 | 689 | is-type-of@2.2.0: 690 | resolution: {integrity: sha512-72axShMJMnMy5HSU/jLGNOonZD5rWM0MwJSCYpKCTQCbggQZBJO/CLMMVP5HgS8kPSYFBkTysJexsD6NMvGKDQ==} 691 | 692 | isexe@2.0.0: 693 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 694 | 695 | joi@17.13.3: 696 | resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} 697 | 698 | js-yaml@4.1.0: 699 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 700 | hasBin: true 701 | 702 | json-buffer@3.0.1: 703 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 704 | 705 | json-schema-traverse@0.4.1: 706 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 707 | 708 | json-stable-stringify-without-jsonify@1.0.1: 709 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 710 | 711 | jsonfile@4.0.0: 712 | resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} 713 | 714 | jsonwebtoken@9.0.2: 715 | resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} 716 | engines: {node: '>=12', npm: '>=6'} 717 | 718 | jwa@1.4.1: 719 | resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} 720 | 721 | jws@3.2.2: 722 | resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} 723 | 724 | jwt-decode@4.0.0: 725 | resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} 726 | engines: {node: '>=18'} 727 | 728 | keygrip@1.1.0: 729 | resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} 730 | engines: {node: '>= 0.6'} 731 | 732 | keyv@4.5.4: 733 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 734 | 735 | koa-bodyparser@4.4.1: 736 | resolution: {integrity: sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==} 737 | engines: {node: '>=8.0.0'} 738 | 739 | koa-compose@4.1.0: 740 | resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} 741 | 742 | koa-convert@2.0.0: 743 | resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} 744 | engines: {node: '>= 10'} 745 | 746 | koa-helmet@8.0.1: 747 | resolution: {integrity: sha512-lXDqTqNLgqyOKEuCSF3MFReJmEQQ0GD0NzkdSe9dKH87NSMc5GxAA7H5mYaAT+UJypYkIS1lPNRqTuEUgl+l3Q==} 748 | engines: {node: '>= 18.0.0'} 749 | peerDependencies: 750 | helmet: '>= 6' 751 | 752 | koa-jwt@4.0.4: 753 | resolution: {integrity: sha512-Tid9BQfpVtUG/8YZV38a+hDKll0pfVhfl7A/2cNaYThS1cxMFXylZzfARqHQqvNhHy9qM+qkxd4/z6EaIV4SAQ==} 754 | engines: {node: '>= 8'} 755 | 756 | koa-logger@3.2.1: 757 | resolution: {integrity: sha512-MjlznhLLKy9+kG8nAXKJLM0/ClsQp/Or2vI3a5rbSQmgl8IJBQO0KI5FA70BvW+hqjtxjp49SpH2E7okS6NmHg==} 758 | engines: {node: '>= 7.6.0'} 759 | 760 | koa-router@13.0.1: 761 | resolution: {integrity: sha512-4/sijXdSxocIe2wv7RFFSxvo2ic1pDzPSmy11yCGztng1hx408qfw1wVmN3aqhQaU7U6nJ039JKC8ObE73Ohgw==} 762 | engines: {node: '>= 18'} 763 | 764 | koa-send@5.0.1: 765 | resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==} 766 | engines: {node: '>= 8'} 767 | 768 | koa-session@7.0.2: 769 | resolution: {integrity: sha512-nMWJndLmIuKQMTYPr5NokGQOGD2Aqal5GVi1xAhrQjrrzKq1ASy1WTFVkZ/xhwhtC4KpWi5KdqNYewZo7KJb4w==} 770 | engines: {node: '>= 18.19.0'} 771 | 772 | koa-static@5.0.0: 773 | resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} 774 | engines: {node: '>= 7.6.0'} 775 | 776 | koa-unless@1.0.7: 777 | resolution: {integrity: sha512-NKiz+nk4KxSJFskiJMuJvxeA41Lcnx3d8Zy+8QETgifm4ab4aOeGD3RgR6bIz0FGNWwo3Fz0DtnK77mEIqHWxA==} 778 | 779 | koa@2.16.0: 780 | resolution: {integrity: sha512-Afhqq0Vq3W7C+/rW6IqHVBDLzqObwZ07JaUNUEF8yCQ6afiyFE3RAy+i7V0E46XOWlH7vPWn/x0vsZwNy6PWxw==} 781 | engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} 782 | 783 | levn@0.4.1: 784 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 785 | engines: {node: '>= 0.8.0'} 786 | 787 | locate-path@6.0.0: 788 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 789 | engines: {node: '>=10'} 790 | 791 | lodash.includes@4.3.0: 792 | resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} 793 | 794 | lodash.isboolean@3.0.3: 795 | resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} 796 | 797 | lodash.isinteger@4.0.4: 798 | resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} 799 | 800 | lodash.isnumber@3.0.3: 801 | resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} 802 | 803 | lodash.isplainobject@4.0.6: 804 | resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} 805 | 806 | lodash.isstring@4.0.1: 807 | resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} 808 | 809 | lodash.merge@4.6.2: 810 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 811 | 812 | lodash.once@4.1.1: 813 | resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} 814 | 815 | lodash@4.17.21: 816 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 817 | 818 | log4js@6.9.1: 819 | resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} 820 | engines: {node: '>=8.0'} 821 | 822 | long@5.3.1: 823 | resolution: {integrity: sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==} 824 | 825 | lru-cache@7.18.3: 826 | resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} 827 | engines: {node: '>=12'} 828 | 829 | lru.min@1.1.2: 830 | resolution: {integrity: sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==} 831 | engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} 832 | 833 | math-intrinsics@1.1.0: 834 | resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 835 | engines: {node: '>= 0.4'} 836 | 837 | media-typer@0.3.0: 838 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} 839 | engines: {node: '>= 0.6'} 840 | 841 | mime-db@1.52.0: 842 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 843 | engines: {node: '>= 0.6'} 844 | 845 | mime-types@2.1.35: 846 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 847 | engines: {node: '>= 0.6'} 848 | 849 | minimatch@3.1.2: 850 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 851 | 852 | moment-timezone@0.5.48: 853 | resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==} 854 | 855 | moment@2.30.1: 856 | resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} 857 | 858 | ms@2.1.3: 859 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 860 | 861 | mysql2@3.14.0: 862 | resolution: {integrity: sha512-8eMhmG6gt/hRkU1G+8KlGOdQi2w+CgtNoD1ksXZq9gQfkfDsX4LHaBwTe1SY0Imx//t2iZA03DFnyYKPinxSRw==} 863 | engines: {node: '>= 8.0'} 864 | 865 | named-placeholders@1.1.3: 866 | resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} 867 | engines: {node: '>=12.0.0'} 868 | 869 | natural-compare@1.4.0: 870 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 871 | 872 | negotiator@0.6.3: 873 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} 874 | engines: {node: '>= 0.6'} 875 | 876 | nodemon@3.1.9: 877 | resolution: {integrity: sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==} 878 | engines: {node: '>=10'} 879 | hasBin: true 880 | 881 | normalize-path@3.0.0: 882 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 883 | engines: {node: '>=0.10.0'} 884 | 885 | object-inspect@1.13.4: 886 | resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} 887 | engines: {node: '>= 0.4'} 888 | 889 | on-finished@2.4.1: 890 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 891 | engines: {node: '>= 0.8'} 892 | 893 | only@0.0.2: 894 | resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} 895 | 896 | opentype.js@0.7.3: 897 | resolution: {integrity: sha512-Veui5vl2bLonFJ/SjX/WRWJT3SncgiZNnKUyahmXCc2sa1xXW15u3R/3TN5+JFiP7RsjK5ER4HA5eWaEmV9deA==} 898 | hasBin: true 899 | 900 | optionator@0.9.4: 901 | resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 902 | engines: {node: '>= 0.8.0'} 903 | 904 | p-any@2.1.0: 905 | resolution: {integrity: sha512-JAERcaMBLYKMq+voYw36+x5Dgh47+/o7yuv2oQYuSSUml4YeqJEFznBrY2UeEkoSHqBua6hz518n/PsowTYLLg==} 906 | engines: {node: '>=8'} 907 | 908 | p-cancelable@2.1.1: 909 | resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} 910 | engines: {node: '>=8'} 911 | 912 | p-limit@3.1.0: 913 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 914 | engines: {node: '>=10'} 915 | 916 | p-locate@5.0.0: 917 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 918 | engines: {node: '>=10'} 919 | 920 | p-some@4.1.0: 921 | resolution: {integrity: sha512-MF/HIbq6GeBqTrTIl5OJubzkGU+qfFhAFi0gnTAK6rgEIJIknEiABHOTtQu4e6JiXjIwuMPMUFQzyHh5QjCl1g==} 922 | engines: {node: '>=8'} 923 | 924 | parent-module@1.0.1: 925 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 926 | engines: {node: '>=6'} 927 | 928 | parseurl@1.3.3: 929 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 930 | engines: {node: '>= 0.8'} 931 | 932 | passthrough-counter@1.0.0: 933 | resolution: {integrity: sha512-Wy8PXTLqPAN0oEgBrlnsXPMww3SYJ44tQ8aVrGAI4h4JZYCS0oYqsPqtPR8OhJpv6qFbpbB7XAn0liKV7EXubA==} 934 | 935 | path-exists@4.0.0: 936 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 937 | engines: {node: '>=8'} 938 | 939 | path-is-absolute@1.0.1: 940 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 941 | engines: {node: '>=0.10.0'} 942 | 943 | path-key@3.1.1: 944 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 945 | engines: {node: '>=8'} 946 | 947 | path-to-regexp@8.2.0: 948 | resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} 949 | engines: {node: '>=16'} 950 | 951 | pg-connection-string@2.7.0: 952 | resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} 953 | 954 | picomatch@2.3.1: 955 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 956 | engines: {node: '>=8.6'} 957 | 958 | prelude-ls@1.2.1: 959 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 960 | engines: {node: '>= 0.8.0'} 961 | 962 | prettier-linter-helpers@1.0.0: 963 | resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} 964 | engines: {node: '>=6.0.0'} 965 | 966 | prettier@3.5.3: 967 | resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} 968 | engines: {node: '>=14'} 969 | hasBin: true 970 | 971 | pstree.remy@1.1.8: 972 | resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} 973 | 974 | punycode@2.3.1: 975 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 976 | engines: {node: '>=6'} 977 | 978 | qs@6.14.0: 979 | resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} 980 | engines: {node: '>=0.6'} 981 | 982 | raw-body@2.5.2: 983 | resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} 984 | engines: {node: '>= 0.8'} 985 | 986 | readdirp@3.6.0: 987 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 988 | engines: {node: '>=8.10.0'} 989 | 990 | resolve-from@4.0.0: 991 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 992 | engines: {node: '>=4'} 993 | 994 | resolve-path@1.4.0: 995 | resolution: {integrity: sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==} 996 | engines: {node: '>= 0.8'} 997 | 998 | retry-as-promised@7.1.1: 999 | resolution: {integrity: sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw==} 1000 | 1001 | rfdc@1.4.1: 1002 | resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} 1003 | 1004 | safe-buffer@5.2.1: 1005 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1006 | 1007 | safe-regex-test@1.1.0: 1008 | resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} 1009 | engines: {node: '>= 0.4'} 1010 | 1011 | safer-buffer@2.1.2: 1012 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1013 | 1014 | semver@7.7.1: 1015 | resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} 1016 | engines: {node: '>=10'} 1017 | hasBin: true 1018 | 1019 | seq-queue@0.0.5: 1020 | resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} 1021 | 1022 | sequelize-pool@7.1.0: 1023 | resolution: {integrity: sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==} 1024 | engines: {node: '>= 10.0.0'} 1025 | 1026 | sequelize@6.37.7: 1027 | resolution: {integrity: sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA==} 1028 | engines: {node: '>=10.0.0'} 1029 | peerDependencies: 1030 | ibm_db: '*' 1031 | mariadb: '*' 1032 | mysql2: '*' 1033 | oracledb: '*' 1034 | pg: '*' 1035 | pg-hstore: '*' 1036 | snowflake-sdk: '*' 1037 | sqlite3: '*' 1038 | tedious: '*' 1039 | peerDependenciesMeta: 1040 | ibm_db: 1041 | optional: true 1042 | mariadb: 1043 | optional: true 1044 | mysql2: 1045 | optional: true 1046 | oracledb: 1047 | optional: true 1048 | pg: 1049 | optional: true 1050 | pg-hstore: 1051 | optional: true 1052 | snowflake-sdk: 1053 | optional: true 1054 | sqlite3: 1055 | optional: true 1056 | tedious: 1057 | optional: true 1058 | 1059 | setprototypeof@1.1.0: 1060 | resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} 1061 | 1062 | setprototypeof@1.2.0: 1063 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 1064 | 1065 | shebang-command@2.0.0: 1066 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1067 | engines: {node: '>=8'} 1068 | 1069 | shebang-regex@3.0.0: 1070 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1071 | engines: {node: '>=8'} 1072 | 1073 | side-channel-list@1.0.0: 1074 | resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} 1075 | engines: {node: '>= 0.4'} 1076 | 1077 | side-channel-map@1.0.1: 1078 | resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} 1079 | engines: {node: '>= 0.4'} 1080 | 1081 | side-channel-weakmap@1.0.2: 1082 | resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} 1083 | engines: {node: '>= 0.4'} 1084 | 1085 | side-channel@1.1.0: 1086 | resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} 1087 | engines: {node: '>= 0.4'} 1088 | 1089 | simple-update-notifier@2.0.0: 1090 | resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} 1091 | engines: {node: '>=10'} 1092 | 1093 | sqlstring@2.3.3: 1094 | resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} 1095 | engines: {node: '>= 0.6'} 1096 | 1097 | statuses@1.5.0: 1098 | resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} 1099 | engines: {node: '>= 0.6'} 1100 | 1101 | statuses@2.0.1: 1102 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 1103 | engines: {node: '>= 0.8'} 1104 | 1105 | streamroller@3.1.5: 1106 | resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} 1107 | engines: {node: '>=8.0'} 1108 | 1109 | strip-json-comments@3.1.1: 1110 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1111 | engines: {node: '>=8'} 1112 | 1113 | supports-color@5.5.0: 1114 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 1115 | engines: {node: '>=4'} 1116 | 1117 | supports-color@7.2.0: 1118 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1119 | engines: {node: '>=8'} 1120 | 1121 | svg-captcha@1.4.0: 1122 | resolution: {integrity: sha512-/fkkhavXPE57zRRCjNqAP3txRCSncpMx3NnNZL7iEoyAtYwUjPhJxW6FQTQPG5UPEmCrbFoXS10C3YdJlW7PDg==} 1123 | engines: {node: '>=4.x'} 1124 | 1125 | synckit@0.11.1: 1126 | resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==} 1127 | engines: {node: ^14.18.0 || >=16.0.0} 1128 | 1129 | tiny-inflate@1.0.3: 1130 | resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} 1131 | 1132 | to-regex-range@5.0.1: 1133 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1134 | engines: {node: '>=8.0'} 1135 | 1136 | toidentifier@1.0.1: 1137 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 1138 | engines: {node: '>=0.6'} 1139 | 1140 | toposort-class@1.0.1: 1141 | resolution: {integrity: sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg==} 1142 | 1143 | touch@3.1.1: 1144 | resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} 1145 | hasBin: true 1146 | 1147 | tslib@2.8.1: 1148 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1149 | 1150 | tsscmp@1.0.6: 1151 | resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} 1152 | engines: {node: '>=0.6.x'} 1153 | 1154 | type-check@0.4.0: 1155 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1156 | engines: {node: '>= 0.8.0'} 1157 | 1158 | type-fest@0.3.1: 1159 | resolution: {integrity: sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==} 1160 | engines: {node: '>=6'} 1161 | 1162 | type-is@1.6.18: 1163 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 1164 | engines: {node: '>= 0.6'} 1165 | 1166 | undefsafe@2.0.5: 1167 | resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} 1168 | 1169 | undici-types@6.21.0: 1170 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 1171 | 1172 | universalify@0.1.2: 1173 | resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} 1174 | engines: {node: '>= 4.0.0'} 1175 | 1176 | unpipe@1.0.0: 1177 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 1178 | engines: {node: '>= 0.8'} 1179 | 1180 | uri-js@4.4.1: 1181 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1182 | 1183 | uuid@8.3.2: 1184 | resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 1185 | hasBin: true 1186 | 1187 | validator@13.15.0: 1188 | resolution: {integrity: sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==} 1189 | engines: {node: '>= 0.10'} 1190 | 1191 | vary@1.1.2: 1192 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 1193 | engines: {node: '>= 0.8'} 1194 | 1195 | which@2.0.2: 1196 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1197 | engines: {node: '>= 8'} 1198 | hasBin: true 1199 | 1200 | wkx@0.5.0: 1201 | resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} 1202 | 1203 | word-wrap@1.2.5: 1204 | resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 1205 | engines: {node: '>=0.10.0'} 1206 | 1207 | ylru@1.4.0: 1208 | resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} 1209 | engines: {node: '>= 4.0.0'} 1210 | 1211 | yocto-queue@0.1.0: 1212 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1213 | engines: {node: '>=10'} 1214 | 1215 | zod@3.24.3: 1216 | resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} 1217 | 1218 | snapshots: 1219 | 1220 | '@eslint-community/eslint-utils@4.5.1(eslint@9.23.0)': 1221 | dependencies: 1222 | eslint: 9.23.0 1223 | eslint-visitor-keys: 3.4.3 1224 | 1225 | '@eslint-community/regexpp@4.12.1': {} 1226 | 1227 | '@eslint/config-array@0.19.2': 1228 | dependencies: 1229 | '@eslint/object-schema': 2.1.6 1230 | debug: 4.4.0(supports-color@5.5.0) 1231 | minimatch: 3.1.2 1232 | transitivePeerDependencies: 1233 | - supports-color 1234 | 1235 | '@eslint/config-helpers@0.2.1': {} 1236 | 1237 | '@eslint/core@0.12.0': 1238 | dependencies: 1239 | '@types/json-schema': 7.0.15 1240 | 1241 | '@eslint/core@0.13.0': 1242 | dependencies: 1243 | '@types/json-schema': 7.0.15 1244 | 1245 | '@eslint/eslintrc@3.3.1': 1246 | dependencies: 1247 | ajv: 6.12.6 1248 | debug: 4.4.0(supports-color@5.5.0) 1249 | espree: 10.3.0 1250 | globals: 14.0.0 1251 | ignore: 5.3.2 1252 | import-fresh: 3.3.1 1253 | js-yaml: 4.1.0 1254 | minimatch: 3.1.2 1255 | strip-json-comments: 3.1.1 1256 | transitivePeerDependencies: 1257 | - supports-color 1258 | 1259 | '@eslint/js@9.23.0': {} 1260 | 1261 | '@eslint/object-schema@2.1.6': {} 1262 | 1263 | '@eslint/plugin-kit@0.2.8': 1264 | dependencies: 1265 | '@eslint/core': 0.13.0 1266 | levn: 0.4.1 1267 | 1268 | '@hapi/bourne@3.0.0': {} 1269 | 1270 | '@hapi/hoek@9.3.0': {} 1271 | 1272 | '@hapi/topo@5.1.0': 1273 | dependencies: 1274 | '@hapi/hoek': 9.3.0 1275 | 1276 | '@humanfs/core@0.19.1': {} 1277 | 1278 | '@humanfs/node@0.16.6': 1279 | dependencies: 1280 | '@humanfs/core': 0.19.1 1281 | '@humanwhocodes/retry': 0.3.1 1282 | 1283 | '@humanwhocodes/module-importer@1.0.1': {} 1284 | 1285 | '@humanwhocodes/retry@0.3.1': {} 1286 | 1287 | '@humanwhocodes/retry@0.4.2': {} 1288 | 1289 | '@koa/cors@5.0.0': 1290 | dependencies: 1291 | vary: 1.1.2 1292 | 1293 | '@pkgr/core@0.2.0': {} 1294 | 1295 | '@sideway/address@4.1.5': 1296 | dependencies: 1297 | '@hapi/hoek': 9.3.0 1298 | 1299 | '@sideway/formula@3.0.1': {} 1300 | 1301 | '@sideway/pinpoint@2.0.0': {} 1302 | 1303 | '@types/debug@4.1.12': 1304 | dependencies: 1305 | '@types/ms': 2.1.0 1306 | 1307 | '@types/estree@1.0.7': {} 1308 | 1309 | '@types/json-schema@7.0.15': {} 1310 | 1311 | '@types/ms@2.1.0': {} 1312 | 1313 | '@types/node@22.14.0': 1314 | dependencies: 1315 | undici-types: 6.21.0 1316 | 1317 | '@types/validator@13.12.3': {} 1318 | 1319 | accepts@1.3.8: 1320 | dependencies: 1321 | mime-types: 2.1.35 1322 | negotiator: 0.6.3 1323 | 1324 | acorn-jsx@5.3.2(acorn@8.14.1): 1325 | dependencies: 1326 | acorn: 8.14.1 1327 | 1328 | acorn@8.14.1: {} 1329 | 1330 | aggregate-error@3.1.0: 1331 | dependencies: 1332 | clean-stack: 2.2.0 1333 | indent-string: 4.0.0 1334 | 1335 | ajv@6.12.6: 1336 | dependencies: 1337 | fast-deep-equal: 3.1.3 1338 | fast-json-stable-stringify: 2.1.0 1339 | json-schema-traverse: 0.4.1 1340 | uri-js: 4.4.1 1341 | 1342 | ansi-styles@3.2.1: 1343 | dependencies: 1344 | color-convert: 1.9.3 1345 | 1346 | ansi-styles@4.3.0: 1347 | dependencies: 1348 | color-convert: 2.0.1 1349 | 1350 | anymatch@3.1.3: 1351 | dependencies: 1352 | normalize-path: 3.0.0 1353 | picomatch: 2.3.1 1354 | 1355 | argparse@2.0.1: {} 1356 | 1357 | aws-ssl-profiles@1.1.2: {} 1358 | 1359 | balanced-match@1.0.2: {} 1360 | 1361 | base64-js@1.5.1: {} 1362 | 1363 | bcryptjs@3.0.2: {} 1364 | 1365 | binary-extensions@2.3.0: {} 1366 | 1367 | brace-expansion@1.1.11: 1368 | dependencies: 1369 | balanced-match: 1.0.2 1370 | concat-map: 0.0.1 1371 | 1372 | braces@3.0.3: 1373 | dependencies: 1374 | fill-range: 7.1.1 1375 | 1376 | buffer-equal-constant-time@1.0.1: {} 1377 | 1378 | buffer@5.7.1: 1379 | dependencies: 1380 | base64-js: 1.5.1 1381 | ieee754: 1.2.1 1382 | 1383 | bytes@3.1.2: {} 1384 | 1385 | cache-content-type@1.0.1: 1386 | dependencies: 1387 | mime-types: 2.1.35 1388 | ylru: 1.4.0 1389 | 1390 | call-bind-apply-helpers@1.0.2: 1391 | dependencies: 1392 | es-errors: 1.3.0 1393 | function-bind: 1.1.2 1394 | 1395 | call-bound@1.0.4: 1396 | dependencies: 1397 | call-bind-apply-helpers: 1.0.2 1398 | get-intrinsic: 1.3.0 1399 | 1400 | callsites@3.1.0: {} 1401 | 1402 | chalk@2.4.2: 1403 | dependencies: 1404 | ansi-styles: 3.2.1 1405 | escape-string-regexp: 1.0.5 1406 | supports-color: 5.5.0 1407 | 1408 | chalk@4.1.2: 1409 | dependencies: 1410 | ansi-styles: 4.3.0 1411 | supports-color: 7.2.0 1412 | 1413 | chokidar@3.6.0: 1414 | dependencies: 1415 | anymatch: 3.1.3 1416 | braces: 3.0.3 1417 | glob-parent: 5.1.2 1418 | is-binary-path: 2.1.0 1419 | is-glob: 4.0.3 1420 | normalize-path: 3.0.0 1421 | readdirp: 3.6.0 1422 | optionalDependencies: 1423 | fsevents: 2.3.3 1424 | 1425 | clean-stack@2.2.0: {} 1426 | 1427 | co-body@6.2.0: 1428 | dependencies: 1429 | '@hapi/bourne': 3.0.0 1430 | inflation: 2.1.0 1431 | qs: 6.14.0 1432 | raw-body: 2.5.2 1433 | type-is: 1.6.18 1434 | 1435 | co@4.6.0: {} 1436 | 1437 | color-convert@1.9.3: 1438 | dependencies: 1439 | color-name: 1.1.3 1440 | 1441 | color-convert@2.0.1: 1442 | dependencies: 1443 | color-name: 1.1.4 1444 | 1445 | color-name@1.1.3: {} 1446 | 1447 | color-name@1.1.4: {} 1448 | 1449 | concat-map@0.0.1: {} 1450 | 1451 | content-disposition@0.5.4: 1452 | dependencies: 1453 | safe-buffer: 5.2.1 1454 | 1455 | content-type@1.0.5: {} 1456 | 1457 | cookies@0.9.1: 1458 | dependencies: 1459 | depd: 2.0.0 1460 | keygrip: 1.1.0 1461 | 1462 | copy-to@2.0.1: {} 1463 | 1464 | crc@3.8.0: 1465 | dependencies: 1466 | buffer: 5.7.1 1467 | 1468 | cross-spawn@7.0.6: 1469 | dependencies: 1470 | path-key: 3.1.1 1471 | shebang-command: 2.0.0 1472 | which: 2.0.2 1473 | 1474 | date-format@4.0.14: {} 1475 | 1476 | debug@3.2.7: 1477 | dependencies: 1478 | ms: 2.1.3 1479 | 1480 | debug@4.4.0(supports-color@5.5.0): 1481 | dependencies: 1482 | ms: 2.1.3 1483 | optionalDependencies: 1484 | supports-color: 5.5.0 1485 | 1486 | deep-equal@1.0.1: {} 1487 | 1488 | deep-is@0.1.4: {} 1489 | 1490 | delegates@1.0.0: {} 1491 | 1492 | denque@2.1.0: {} 1493 | 1494 | depd@1.1.2: {} 1495 | 1496 | depd@2.0.0: {} 1497 | 1498 | destroy@1.2.0: {} 1499 | 1500 | dottie@2.0.6: {} 1501 | 1502 | dunder-proto@1.0.1: 1503 | dependencies: 1504 | call-bind-apply-helpers: 1.0.2 1505 | es-errors: 1.3.0 1506 | gopd: 1.2.0 1507 | 1508 | ecdsa-sig-formatter@1.0.11: 1509 | dependencies: 1510 | safe-buffer: 5.2.1 1511 | 1512 | ee-first@1.1.1: {} 1513 | 1514 | encodeurl@1.0.2: {} 1515 | 1516 | es-define-property@1.0.1: {} 1517 | 1518 | es-errors@1.3.0: {} 1519 | 1520 | es-object-atoms@1.1.1: 1521 | dependencies: 1522 | es-errors: 1.3.0 1523 | 1524 | escape-html@1.0.3: {} 1525 | 1526 | escape-string-regexp@1.0.5: {} 1527 | 1528 | escape-string-regexp@4.0.0: {} 1529 | 1530 | eslint-config-prettier@9.1.0(eslint@9.23.0): 1531 | dependencies: 1532 | eslint: 9.23.0 1533 | 1534 | eslint-define-config@2.1.0: {} 1535 | 1536 | eslint-plugin-prettier@5.2.6(eslint-config-prettier@9.1.0(eslint@9.23.0))(eslint@9.23.0)(prettier@3.5.3): 1537 | dependencies: 1538 | eslint: 9.23.0 1539 | prettier: 3.5.3 1540 | prettier-linter-helpers: 1.0.0 1541 | synckit: 0.11.1 1542 | optionalDependencies: 1543 | eslint-config-prettier: 9.1.0(eslint@9.23.0) 1544 | 1545 | eslint-scope@8.3.0: 1546 | dependencies: 1547 | esrecurse: 4.3.0 1548 | estraverse: 5.3.0 1549 | 1550 | eslint-visitor-keys@3.4.3: {} 1551 | 1552 | eslint-visitor-keys@4.2.0: {} 1553 | 1554 | eslint@9.23.0: 1555 | dependencies: 1556 | '@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0) 1557 | '@eslint-community/regexpp': 4.12.1 1558 | '@eslint/config-array': 0.19.2 1559 | '@eslint/config-helpers': 0.2.1 1560 | '@eslint/core': 0.12.0 1561 | '@eslint/eslintrc': 3.3.1 1562 | '@eslint/js': 9.23.0 1563 | '@eslint/plugin-kit': 0.2.8 1564 | '@humanfs/node': 0.16.6 1565 | '@humanwhocodes/module-importer': 1.0.1 1566 | '@humanwhocodes/retry': 0.4.2 1567 | '@types/estree': 1.0.7 1568 | '@types/json-schema': 7.0.15 1569 | ajv: 6.12.6 1570 | chalk: 4.1.2 1571 | cross-spawn: 7.0.6 1572 | debug: 4.4.0(supports-color@5.5.0) 1573 | escape-string-regexp: 4.0.0 1574 | eslint-scope: 8.3.0 1575 | eslint-visitor-keys: 4.2.0 1576 | espree: 10.3.0 1577 | esquery: 1.6.0 1578 | esutils: 2.0.3 1579 | fast-deep-equal: 3.1.3 1580 | file-entry-cache: 8.0.0 1581 | find-up: 5.0.0 1582 | glob-parent: 6.0.2 1583 | ignore: 5.3.2 1584 | imurmurhash: 0.1.4 1585 | is-glob: 4.0.3 1586 | json-stable-stringify-without-jsonify: 1.0.1 1587 | lodash.merge: 4.6.2 1588 | minimatch: 3.1.2 1589 | natural-compare: 1.4.0 1590 | optionator: 0.9.4 1591 | transitivePeerDependencies: 1592 | - supports-color 1593 | 1594 | espree@10.3.0: 1595 | dependencies: 1596 | acorn: 8.14.1 1597 | acorn-jsx: 5.3.2(acorn@8.14.1) 1598 | eslint-visitor-keys: 4.2.0 1599 | 1600 | esquery@1.6.0: 1601 | dependencies: 1602 | estraverse: 5.3.0 1603 | 1604 | esrecurse@4.3.0: 1605 | dependencies: 1606 | estraverse: 5.3.0 1607 | 1608 | estraverse@5.3.0: {} 1609 | 1610 | esutils@2.0.3: {} 1611 | 1612 | fast-deep-equal@3.1.3: {} 1613 | 1614 | fast-diff@1.3.0: {} 1615 | 1616 | fast-json-stable-stringify@2.1.0: {} 1617 | 1618 | fast-levenshtein@2.0.6: {} 1619 | 1620 | file-entry-cache@8.0.0: 1621 | dependencies: 1622 | flat-cache: 4.0.1 1623 | 1624 | fill-range@7.1.1: 1625 | dependencies: 1626 | to-regex-range: 5.0.1 1627 | 1628 | find-up@5.0.0: 1629 | dependencies: 1630 | locate-path: 6.0.0 1631 | path-exists: 4.0.0 1632 | 1633 | flat-cache@4.0.1: 1634 | dependencies: 1635 | flatted: 3.3.3 1636 | keyv: 4.5.4 1637 | 1638 | flatted@3.3.3: {} 1639 | 1640 | fresh@0.5.2: {} 1641 | 1642 | fs-extra@8.1.0: 1643 | dependencies: 1644 | graceful-fs: 4.2.11 1645 | jsonfile: 4.0.0 1646 | universalify: 0.1.2 1647 | 1648 | fsevents@2.3.3: 1649 | optional: true 1650 | 1651 | function-bind@1.1.2: {} 1652 | 1653 | generate-function@2.3.1: 1654 | dependencies: 1655 | is-property: 1.0.2 1656 | 1657 | get-intrinsic@1.3.0: 1658 | dependencies: 1659 | call-bind-apply-helpers: 1.0.2 1660 | es-define-property: 1.0.1 1661 | es-errors: 1.3.0 1662 | es-object-atoms: 1.1.1 1663 | function-bind: 1.1.2 1664 | get-proto: 1.0.1 1665 | gopd: 1.2.0 1666 | has-symbols: 1.1.0 1667 | hasown: 2.0.2 1668 | math-intrinsics: 1.1.0 1669 | 1670 | get-proto@1.0.1: 1671 | dependencies: 1672 | dunder-proto: 1.0.1 1673 | es-object-atoms: 1.1.1 1674 | 1675 | glob-parent@5.1.2: 1676 | dependencies: 1677 | is-glob: 4.0.3 1678 | 1679 | glob-parent@6.0.2: 1680 | dependencies: 1681 | is-glob: 4.0.3 1682 | 1683 | globals@14.0.0: {} 1684 | 1685 | gopd@1.2.0: {} 1686 | 1687 | graceful-fs@4.2.11: {} 1688 | 1689 | has-flag@3.0.0: {} 1690 | 1691 | has-flag@4.0.0: {} 1692 | 1693 | has-symbols@1.1.0: {} 1694 | 1695 | has-tostringtag@1.0.2: 1696 | dependencies: 1697 | has-symbols: 1.1.0 1698 | 1699 | hasown@2.0.2: 1700 | dependencies: 1701 | function-bind: 1.1.2 1702 | 1703 | helmet@8.1.0: {} 1704 | 1705 | http-assert@1.5.0: 1706 | dependencies: 1707 | deep-equal: 1.0.1 1708 | http-errors: 1.8.1 1709 | 1710 | http-errors@1.6.3: 1711 | dependencies: 1712 | depd: 1.1.2 1713 | inherits: 2.0.3 1714 | setprototypeof: 1.1.0 1715 | statuses: 1.5.0 1716 | 1717 | http-errors@1.8.1: 1718 | dependencies: 1719 | depd: 1.1.2 1720 | inherits: 2.0.4 1721 | setprototypeof: 1.2.0 1722 | statuses: 1.5.0 1723 | toidentifier: 1.0.1 1724 | 1725 | http-errors@2.0.0: 1726 | dependencies: 1727 | depd: 2.0.0 1728 | inherits: 2.0.4 1729 | setprototypeof: 1.2.0 1730 | statuses: 2.0.1 1731 | toidentifier: 1.0.1 1732 | 1733 | humanize-number@0.0.2: {} 1734 | 1735 | iconv-lite@0.4.24: 1736 | dependencies: 1737 | safer-buffer: 2.1.2 1738 | 1739 | iconv-lite@0.6.3: 1740 | dependencies: 1741 | safer-buffer: 2.1.2 1742 | 1743 | ieee754@1.2.1: {} 1744 | 1745 | ignore-by-default@1.0.1: {} 1746 | 1747 | ignore@5.3.2: {} 1748 | 1749 | import-fresh@3.3.1: 1750 | dependencies: 1751 | parent-module: 1.0.1 1752 | resolve-from: 4.0.0 1753 | 1754 | imurmurhash@0.1.4: {} 1755 | 1756 | indent-string@4.0.0: {} 1757 | 1758 | inflation@2.1.0: {} 1759 | 1760 | inflection@1.13.4: {} 1761 | 1762 | inherits@2.0.3: {} 1763 | 1764 | inherits@2.0.4: {} 1765 | 1766 | is-binary-path@2.1.0: 1767 | dependencies: 1768 | binary-extensions: 2.3.0 1769 | 1770 | is-extglob@2.1.1: {} 1771 | 1772 | is-generator-function@1.1.0: 1773 | dependencies: 1774 | call-bound: 1.0.4 1775 | get-proto: 1.0.1 1776 | has-tostringtag: 1.0.2 1777 | safe-regex-test: 1.1.0 1778 | 1779 | is-glob@4.0.3: 1780 | dependencies: 1781 | is-extglob: 2.1.1 1782 | 1783 | is-number@7.0.0: {} 1784 | 1785 | is-property@1.0.2: {} 1786 | 1787 | is-regex@1.2.1: 1788 | dependencies: 1789 | call-bound: 1.0.4 1790 | gopd: 1.2.0 1791 | has-tostringtag: 1.0.2 1792 | hasown: 2.0.2 1793 | 1794 | is-type-of@2.2.0: {} 1795 | 1796 | isexe@2.0.0: {} 1797 | 1798 | joi@17.13.3: 1799 | dependencies: 1800 | '@hapi/hoek': 9.3.0 1801 | '@hapi/topo': 5.1.0 1802 | '@sideway/address': 4.1.5 1803 | '@sideway/formula': 3.0.1 1804 | '@sideway/pinpoint': 2.0.0 1805 | 1806 | js-yaml@4.1.0: 1807 | dependencies: 1808 | argparse: 2.0.1 1809 | 1810 | json-buffer@3.0.1: {} 1811 | 1812 | json-schema-traverse@0.4.1: {} 1813 | 1814 | json-stable-stringify-without-jsonify@1.0.1: {} 1815 | 1816 | jsonfile@4.0.0: 1817 | optionalDependencies: 1818 | graceful-fs: 4.2.11 1819 | 1820 | jsonwebtoken@9.0.2: 1821 | dependencies: 1822 | jws: 3.2.2 1823 | lodash.includes: 4.3.0 1824 | lodash.isboolean: 3.0.3 1825 | lodash.isinteger: 4.0.4 1826 | lodash.isnumber: 3.0.3 1827 | lodash.isplainobject: 4.0.6 1828 | lodash.isstring: 4.0.1 1829 | lodash.once: 4.1.1 1830 | ms: 2.1.3 1831 | semver: 7.7.1 1832 | 1833 | jwa@1.4.1: 1834 | dependencies: 1835 | buffer-equal-constant-time: 1.0.1 1836 | ecdsa-sig-formatter: 1.0.11 1837 | safe-buffer: 5.2.1 1838 | 1839 | jws@3.2.2: 1840 | dependencies: 1841 | jwa: 1.4.1 1842 | safe-buffer: 5.2.1 1843 | 1844 | jwt-decode@4.0.0: {} 1845 | 1846 | keygrip@1.1.0: 1847 | dependencies: 1848 | tsscmp: 1.0.6 1849 | 1850 | keyv@4.5.4: 1851 | dependencies: 1852 | json-buffer: 3.0.1 1853 | 1854 | koa-bodyparser@4.4.1: 1855 | dependencies: 1856 | co-body: 6.2.0 1857 | copy-to: 2.0.1 1858 | type-is: 1.6.18 1859 | 1860 | koa-compose@4.1.0: {} 1861 | 1862 | koa-convert@2.0.0: 1863 | dependencies: 1864 | co: 4.6.0 1865 | koa-compose: 4.1.0 1866 | 1867 | koa-helmet@8.0.1(helmet@8.1.0): 1868 | dependencies: 1869 | helmet: 8.1.0 1870 | 1871 | koa-jwt@4.0.4: 1872 | dependencies: 1873 | jsonwebtoken: 9.0.2 1874 | koa-unless: 1.0.7 1875 | p-any: 2.1.0 1876 | 1877 | koa-logger@3.2.1: 1878 | dependencies: 1879 | bytes: 3.1.2 1880 | chalk: 2.4.2 1881 | humanize-number: 0.0.2 1882 | passthrough-counter: 1.0.0 1883 | 1884 | koa-router@13.0.1: 1885 | dependencies: 1886 | http-errors: 2.0.0 1887 | koa-compose: 4.1.0 1888 | path-to-regexp: 8.2.0 1889 | 1890 | koa-send@5.0.1: 1891 | dependencies: 1892 | debug: 4.4.0(supports-color@5.5.0) 1893 | http-errors: 1.8.1 1894 | resolve-path: 1.4.0 1895 | transitivePeerDependencies: 1896 | - supports-color 1897 | 1898 | koa-session@7.0.2: 1899 | dependencies: 1900 | crc: 3.8.0 1901 | is-type-of: 2.2.0 1902 | zod: 3.24.3 1903 | 1904 | koa-static@5.0.0: 1905 | dependencies: 1906 | debug: 3.2.7 1907 | koa-send: 5.0.1 1908 | transitivePeerDependencies: 1909 | - supports-color 1910 | 1911 | koa-unless@1.0.7: {} 1912 | 1913 | koa@2.16.0: 1914 | dependencies: 1915 | accepts: 1.3.8 1916 | cache-content-type: 1.0.1 1917 | content-disposition: 0.5.4 1918 | content-type: 1.0.5 1919 | cookies: 0.9.1 1920 | debug: 4.4.0(supports-color@5.5.0) 1921 | delegates: 1.0.0 1922 | depd: 2.0.0 1923 | destroy: 1.2.0 1924 | encodeurl: 1.0.2 1925 | escape-html: 1.0.3 1926 | fresh: 0.5.2 1927 | http-assert: 1.5.0 1928 | http-errors: 1.8.1 1929 | is-generator-function: 1.1.0 1930 | koa-compose: 4.1.0 1931 | koa-convert: 2.0.0 1932 | on-finished: 2.4.1 1933 | only: 0.0.2 1934 | parseurl: 1.3.3 1935 | statuses: 1.5.0 1936 | type-is: 1.6.18 1937 | vary: 1.1.2 1938 | transitivePeerDependencies: 1939 | - supports-color 1940 | 1941 | levn@0.4.1: 1942 | dependencies: 1943 | prelude-ls: 1.2.1 1944 | type-check: 0.4.0 1945 | 1946 | locate-path@6.0.0: 1947 | dependencies: 1948 | p-locate: 5.0.0 1949 | 1950 | lodash.includes@4.3.0: {} 1951 | 1952 | lodash.isboolean@3.0.3: {} 1953 | 1954 | lodash.isinteger@4.0.4: {} 1955 | 1956 | lodash.isnumber@3.0.3: {} 1957 | 1958 | lodash.isplainobject@4.0.6: {} 1959 | 1960 | lodash.isstring@4.0.1: {} 1961 | 1962 | lodash.merge@4.6.2: {} 1963 | 1964 | lodash.once@4.1.1: {} 1965 | 1966 | lodash@4.17.21: {} 1967 | 1968 | log4js@6.9.1: 1969 | dependencies: 1970 | date-format: 4.0.14 1971 | debug: 4.4.0(supports-color@5.5.0) 1972 | flatted: 3.3.3 1973 | rfdc: 1.4.1 1974 | streamroller: 3.1.5 1975 | transitivePeerDependencies: 1976 | - supports-color 1977 | 1978 | long@5.3.1: {} 1979 | 1980 | lru-cache@7.18.3: {} 1981 | 1982 | lru.min@1.1.2: {} 1983 | 1984 | math-intrinsics@1.1.0: {} 1985 | 1986 | media-typer@0.3.0: {} 1987 | 1988 | mime-db@1.52.0: {} 1989 | 1990 | mime-types@2.1.35: 1991 | dependencies: 1992 | mime-db: 1.52.0 1993 | 1994 | minimatch@3.1.2: 1995 | dependencies: 1996 | brace-expansion: 1.1.11 1997 | 1998 | moment-timezone@0.5.48: 1999 | dependencies: 2000 | moment: 2.30.1 2001 | 2002 | moment@2.30.1: {} 2003 | 2004 | ms@2.1.3: {} 2005 | 2006 | mysql2@3.14.0: 2007 | dependencies: 2008 | aws-ssl-profiles: 1.1.2 2009 | denque: 2.1.0 2010 | generate-function: 2.3.1 2011 | iconv-lite: 0.6.3 2012 | long: 5.3.1 2013 | lru.min: 1.1.2 2014 | named-placeholders: 1.1.3 2015 | seq-queue: 0.0.5 2016 | sqlstring: 2.3.3 2017 | 2018 | named-placeholders@1.1.3: 2019 | dependencies: 2020 | lru-cache: 7.18.3 2021 | 2022 | natural-compare@1.4.0: {} 2023 | 2024 | negotiator@0.6.3: {} 2025 | 2026 | nodemon@3.1.9: 2027 | dependencies: 2028 | chokidar: 3.6.0 2029 | debug: 4.4.0(supports-color@5.5.0) 2030 | ignore-by-default: 1.0.1 2031 | minimatch: 3.1.2 2032 | pstree.remy: 1.1.8 2033 | semver: 7.7.1 2034 | simple-update-notifier: 2.0.0 2035 | supports-color: 5.5.0 2036 | touch: 3.1.1 2037 | undefsafe: 2.0.5 2038 | 2039 | normalize-path@3.0.0: {} 2040 | 2041 | object-inspect@1.13.4: {} 2042 | 2043 | on-finished@2.4.1: 2044 | dependencies: 2045 | ee-first: 1.1.1 2046 | 2047 | only@0.0.2: {} 2048 | 2049 | opentype.js@0.7.3: 2050 | dependencies: 2051 | tiny-inflate: 1.0.3 2052 | 2053 | optionator@0.9.4: 2054 | dependencies: 2055 | deep-is: 0.1.4 2056 | fast-levenshtein: 2.0.6 2057 | levn: 0.4.1 2058 | prelude-ls: 1.2.1 2059 | type-check: 0.4.0 2060 | word-wrap: 1.2.5 2061 | 2062 | p-any@2.1.0: 2063 | dependencies: 2064 | p-cancelable: 2.1.1 2065 | p-some: 4.1.0 2066 | type-fest: 0.3.1 2067 | 2068 | p-cancelable@2.1.1: {} 2069 | 2070 | p-limit@3.1.0: 2071 | dependencies: 2072 | yocto-queue: 0.1.0 2073 | 2074 | p-locate@5.0.0: 2075 | dependencies: 2076 | p-limit: 3.1.0 2077 | 2078 | p-some@4.1.0: 2079 | dependencies: 2080 | aggregate-error: 3.1.0 2081 | p-cancelable: 2.1.1 2082 | 2083 | parent-module@1.0.1: 2084 | dependencies: 2085 | callsites: 3.1.0 2086 | 2087 | parseurl@1.3.3: {} 2088 | 2089 | passthrough-counter@1.0.0: {} 2090 | 2091 | path-exists@4.0.0: {} 2092 | 2093 | path-is-absolute@1.0.1: {} 2094 | 2095 | path-key@3.1.1: {} 2096 | 2097 | path-to-regexp@8.2.0: {} 2098 | 2099 | pg-connection-string@2.7.0: {} 2100 | 2101 | picomatch@2.3.1: {} 2102 | 2103 | prelude-ls@1.2.1: {} 2104 | 2105 | prettier-linter-helpers@1.0.0: 2106 | dependencies: 2107 | fast-diff: 1.3.0 2108 | 2109 | prettier@3.5.3: {} 2110 | 2111 | pstree.remy@1.1.8: {} 2112 | 2113 | punycode@2.3.1: {} 2114 | 2115 | qs@6.14.0: 2116 | dependencies: 2117 | side-channel: 1.1.0 2118 | 2119 | raw-body@2.5.2: 2120 | dependencies: 2121 | bytes: 3.1.2 2122 | http-errors: 2.0.0 2123 | iconv-lite: 0.4.24 2124 | unpipe: 1.0.0 2125 | 2126 | readdirp@3.6.0: 2127 | dependencies: 2128 | picomatch: 2.3.1 2129 | 2130 | resolve-from@4.0.0: {} 2131 | 2132 | resolve-path@1.4.0: 2133 | dependencies: 2134 | http-errors: 1.6.3 2135 | path-is-absolute: 1.0.1 2136 | 2137 | retry-as-promised@7.1.1: {} 2138 | 2139 | rfdc@1.4.1: {} 2140 | 2141 | safe-buffer@5.2.1: {} 2142 | 2143 | safe-regex-test@1.1.0: 2144 | dependencies: 2145 | call-bound: 1.0.4 2146 | es-errors: 1.3.0 2147 | is-regex: 1.2.1 2148 | 2149 | safer-buffer@2.1.2: {} 2150 | 2151 | semver@7.7.1: {} 2152 | 2153 | seq-queue@0.0.5: {} 2154 | 2155 | sequelize-pool@7.1.0: {} 2156 | 2157 | sequelize@6.37.7(mysql2@3.14.0): 2158 | dependencies: 2159 | '@types/debug': 4.1.12 2160 | '@types/validator': 13.12.3 2161 | debug: 4.4.0(supports-color@5.5.0) 2162 | dottie: 2.0.6 2163 | inflection: 1.13.4 2164 | lodash: 4.17.21 2165 | moment: 2.30.1 2166 | moment-timezone: 0.5.48 2167 | pg-connection-string: 2.7.0 2168 | retry-as-promised: 7.1.1 2169 | semver: 7.7.1 2170 | sequelize-pool: 7.1.0 2171 | toposort-class: 1.0.1 2172 | uuid: 8.3.2 2173 | validator: 13.15.0 2174 | wkx: 0.5.0 2175 | optionalDependencies: 2176 | mysql2: 3.14.0 2177 | transitivePeerDependencies: 2178 | - supports-color 2179 | 2180 | setprototypeof@1.1.0: {} 2181 | 2182 | setprototypeof@1.2.0: {} 2183 | 2184 | shebang-command@2.0.0: 2185 | dependencies: 2186 | shebang-regex: 3.0.0 2187 | 2188 | shebang-regex@3.0.0: {} 2189 | 2190 | side-channel-list@1.0.0: 2191 | dependencies: 2192 | es-errors: 1.3.0 2193 | object-inspect: 1.13.4 2194 | 2195 | side-channel-map@1.0.1: 2196 | dependencies: 2197 | call-bound: 1.0.4 2198 | es-errors: 1.3.0 2199 | get-intrinsic: 1.3.0 2200 | object-inspect: 1.13.4 2201 | 2202 | side-channel-weakmap@1.0.2: 2203 | dependencies: 2204 | call-bound: 1.0.4 2205 | es-errors: 1.3.0 2206 | get-intrinsic: 1.3.0 2207 | object-inspect: 1.13.4 2208 | side-channel-map: 1.0.1 2209 | 2210 | side-channel@1.1.0: 2211 | dependencies: 2212 | es-errors: 1.3.0 2213 | object-inspect: 1.13.4 2214 | side-channel-list: 1.0.0 2215 | side-channel-map: 1.0.1 2216 | side-channel-weakmap: 1.0.2 2217 | 2218 | simple-update-notifier@2.0.0: 2219 | dependencies: 2220 | semver: 7.7.1 2221 | 2222 | sqlstring@2.3.3: {} 2223 | 2224 | statuses@1.5.0: {} 2225 | 2226 | statuses@2.0.1: {} 2227 | 2228 | streamroller@3.1.5: 2229 | dependencies: 2230 | date-format: 4.0.14 2231 | debug: 4.4.0(supports-color@5.5.0) 2232 | fs-extra: 8.1.0 2233 | transitivePeerDependencies: 2234 | - supports-color 2235 | 2236 | strip-json-comments@3.1.1: {} 2237 | 2238 | supports-color@5.5.0: 2239 | dependencies: 2240 | has-flag: 3.0.0 2241 | 2242 | supports-color@7.2.0: 2243 | dependencies: 2244 | has-flag: 4.0.0 2245 | 2246 | svg-captcha@1.4.0: 2247 | dependencies: 2248 | opentype.js: 0.7.3 2249 | 2250 | synckit@0.11.1: 2251 | dependencies: 2252 | '@pkgr/core': 0.2.0 2253 | tslib: 2.8.1 2254 | 2255 | tiny-inflate@1.0.3: {} 2256 | 2257 | to-regex-range@5.0.1: 2258 | dependencies: 2259 | is-number: 7.0.0 2260 | 2261 | toidentifier@1.0.1: {} 2262 | 2263 | toposort-class@1.0.1: {} 2264 | 2265 | touch@3.1.1: {} 2266 | 2267 | tslib@2.8.1: {} 2268 | 2269 | tsscmp@1.0.6: {} 2270 | 2271 | type-check@0.4.0: 2272 | dependencies: 2273 | prelude-ls: 1.2.1 2274 | 2275 | type-fest@0.3.1: {} 2276 | 2277 | type-is@1.6.18: 2278 | dependencies: 2279 | media-typer: 0.3.0 2280 | mime-types: 2.1.35 2281 | 2282 | undefsafe@2.0.5: {} 2283 | 2284 | undici-types@6.21.0: {} 2285 | 2286 | universalify@0.1.2: {} 2287 | 2288 | unpipe@1.0.0: {} 2289 | 2290 | uri-js@4.4.1: 2291 | dependencies: 2292 | punycode: 2.3.1 2293 | 2294 | uuid@8.3.2: {} 2295 | 2296 | validator@13.15.0: {} 2297 | 2298 | vary@1.1.2: {} 2299 | 2300 | which@2.0.2: 2301 | dependencies: 2302 | isexe: 2.0.0 2303 | 2304 | wkx@0.5.0: 2305 | dependencies: 2306 | '@types/node': 22.14.0 2307 | 2308 | word-wrap@1.2.5: {} 2309 | 2310 | ylru@1.4.0: {} 2311 | 2312 | yocto-queue@0.1.0: {} 2313 | 2314 | zod@3.24.3: {} 2315 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // 配置文档: https://prettier.nodejs.cn/ 2 | 3 | /** @type {import('prettier').Config} */ 4 | export default { 5 | // 每行最大宽度,超过换行 6 | printWidth: 120, 7 | // 缩进级别的空格数 8 | tabWidth: 2, 9 | // 用制表符而不是空格缩进行 10 | useTabs: false, 11 | // 语句末尾用分号 12 | semi: false, 13 | // 使用单引号而不是双引号 14 | singleQuote: true, 15 | // 尾随逗号 16 | trailingComma: 'none', 17 | // 对象字面量中括号之间有空格 { foo: bar } 18 | bracketSpacing: true, 19 | // 在唯一的箭头函数参数周围包含括号(avoid:省略括号, always:不省略括号) 20 | arrowParens: 'avoid', 21 | // 换行符使用 lf 结尾 可选值 auto|lf|crlf|cr 22 | endOfLine: 'lf' 23 | } 24 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import Koa from 'koa' 2 | import bodyParser from 'koa-bodyparser' 3 | import session from 'koa-session' 4 | import cors from '@koa/cors' 5 | import helmet from 'koa-helmet' 6 | import logger from 'koa-logger' 7 | import errorHandler from './middlewares/error.js' 8 | import config from './config/index.js' 9 | import router from './routes/index.js' 10 | import db from './models/index.js' 11 | 12 | const app = new Koa() 13 | 14 | // 配置会话 15 | app.keys = [config.session.secret] // 用于签名会话ID cookie的密钥 16 | app.use(session(config.sessionConf, app)) 17 | 18 | // 安全头 19 | app.use(helmet()) 20 | 21 | // 跨域 22 | app.use( 23 | cors({ 24 | credentials: true // 允许发送凭证 25 | }) 26 | ) 27 | 28 | // 日志 29 | app.use(logger()) 30 | 31 | // 错误处理 32 | app.use(errorHandler) 33 | 34 | // 解析请求体 35 | app.use(bodyParser()) 36 | 37 | // 路由挂载 38 | app.use(router.routes()).use(router.allowedMethods()) 39 | 40 | // 同步所有模型 41 | db.sequelize.sync().catch(err => { 42 | console.error('数据库连接失败:', err) 43 | }) 44 | 45 | export default app 46 | -------------------------------------------------------------------------------- /src/config/db.js: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize' 2 | import config from './index.js' 3 | 4 | const { database, username, password, host, port } = config.database 5 | 6 | const sequelize = new Sequelize(database, username, password, { 7 | host, 8 | port, 9 | dialect: 'mysql', 10 | timezone: '+08:00', 11 | define: { 12 | timestamps: false, // 添加时间戳字段 13 | paranoid: true, // 软删除 14 | underscored: true // 下划线字段(snake_case) 15 | }, 16 | logging: log => { 17 | console.log(log) 18 | } 19 | }) 20 | 21 | export default sequelize 22 | -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | environment: 'prod', 3 | port: 3000, 4 | database: { 5 | database: 'admin-design', 6 | username: 'root', 7 | password: '123456789', 8 | host: 'localhost', 9 | port: 3306 10 | }, 11 | jwt: { 12 | secret: 'admin-design_jwt-secret', 13 | expiresIn: 60 * 60 * 24 14 | }, 15 | session: { 16 | secret: 'admin-design_session-secret' // 用于签名会话ID cookie的密钥 17 | }, 18 | sessionConf: { 19 | key: 'koa:sess', // cookie键名 20 | maxAge: 86400000, // cookie有效期,默认1天 21 | autoCommit: true, // 自动提交头部 22 | overwrite: true, // 是否可以覆盖 23 | httpOnly: true, // 是否仅服务器可访问 24 | signed: true, // 是否签名 25 | rolling: false, // 是否每次响应时刷新会话有效期 26 | renew: false // 是否在会话快过期时刷新会话 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/controllers/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import svgCaptcha from 'svg-captcha' 3 | import db from '../models/index.js' 4 | import config from '../config/index.js' 5 | 6 | const { User, Role, Menu } = db 7 | 8 | // 生成验证码 9 | export const captcha = async ctx => { 10 | // 创建验证码 11 | const captcha = svgCaptcha.create({ 12 | size: 4, // 验证码长度 13 | ignoreChars: '0o1il', // 排除容易混淆的字符 14 | noise: 2, // 干扰线条数量 15 | color: true, // 验证码颜色 16 | background: '#f0f0f0' // 背景色 17 | }) 18 | 19 | // 将验证码存入会话 20 | ctx.session.captcha = captcha.text.toLowerCase() 21 | 22 | // 设置响应头 23 | ctx.type = 'image/svg+xml' 24 | ctx.body = captcha.data 25 | } 26 | 27 | // 用户登录 28 | export const login = async ctx => { 29 | const { username, password, captcha } = ctx.request.body 30 | 31 | // 验证验证码 32 | if (!captcha || !ctx.session.captcha || captcha.toLowerCase() !== ctx.session.captcha) { 33 | ctx.status = 400 34 | ctx.body = { 35 | code: 400, 36 | message: '验证码错误' 37 | } 38 | return 39 | } 40 | 41 | // 清除验证码,防止重复使用 42 | ctx.session.captcha = null 43 | 44 | // 查询用户 45 | const user = await User.findOne({ 46 | where: { username }, 47 | include: [ 48 | { 49 | model: Role, 50 | through: { 51 | attributes: [] 52 | }, 53 | include: [ 54 | { 55 | model: Menu, 56 | through: { 57 | attributes: [] 58 | } 59 | } 60 | ] 61 | } 62 | ] 63 | }) 64 | 65 | // 验证用户是否存在 66 | if (!user) { 67 | ctx.status = 400 68 | ctx.body = { 69 | code: 400, 70 | message: '用户名或密码错误' 71 | } 72 | return 73 | } 74 | 75 | // 验证密码 76 | if (!user.comparePassword(password)) { 77 | ctx.status = 400 78 | ctx.body = { 79 | code: 400, 80 | message: '用户名或密码错误' 81 | } 82 | return 83 | } 84 | 85 | // 验证用户状态 86 | if (!user.enabled) { 87 | ctx.status = 403 88 | ctx.body = { 89 | code: 403, 90 | message: '用户已被禁用' 91 | } 92 | return 93 | } 94 | 95 | // 生成 token 96 | const token = jwt.sign({ id: user.user_id, username: user.username }, config.jwt.secret, { 97 | expiresIn: config.jwt.expiresIn 98 | }) 99 | 100 | // 返回用户信息和 token 101 | ctx.body = { 102 | code: 200, 103 | message: '登录成功', 104 | data: { 105 | token, 106 | user: { 107 | id: user.user_id, 108 | username: user.username, 109 | nickname: user.nick_name, 110 | avatar: user.avatar_path, 111 | email: user.email, 112 | roles: user.Roles?.map(role => ({ 113 | id: role.role_id, 114 | name: role.name, 115 | level: role.level, 116 | description: role.description 117 | })) 118 | } 119 | } 120 | } 121 | } 122 | 123 | // 获取用户信息 124 | export const info = async ctx => { 125 | const user = ctx.state.user 126 | 127 | ctx.body = { 128 | code: 200, 129 | data: { 130 | user: { 131 | id: user.user_id, 132 | username: user.username, 133 | nickname: user.nick_name, 134 | avatar: user.avatar_path, 135 | email: user.email, 136 | roles: user.Roles.map(role => ({ 137 | id: role.role_id, 138 | name: role.name, 139 | level: role.level, 140 | description: role.description 141 | })) 142 | } 143 | } 144 | } 145 | } 146 | 147 | // 用户登出 148 | export const logout = async ctx => { 149 | ctx.body = { 150 | code: 200, 151 | message: '登出成功' 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/controllers/system/dept.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { Dept, User } = db 5 | 6 | // 获取部门树 7 | export const tree = async ctx => { 8 | const { name } = ctx.query 9 | 10 | // 构建查询条件 11 | const where = {} 12 | if (name) { 13 | where.name = { 14 | [Op.like]: `%${name}%` 15 | } 16 | } 17 | 18 | const depts = await Dept.findAll({ 19 | where, 20 | order: [['dept_sort', 'ASC']] 21 | }) 22 | 23 | // 构建部门树 24 | const buildTree = (items, parentId = null) => { 25 | const result = [] 26 | 27 | for (const item of items) { 28 | if ((parentId === null && item.pid === null) || item.pid === parentId) { 29 | const children = buildTree(items, item.dept_id) 30 | 31 | const node = { 32 | id: item.dept_id, 33 | label: item.name, 34 | sort: item.dept_sort, 35 | enabled: item.enabled, 36 | createTime: item.create_time 37 | } 38 | 39 | if (children.length > 0) { 40 | node.children = children 41 | } 42 | 43 | result.push(node) 44 | } 45 | } 46 | 47 | return result 48 | } 49 | 50 | const tree = buildTree(depts) 51 | 52 | ctx.body = { 53 | code: 200, 54 | data: tree 55 | } 56 | } 57 | 58 | // 获取部门列表 59 | export const list = async ctx => { 60 | const { name, enabled } = ctx.query 61 | 62 | // 构建查询条件 63 | const where = {} 64 | if (name) { 65 | where.name = { 66 | [Op.like]: `%${name}%` 67 | } 68 | } 69 | if (enabled !== undefined) { 70 | where.enabled = enabled === 'true' 71 | } 72 | 73 | const depts = await Dept.findAll({ 74 | where, 75 | order: [['dept_sort', 'ASC']] 76 | }) 77 | 78 | ctx.body = { 79 | code: 200, 80 | data: depts 81 | } 82 | } 83 | 84 | // 获取部门详情 85 | export const detail = async ctx => { 86 | const { id } = ctx.params 87 | 88 | const dept = await Dept.findByPk(id) 89 | 90 | if (!dept) { 91 | ctx.status = 404 92 | ctx.body = { 93 | code: 404, 94 | message: '部门不存在' 95 | } 96 | return 97 | } 98 | 99 | ctx.body = { 100 | code: 200, 101 | data: dept 102 | } 103 | } 104 | 105 | // 创建部门 106 | export const create = async ctx => { 107 | const deptData = ctx.request.body 108 | 109 | // 检查部门名是否已存在 110 | const existDept = await Dept.findOne({ 111 | where: { 112 | name: deptData.name, 113 | pid: deptData.pid || null 114 | } 115 | }) 116 | 117 | if (existDept) { 118 | ctx.status = 400 119 | ctx.body = { 120 | code: 400, 121 | message: '部门名已存在' 122 | } 123 | return 124 | } 125 | 126 | // 如果是子部门,更新父部门的子部门数量 127 | if (deptData.pid) { 128 | const parentDept = await Dept.findByPk(deptData.pid) 129 | if (parentDept) { 130 | await parentDept.update({ 131 | sub_count: parentDept.sub_count + 1 132 | }) 133 | } 134 | } 135 | 136 | // 创建部门 137 | const dept = await Dept.create({ 138 | ...deptData, 139 | create_by: ctx.state.user.username 140 | }) 141 | 142 | ctx.status = 201 143 | ctx.body = { 144 | code: 201, 145 | message: '创建成功', 146 | data: dept 147 | } 148 | } 149 | 150 | // 更新部门 151 | export const update = async ctx => { 152 | const { id } = ctx.params 153 | const deptData = ctx.request.body 154 | 155 | // 查询部门 156 | const dept = await Dept.findByPk(id) 157 | 158 | if (!dept) { 159 | ctx.status = 404 160 | ctx.body = { 161 | code: 404, 162 | message: '部门不存在' 163 | } 164 | return 165 | } 166 | 167 | // 检查部门名是否已存在 168 | if (deptData.name !== dept.name || deptData.pid !== dept.pid) { 169 | const existDept = await Dept.findOne({ 170 | where: { 171 | name: deptData.name, 172 | pid: deptData.pid || null, 173 | dept_id: { 174 | [Op.ne]: id 175 | } 176 | } 177 | }) 178 | 179 | if (existDept) { 180 | ctx.status = 400 181 | ctx.body = { 182 | code: 400, 183 | message: '部门名已存在' 184 | } 185 | return 186 | } 187 | } 188 | 189 | // 如果更改了父部门,需要更新父部门的子部门数量 190 | if (deptData.pid !== dept.pid) { 191 | // 减少原父部门的子部门数量 192 | if (dept.pid) { 193 | const oldParentDept = await Dept.findByPk(dept.pid) 194 | if (oldParentDept) { 195 | await oldParentDept.update({ 196 | sub_count: Math.max(0, oldParentDept.sub_count - 1) 197 | }) 198 | } 199 | } 200 | 201 | // 增加新父部门的子部门数量 202 | if (deptData.pid) { 203 | const newParentDept = await Dept.findByPk(deptData.pid) 204 | if (newParentDept) { 205 | await newParentDept.update({ 206 | sub_count: newParentDept.sub_count + 1 207 | }) 208 | } 209 | } 210 | } 211 | 212 | // 更新部门信息 213 | await dept.update({ 214 | ...deptData, 215 | update_by: ctx.state.user.username 216 | }) 217 | 218 | ctx.body = { 219 | code: 200, 220 | message: '更新成功' 221 | } 222 | } 223 | 224 | // 删除部门 225 | export const remove = async ctx => { 226 | const { id } = ctx.params 227 | 228 | // 查询部门 229 | const dept = await Dept.findByPk(id) 230 | 231 | if (!dept) { 232 | ctx.status = 404 233 | ctx.body = { 234 | code: 404, 235 | message: '部门不存在' 236 | } 237 | return 238 | } 239 | 240 | // 检查是否有子部门 241 | if (dept.sub_count > 0) { 242 | ctx.status = 400 243 | ctx.body = { 244 | code: 400, 245 | message: '存在子部门,无法删除' 246 | } 247 | return 248 | } 249 | 250 | // 检查是否有用户关联此部门 251 | const userCount = await User.count({ 252 | where: { dept_id: id } 253 | }) 254 | 255 | if (userCount > 0) { 256 | ctx.status = 400 257 | ctx.body = { 258 | code: 400, 259 | message: '部门下存在用户,无法删除' 260 | } 261 | return 262 | } 263 | 264 | // 如果有父部门,更新父部门的子部门数量 265 | if (dept.pid) { 266 | const parentDept = await Dept.findByPk(dept.pid) 267 | if (parentDept) { 268 | await parentDept.update({ 269 | sub_count: Math.max(0, parentDept.sub_count - 1) 270 | }) 271 | } 272 | } 273 | 274 | // 删除部门 275 | await dept.destroy() 276 | 277 | ctx.body = { 278 | code: 200, 279 | message: '删除成功' 280 | } 281 | } 282 | 283 | // 获取所有部门 284 | export const all = async ctx => { 285 | const depts = await Dept.findAll({ 286 | where: { enabled: true }, 287 | order: [['dept_sort', 'ASC']] 288 | }) 289 | 290 | ctx.body = { 291 | code: 200, 292 | data: depts 293 | } 294 | } 295 | 296 | // 获取上级部门 297 | export const superior = async ctx => { 298 | const { ids } = ctx.query 299 | const deptIds = ids.split(',').map(id => parseInt(id)) 300 | 301 | // 获取所有部门 302 | const depts = await Dept.findAll({ 303 | order: [['dept_sort', 'ASC']] 304 | }) 305 | 306 | // 构建部门树 307 | const buildTree = (items, parentId = null) => { 308 | const result = [] 309 | 310 | for (const item of items) { 311 | if ((parentId === null && item.pid === null) || item.pid === parentId) { 312 | const children = buildTree(items, item.dept_id) 313 | 314 | const node = { 315 | id: item.dept_id, 316 | label: item.name, 317 | children: children.length > 0 ? children : undefined 318 | } 319 | 320 | result.push(node) 321 | } 322 | } 323 | 324 | return result 325 | } 326 | 327 | // 获取指定部门的所有上级部门 328 | const getSuperior = (depts, id) => { 329 | const dept = depts.find(d => d.dept_id === id) 330 | if (!dept || !dept.pid) return [] 331 | 332 | const parent = depts.find(d => d.dept_id === dept.pid) 333 | if (!parent) return [] 334 | 335 | return [...getSuperior(depts, parent.dept_id), parent] 336 | } 337 | 338 | // 获取所有上级部门 339 | const superiorDepts = [] 340 | for (const id of deptIds) { 341 | const superior = getSuperior(depts, id) 342 | superiorDepts.push(...superior) 343 | } 344 | 345 | // 去重 346 | const uniqueDepts = [...new Map(superiorDepts.map(item => [item.dept_id, item])).values()] 347 | 348 | // 构建树 349 | const tree = buildTree(uniqueDepts) 350 | 351 | ctx.body = { 352 | code: 200, 353 | data: tree 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/controllers/system/dict.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { Dict, DictDetail } = db 5 | 6 | // 获取字典列表 7 | export const list = async ctx => { 8 | const { page = 1, size = 10, name } = ctx.query 9 | 10 | // 构建查询条件 11 | const where = {} 12 | if (name) { 13 | where.name = { 14 | [Op.like]: `%${name}%` 15 | } 16 | } 17 | 18 | // 查询字典列表 19 | const { count, rows } = await Dict.findAndCountAll({ 20 | where, 21 | offset: (page - 1) * size, 22 | limit: parseInt(size), 23 | order: [['create_time', 'DESC']] 24 | }) 25 | 26 | ctx.body = { 27 | code: 200, 28 | data: { 29 | content: rows, 30 | totalElements: count 31 | } 32 | } 33 | } 34 | 35 | // 获取字典详情 36 | export const detail = async ctx => { 37 | const { id } = ctx.params 38 | 39 | const dict = await Dict.findByPk(id) 40 | 41 | if (!dict) { 42 | ctx.status = 404 43 | ctx.body = { 44 | code: 404, 45 | message: '字典不存在' 46 | } 47 | return 48 | } 49 | 50 | ctx.body = { 51 | code: 200, 52 | data: dict 53 | } 54 | } 55 | 56 | // 创建字典 57 | export const create = async ctx => { 58 | const dictData = ctx.request.body 59 | 60 | // 检查字典名是否已存在 61 | const existDict = await Dict.findOne({ 62 | where: { name: dictData.name } 63 | }) 64 | 65 | if (existDict) { 66 | ctx.status = 400 67 | ctx.body = { 68 | code: 400, 69 | message: '字典名已存在' 70 | } 71 | return 72 | } 73 | 74 | // 创建字典 75 | const dict = await Dict.create({ 76 | ...dictData, 77 | create_by: ctx.state.user.username 78 | }) 79 | 80 | ctx.status = 201 81 | ctx.body = { 82 | code: 201, 83 | message: '创建成功', 84 | data: dict 85 | } 86 | } 87 | 88 | // 更新字典 89 | export const update = async ctx => { 90 | const { id } = ctx.params 91 | const dictData = ctx.request.body 92 | 93 | // 查询字典 94 | const dict = await Dict.findByPk(id) 95 | 96 | if (!dict) { 97 | ctx.status = 404 98 | ctx.body = { 99 | code: 404, 100 | message: '字典不存在' 101 | } 102 | return 103 | } 104 | 105 | // 检查字典名是否已存在 106 | if (dictData.name !== dict.name) { 107 | const existDict = await Dict.findOne({ 108 | where: { 109 | name: dictData.name, 110 | dict_id: { 111 | [Op.ne]: id 112 | } 113 | } 114 | }) 115 | 116 | if (existDict) { 117 | ctx.status = 400 118 | ctx.body = { 119 | code: 400, 120 | message: '字典名已存在' 121 | } 122 | return 123 | } 124 | } 125 | 126 | // 更新字典信息 127 | await dict.update({ 128 | ...dictData, 129 | update_by: ctx.state.user.username 130 | }) 131 | 132 | ctx.body = { 133 | code: 200, 134 | message: '更新成功' 135 | } 136 | } 137 | 138 | // 删除字典 139 | export const remove = async ctx => { 140 | const { id } = ctx.params 141 | 142 | // 查询字典 143 | const dict = await Dict.findByPk(id) 144 | 145 | if (!dict) { 146 | ctx.status = 404 147 | ctx.body = { 148 | code: 404, 149 | message: '字典不存在' 150 | } 151 | return 152 | } 153 | 154 | // 检查是否有字典详情关联此字典 155 | const detailCount = await DictDetail.count({ 156 | where: { dict_id: id } 157 | }) 158 | 159 | if (detailCount > 0) { 160 | ctx.status = 400 161 | ctx.body = { 162 | code: 400, 163 | message: '字典下存在字典详情,无法删除' 164 | } 165 | return 166 | } 167 | 168 | // 删除字典 169 | await dict.destroy() 170 | 171 | ctx.body = { 172 | code: 200, 173 | message: '删除成功' 174 | } 175 | } 176 | 177 | // 获取所有字典 178 | export const all = async ctx => { 179 | const dicts = await Dict.findAll() 180 | 181 | ctx.body = { 182 | code: 200, 183 | data: dicts 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/controllers/system/dictDetail.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { Dict, DictDetail } = db 5 | 6 | // 获取字典详情列表 7 | export const list = async ctx => { 8 | const { page = 1, size = 10, dictName } = ctx.query 9 | 10 | // 构建查询条件 11 | const where = {} 12 | if (dictName) { 13 | const dict = await Dict.findOne({ 14 | where: { 15 | name: dictName 16 | } 17 | }) 18 | 19 | if (dict) { 20 | where.dict_id = dict.dict_id 21 | } else { 22 | // 如果找不到对应的字典,返回空结果 23 | ctx.body = { 24 | code: 200, 25 | data: { 26 | content: [], 27 | totalElements: 0 28 | } 29 | } 30 | return 31 | } 32 | } 33 | 34 | // 查询字典详情列表 35 | const { count, rows } = await DictDetail.findAndCountAll({ 36 | where, 37 | include: [ 38 | { 39 | model: Dict, 40 | attributes: ['name'] 41 | } 42 | ], 43 | offset: (page - 1) * size, 44 | limit: parseInt(size), 45 | order: [['dict_sort', 'ASC']] 46 | }) 47 | 48 | ctx.body = { 49 | code: 200, 50 | data: { 51 | content: rows, 52 | totalElements: count 53 | } 54 | } 55 | } 56 | 57 | // 获取字典详情 58 | export const detail = async ctx => { 59 | const { id } = ctx.params 60 | 61 | const dictDetail = await DictDetail.findByPk(id, { 62 | include: [ 63 | { 64 | model: Dict, 65 | attributes: ['name'] 66 | } 67 | ] 68 | }) 69 | 70 | if (!dictDetail) { 71 | ctx.status = 404 72 | ctx.body = { 73 | code: 404, 74 | message: '字典详情不存在' 75 | } 76 | return 77 | } 78 | 79 | ctx.body = { 80 | code: 200, 81 | data: dictDetail 82 | } 83 | } 84 | 85 | // 创建字典详情 86 | export const create = async ctx => { 87 | const dictDetailData = ctx.request.body 88 | 89 | // 检查字典是否存在 90 | const dict = await Dict.findByPk(dictDetailData.dict_id) 91 | 92 | if (!dict) { 93 | ctx.status = 400 94 | ctx.body = { 95 | code: 400, 96 | message: '字典不存在' 97 | } 98 | return 99 | } 100 | 101 | // 检查标签是否已存在 102 | const existDictDetail = await DictDetail.findOne({ 103 | where: { 104 | dict_id: dictDetailData.dict_id, 105 | label: dictDetailData.label 106 | } 107 | }) 108 | 109 | if (existDictDetail) { 110 | ctx.status = 400 111 | ctx.body = { 112 | code: 400, 113 | message: '标签已存在' 114 | } 115 | return 116 | } 117 | 118 | // 创建字典详情 119 | const dictDetail = await DictDetail.create({ 120 | ...dictDetailData, 121 | create_by: ctx.state.user.username 122 | }) 123 | 124 | ctx.status = 201 125 | ctx.body = { 126 | code: 201, 127 | message: '创建成功', 128 | data: dictDetail 129 | } 130 | } 131 | 132 | // 更新字典详情 133 | export const update = async ctx => { 134 | const { id } = ctx.params 135 | const dictDetailData = ctx.request.body 136 | 137 | // 查询字典详情 138 | const dictDetail = await DictDetail.findByPk(id) 139 | 140 | if (!dictDetail) { 141 | ctx.status = 404 142 | ctx.body = { 143 | code: 404, 144 | message: '字典详情不存在' 145 | } 146 | return 147 | } 148 | 149 | // 检查标签是否已存在 150 | if (dictDetailData.label !== dictDetail.label || dictDetailData.dict_id !== dictDetail.dict_id) { 151 | const existDictDetail = await DictDetail.findOne({ 152 | where: { 153 | dict_id: dictDetailData.dict_id, 154 | label: dictDetailData.label, 155 | detail_id: { 156 | [Op.ne]: id 157 | } 158 | } 159 | }) 160 | 161 | if (existDictDetail) { 162 | ctx.status = 400 163 | ctx.body = { 164 | code: 400, 165 | message: '标签已存在' 166 | } 167 | return 168 | } 169 | } 170 | 171 | // 更新字典详情信息 172 | await dictDetail.update({ 173 | ...dictDetailData, 174 | update_by: ctx.state.user.username 175 | }) 176 | 177 | ctx.body = { 178 | code: 200, 179 | message: '更新成功' 180 | } 181 | } 182 | 183 | // 删除字典详情 184 | export const remove = async ctx => { 185 | const { id } = ctx.params 186 | 187 | // 查询字典详情 188 | const dictDetail = await DictDetail.findByPk(id) 189 | 190 | if (!dictDetail) { 191 | ctx.status = 404 192 | ctx.body = { 193 | code: 404, 194 | message: '字典详情不存在' 195 | } 196 | return 197 | } 198 | 199 | // 删除字典详情 200 | await dictDetail.destroy() 201 | 202 | ctx.body = { 203 | code: 200, 204 | message: '删除成功' 205 | } 206 | } 207 | 208 | // 根据字典名获取字典详情 209 | export const getByDictName = async ctx => { 210 | const { dictName } = ctx.params 211 | 212 | // 查询字典 213 | const dict = await Dict.findOne({ 214 | where: { name: dictName } 215 | }) 216 | 217 | if (!dict) { 218 | ctx.status = 404 219 | ctx.body = { 220 | code: 404, 221 | message: '字典不存在' 222 | } 223 | return 224 | } 225 | 226 | // 查询字典详情 227 | const dictDetails = await DictDetail.findAll({ 228 | where: { dict_id: dict.dict_id }, 229 | order: [['dict_sort', 'ASC']] 230 | }) 231 | 232 | ctx.body = { 233 | code: 200, 234 | data: dictDetails 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/controllers/system/job.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { Job } = db 5 | 6 | // 获取岗位列表 7 | export const list = async ctx => { 8 | const { page = 1, size = 10, name, enabled } = ctx.query 9 | 10 | // 构建查询条件 11 | const where = {} 12 | if (name) { 13 | where.name = { 14 | [Op.like]: `%${name}%` 15 | } 16 | } 17 | if (enabled !== undefined) { 18 | where.enabled = enabled === 'true' 19 | } 20 | 21 | // 查询岗位列表 22 | const { count, rows } = await Job.findAndCountAll({ 23 | where, 24 | offset: (page - 1) * size, 25 | limit: parseInt(size), 26 | order: [['job_sort', 'ASC']] 27 | }) 28 | 29 | ctx.body = { 30 | code: 200, 31 | data: { 32 | content: rows, 33 | totalElements: count 34 | } 35 | } 36 | } 37 | 38 | // 获取岗位详情 39 | export const detail = async ctx => { 40 | const { id } = ctx.params 41 | 42 | const job = await Job.findByPk(id) 43 | 44 | if (!job) { 45 | ctx.status = 404 46 | ctx.body = { 47 | code: 404, 48 | message: '岗位不存在' 49 | } 50 | return 51 | } 52 | 53 | ctx.body = { 54 | code: 200, 55 | data: job 56 | } 57 | } 58 | 59 | // 创建岗位 60 | export const create = async ctx => { 61 | const jobData = ctx.request.body 62 | const { name } = jobData 63 | 64 | // 检查岗位名是否已存在 65 | const existJob = await Job.findOne({ 66 | where: { name: name } 67 | }) 68 | 69 | if (existJob) { 70 | ctx.status = 400 71 | ctx.body = { 72 | code: 400, 73 | message: '岗位名已存在' 74 | } 75 | return 76 | } 77 | 78 | // 创建岗位 79 | const job = await Job.create({ 80 | ...jobData, 81 | create_by: ctx.state.user.username 82 | }) 83 | 84 | ctx.status = 201 85 | ctx.body = { 86 | code: 201, 87 | message: '创建成功', 88 | data: job 89 | } 90 | } 91 | 92 | // 更新岗位 93 | export const update = async ctx => { 94 | const { id } = ctx.params 95 | const jobData = ctx.request.body 96 | 97 | // 查询岗位 98 | const job = await Job.findByPk(id) 99 | 100 | if (!job) { 101 | ctx.status = 404 102 | ctx.body = { 103 | code: 404, 104 | message: '岗位不存在' 105 | } 106 | return 107 | } 108 | 109 | // 检查岗位名是否已存在 110 | if (jobData.name !== job.name) { 111 | const existJob = await Job.findOne({ 112 | where: { 113 | name: jobData.name, 114 | job_id: { 115 | [Op.ne]: id 116 | } 117 | } 118 | }) 119 | 120 | if (existJob) { 121 | ctx.status = 400 122 | ctx.body = { 123 | code: 400, 124 | message: '岗位名已存在' 125 | } 126 | return 127 | } 128 | } 129 | 130 | // 更新岗位信息 131 | await job.update({ 132 | ...jobData, 133 | update_by: ctx.state.user.username 134 | }) 135 | 136 | ctx.body = { 137 | code: 200, 138 | message: '更新成功' 139 | } 140 | } 141 | 142 | // 删除岗位 143 | export const remove = async ctx => { 144 | const { id } = ctx.params 145 | 146 | // 查询岗位 147 | const job = await Job.findByPk(id) 148 | 149 | if (!job) { 150 | ctx.status = 404 151 | ctx.body = { 152 | code: 404, 153 | message: '岗位不存在' 154 | } 155 | return 156 | } 157 | 158 | // 检查是否有用户关联此岗位 159 | const userCount = await job.countUsers() 160 | 161 | if (userCount > 0) { 162 | ctx.status = 400 163 | ctx.body = { 164 | code: 400, 165 | message: '岗位已分配给用户,无法删除' 166 | } 167 | return 168 | } 169 | 170 | // 删除岗位 171 | await job.destroy() 172 | 173 | ctx.body = { 174 | code: 200, 175 | message: '删除成功' 176 | } 177 | } 178 | 179 | // 获取所有岗位 180 | export const all = async ctx => { 181 | const jobs = await Job.findAll({ 182 | where: { enabled: true }, 183 | order: [['job_sort', 'ASC']] 184 | }) 185 | 186 | ctx.body = { 187 | code: 200, 188 | data: jobs 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/controllers/system/menu.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { Menu } = db 5 | 6 | // 获取菜单树 7 | export const tree = async ctx => { 8 | const menus = await Menu.findAll({ 9 | order: [['menu_sort', 'ASC']] 10 | }) 11 | 12 | // 构建菜单树 13 | const buildTree = (items, parentId = null) => { 14 | const result = [] 15 | 16 | for (const item of items) { 17 | if ((parentId === null && item.pid === null) || item.pid === parentId) { 18 | const children = buildTree(items, item.menu_id) 19 | 20 | const node = { 21 | id: item.menu_id, 22 | title: item.title, 23 | name: item.name, 24 | component: item.component, 25 | path: item.path, 26 | icon: item.icon, 27 | type: item.type, 28 | permission: item.permission, 29 | sort: item.menu_sort, 30 | hidden: item.hidden, 31 | cache: item.cache, 32 | iFrame: item.i_frame, 33 | createTime: item.create_time 34 | } 35 | 36 | if (children.length > 0) { 37 | node.children = children 38 | } 39 | 40 | result.push(node) 41 | } 42 | } 43 | 44 | return result 45 | } 46 | 47 | const tree = buildTree(menus) 48 | 49 | ctx.body = { 50 | code: 200, 51 | data: tree 52 | } 53 | } 54 | 55 | // 获取菜单列表 56 | export const list = async ctx => { 57 | const { title } = ctx.query 58 | 59 | // 构建查询条件 60 | const where = {} 61 | if (title) { 62 | where.title = { 63 | [Op.like]: `%${title}%` 64 | } 65 | } 66 | 67 | const menus = await Menu.findAll({ 68 | where, 69 | order: [['menu_sort', 'ASC']] 70 | }) 71 | 72 | ctx.body = { 73 | code: 200, 74 | data: menus 75 | } 76 | } 77 | 78 | // 获取菜单详情 79 | export const detail = async ctx => { 80 | const { id } = ctx.params 81 | 82 | const menu = await Menu.findByPk(id) 83 | 84 | if (!menu) { 85 | ctx.status = 404 86 | ctx.body = { 87 | code: 404, 88 | message: '菜单不存在' 89 | } 90 | return 91 | } 92 | 93 | ctx.body = { 94 | code: 200, 95 | data: menu 96 | } 97 | } 98 | 99 | // 创建菜单 100 | export const create = async ctx => { 101 | const menuData = ctx.request.body 102 | 103 | // 检查菜单标题是否已存在 104 | const existMenu = await Menu.findOne({ 105 | where: { 106 | title: menuData.title 107 | } 108 | }) 109 | 110 | if (existMenu) { 111 | ctx.status = 400 112 | ctx.body = { 113 | code: 400, 114 | message: '菜单标题已存在' 115 | } 116 | return 117 | } 118 | 119 | // 如果是子菜单,更新父菜单的子菜单数量 120 | if (menuData.pid) { 121 | const parentMenu = await Menu.findByPk(menuData.pid) 122 | if (parentMenu) { 123 | await parentMenu.update({ 124 | sub_count: parentMenu.sub_count + 1 125 | }) 126 | } 127 | } 128 | 129 | // 创建菜单 130 | const menu = await Menu.create({ 131 | ...menuData, 132 | create_by: ctx.state.user.username 133 | }) 134 | 135 | ctx.status = 201 136 | ctx.body = { 137 | code: 201, 138 | message: '创建成功', 139 | data: menu 140 | } 141 | } 142 | 143 | // 更新菜单 144 | export const update = async ctx => { 145 | const { id } = ctx.params 146 | const menuData = ctx.request.body 147 | 148 | // 查询菜单 149 | const menu = await Menu.findByPk(id) 150 | 151 | if (!menu) { 152 | ctx.status = 404 153 | ctx.body = { 154 | code: 404, 155 | message: '菜单不存在' 156 | } 157 | return 158 | } 159 | 160 | // 检查菜单标题是否已存在 161 | if (menuData.title !== menu.title) { 162 | const existMenu = await Menu.findOne({ 163 | where: { 164 | title: menuData.title, 165 | menu_id: { 166 | [Op.ne]: id 167 | } 168 | } 169 | }) 170 | 171 | if (existMenu) { 172 | ctx.status = 400 173 | ctx.body = { 174 | code: 400, 175 | message: '菜单标题已存在' 176 | } 177 | return 178 | } 179 | } 180 | 181 | // 如果更改了父菜单,需要更新父菜单的子菜单数量 182 | if (menuData.pid !== menu.pid) { 183 | // 减少原父菜单的子菜单数量 184 | if (menu.pid) { 185 | const oldParentMenu = await Menu.findByPk(menu.pid) 186 | if (oldParentMenu) { 187 | await oldParentMenu.update({ 188 | sub_count: Math.max(0, oldParentMenu.sub_count - 1) 189 | }) 190 | } 191 | } 192 | 193 | // 增加新父菜单的子菜单数量 194 | if (menuData.pid) { 195 | const newParentMenu = await Menu.findByPk(menuData.pid) 196 | if (newParentMenu) { 197 | await newParentMenu.update({ 198 | sub_count: newParentMenu.sub_count + 1 199 | }) 200 | } 201 | } 202 | } 203 | 204 | // 更新菜单信息 205 | await menu.update({ 206 | ...menuData, 207 | update_by: ctx.state.user.username 208 | }) 209 | 210 | ctx.body = { 211 | code: 200, 212 | message: '更新成功' 213 | } 214 | } 215 | 216 | // 删除菜单 217 | export const remove = async ctx => { 218 | const { id } = ctx.params 219 | 220 | // 查询菜单 221 | const menu = await Menu.findByPk(id) 222 | 223 | if (!menu) { 224 | ctx.status = 404 225 | ctx.body = { 226 | code: 404, 227 | message: '菜单不存在' 228 | } 229 | return 230 | } 231 | 232 | // 检查是否有子菜单 233 | if (menu.sub_count > 0) { 234 | ctx.status = 400 235 | ctx.body = { 236 | code: 400, 237 | message: '存在子菜单,无法删除' 238 | } 239 | return 240 | } 241 | 242 | // 如果有父菜单,更新父菜单的子菜单数量 243 | if (menu.pid) { 244 | const parentMenu = await Menu.findByPk(menu.pid) 245 | if (parentMenu) { 246 | await parentMenu.update({ 247 | sub_count: Math.max(0, parentMenu.sub_count - 1) 248 | }) 249 | } 250 | } 251 | 252 | // 删除菜单 253 | await menu.destroy() 254 | 255 | ctx.body = { 256 | code: 200, 257 | message: '删除成功' 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/controllers/system/role.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { Role, Menu } = db 5 | 6 | // 获取角色列表 7 | export const list = async ctx => { 8 | const { page = 1, size = 10, name } = ctx.query 9 | 10 | // 构建查询条件 11 | const where = {} 12 | if (name) { 13 | where.name = { 14 | [Op.like]: `%${name}%` 15 | } 16 | } 17 | 18 | // 查询角色列表 19 | const { count, rows } = await Role.findAndCountAll({ 20 | where, 21 | offset: (page - 1) * size, 22 | limit: parseInt(size), 23 | order: [['level', 'ASC']] 24 | }) 25 | 26 | ctx.body = { 27 | code: 200, 28 | data: { 29 | content: rows, 30 | totalElements: count 31 | } 32 | } 33 | } 34 | 35 | // 获取角色详情 36 | export const detail = async ctx => { 37 | const { id } = ctx.params 38 | 39 | const role = await Role.findByPk(id, { 40 | include: [ 41 | { 42 | model: Menu, 43 | through: { attributes: [] } 44 | } 45 | ] 46 | }) 47 | 48 | if (!role) { 49 | ctx.status = 404 50 | ctx.body = { 51 | code: 404, 52 | message: '角色不存在' 53 | } 54 | return 55 | } 56 | 57 | ctx.body = { 58 | code: 200, 59 | data: role 60 | } 61 | } 62 | 63 | // 创建角色 64 | export const create = async ctx => { 65 | const roleData = ctx.request.body 66 | const { name, menuIds } = roleData 67 | 68 | // 检查角色名是否已存在 69 | const existRole = await Role.findOne({ 70 | where: { name } 71 | }) 72 | 73 | if (existRole) { 74 | ctx.status = 400 75 | ctx.body = { 76 | code: 400, 77 | message: '角色名已存在' 78 | } 79 | return 80 | } 81 | 82 | // 创建角色 83 | const role = await Role.create({ 84 | ...roleData, 85 | create_by: ctx.state.user.username 86 | }) 87 | 88 | // 关联菜单 89 | if (menuIds && menuIds.length > 0) { 90 | await role.setMenus(menuIds) 91 | } 92 | 93 | ctx.status = 201 94 | ctx.body = { 95 | code: 201, 96 | message: '创建成功', 97 | data: role 98 | } 99 | } 100 | 101 | // 更新角色 102 | export const update = async ctx => { 103 | const { id } = ctx.params 104 | const roleData = ctx.request.body 105 | const { name, menuIds } = roleData 106 | 107 | // 查询角色 108 | const role = await Role.findByPk(id) 109 | 110 | if (!role) { 111 | ctx.status = 404 112 | ctx.body = { 113 | code: 404, 114 | message: '角色不存在' 115 | } 116 | return 117 | } 118 | 119 | // 检查角色名是否已存在 120 | if (name !== role.name) { 121 | const existRole = await Role.findOne({ 122 | where: { 123 | name, 124 | role_id: { 125 | [Op.ne]: id 126 | } 127 | } 128 | }) 129 | 130 | if (existRole) { 131 | ctx.status = 400 132 | ctx.body = { 133 | code: 400, 134 | message: '角色名已存在' 135 | } 136 | return 137 | } 138 | } 139 | 140 | // 更新角色信息 141 | await role.update({ 142 | ...roleData, 143 | update_by: ctx.state.user.username 144 | }) 145 | 146 | // 更新菜单关联 147 | if (menuIds) { 148 | await role.setMenus(menuIds) 149 | } 150 | 151 | ctx.body = { 152 | code: 200, 153 | message: '更新成功' 154 | } 155 | } 156 | 157 | // 删除角色 158 | export const remove = async ctx => { 159 | const { id } = ctx.params 160 | 161 | // 查询角色 162 | const role = await Role.findByPk(id) 163 | 164 | if (!role) { 165 | ctx.status = 404 166 | ctx.body = { 167 | code: 404, 168 | message: '角色不存在' 169 | } 170 | return 171 | } 172 | 173 | // 检查是否有用户关联此角色 174 | const userCount = await role.countUsers() 175 | 176 | if (userCount > 0) { 177 | ctx.status = 400 178 | ctx.body = { 179 | code: 400, 180 | message: '该角色已分配给用户,无法删除' 181 | } 182 | return 183 | } 184 | 185 | // 删除角色 186 | await role.destroy() 187 | 188 | ctx.body = { 189 | code: 200, 190 | message: '删除成功' 191 | } 192 | } 193 | 194 | // 获取角色的菜单 195 | export const menus = async ctx => { 196 | const { id } = ctx.params 197 | 198 | const role = await Role.findByPk(id, { 199 | include: [ 200 | { 201 | model: Menu, 202 | through: { attributes: [] } 203 | } 204 | ] 205 | }) 206 | 207 | if (!role) { 208 | ctx.status = 404 209 | ctx.body = { 210 | code: 404, 211 | message: '角色不存在' 212 | } 213 | return 214 | } 215 | 216 | ctx.body = { 217 | code: 200, 218 | data: role.menus.map(menu => menu.menu_id) 219 | } 220 | } 221 | 222 | // 获取所有角色 223 | export const all = async ctx => { 224 | const roles = await Role.findAll({ 225 | order: [['level', 'ASC']] 226 | }) 227 | 228 | ctx.body = { 229 | code: 200, 230 | data: roles 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/controllers/system/user.js: -------------------------------------------------------------------------------- 1 | import { Op } from 'sequelize' 2 | import db from '../../models/index.js' 3 | 4 | const { User, Role, Dept, Job } = db 5 | 6 | // 获取用户列表 7 | export const list = async ctx => { 8 | const { page = 1, size = 10, username, deptId, enabled } = ctx.query 9 | 10 | // 构建查询条件 11 | const where = {} 12 | if (username) { 13 | where.username = { 14 | [Op.like]: `%${username}%` 15 | } 16 | } 17 | if (deptId) { 18 | where.dept_id = deptId 19 | } 20 | if (enabled !== undefined) { 21 | where.enabled = enabled === 'true' 22 | } 23 | 24 | // 查询用户列表 25 | const { count, rows } = await User.findAndCountAll({ 26 | where, 27 | include: [ 28 | { 29 | model: Dept, 30 | attributes: ['dept_id', 'name'] 31 | }, 32 | { 33 | model: Role, 34 | through: { attributes: [] }, 35 | attributes: ['role_id', 'name'] 36 | }, 37 | { 38 | model: Job, 39 | through: { attributes: [] }, 40 | attributes: ['job_id', 'name'] 41 | } 42 | ], 43 | attributes: { exclude: ['password'] }, 44 | offset: (page - 1) * size, 45 | limit: parseInt(size), 46 | order: [['create_time', 'DESC']] 47 | }) 48 | 49 | ctx.body = { 50 | code: 200, 51 | data: { 52 | content: rows, 53 | totalElements: count 54 | } 55 | } 56 | } 57 | 58 | // 获取用户详情 59 | export const detail = async ctx => { 60 | const { id } = ctx.params 61 | 62 | const user = await User.findByPk(id, { 63 | include: [ 64 | { 65 | model: Dept, 66 | attributes: ['dept_id', 'name'] 67 | }, 68 | { 69 | model: Role, 70 | through: { attributes: [] }, 71 | attributes: ['role_id', 'name'] 72 | }, 73 | { 74 | model: Job, 75 | through: { attributes: [] }, 76 | attributes: ['job_id', 'name'] 77 | } 78 | ], 79 | attributes: { exclude: ['password'] } 80 | }) 81 | 82 | if (!user) { 83 | ctx.status = 404 84 | ctx.body = { 85 | code: 404, 86 | message: '用户不存在' 87 | } 88 | return 89 | } 90 | 91 | ctx.body = { 92 | code: 200, 93 | data: user 94 | } 95 | } 96 | 97 | // 创建用户 98 | export const create = async ctx => { 99 | const userData = ctx.request.body 100 | const { username, email, phone, roleIds, jobIds } = userData 101 | 102 | // 检查用户名是否已存在 103 | const existUser = await User.findOne({ 104 | where: { 105 | [Op.or]: [{ username }, { email }, { phone }] 106 | } 107 | }) 108 | 109 | if (existUser) { 110 | ctx.status = 400 111 | ctx.body = { 112 | code: 400, 113 | message: '用户名或邮箱或手机号已存在' 114 | } 115 | return 116 | } 117 | 118 | // 创建用户 119 | const user = await User.create({ 120 | ...userData, 121 | create_by: ctx.state.user.username 122 | }) 123 | 124 | // 关联角色 125 | if (roleIds && roleIds.length > 0) { 126 | await user.setRoles(roleIds) 127 | } 128 | 129 | // 关联岗位 130 | if (jobIds && jobIds.length > 0) { 131 | await user.setJobs(jobIds) 132 | } 133 | 134 | ctx.status = 201 135 | ctx.body = { 136 | code: 201, 137 | message: '创建成功', 138 | data: user 139 | } 140 | } 141 | 142 | // 更新用户 143 | export const update = async ctx => { 144 | const { id } = ctx.params 145 | const userData = ctx.request.body 146 | const { username, email, phone, roleIds, jobIds } = userData 147 | 148 | // 查询用户 149 | const user = await User.findByPk(id) 150 | 151 | if (!user) { 152 | ctx.status = 404 153 | ctx.body = { 154 | code: 404, 155 | message: '用户不存在' 156 | } 157 | return 158 | } 159 | 160 | // 检查用户名或邮箱或手机号是否已存在 161 | if (username !== user.username || email !== user.email || phone !== user.phone) { 162 | const existUser = await User.findOne({ 163 | where: { 164 | [Op.or]: [{ username }, { email }, { phone }], 165 | user_id: { 166 | [Op.ne]: id 167 | } 168 | } 169 | }) 170 | 171 | if (existUser) { 172 | ctx.status = 400 173 | ctx.body = { 174 | code: 400, 175 | message: '用户名或邮箱或手机号已存在' 176 | } 177 | return 178 | } 179 | } 180 | 181 | // 更新用户信息 182 | await user.update({ 183 | ...userData, 184 | update_by: ctx.state.user.username 185 | }) 186 | 187 | // 更新角色关联 188 | if (roleIds) { 189 | await user.setRoles(roleIds) 190 | } 191 | 192 | // 更新岗位关联 193 | if (jobIds) { 194 | await user.setJobs(jobIds) 195 | } 196 | 197 | ctx.body = { 198 | code: 200, 199 | message: '更新成功' 200 | } 201 | } 202 | 203 | // 删除用户 204 | export const remove = async ctx => { 205 | const { id } = ctx.params 206 | 207 | // 查询用户 208 | const user = await User.findByPk(id) 209 | 210 | if (!user) { 211 | ctx.status = 404 212 | ctx.body = { 213 | code: 404, 214 | message: '用户不存在' 215 | } 216 | return 217 | } 218 | 219 | // 不能删除超级管理员 220 | if (user.is_admin) { 221 | ctx.status = 400 222 | ctx.body = { 223 | code: 400, 224 | message: '不能删除超级管理员' 225 | } 226 | return 227 | } 228 | 229 | // 删除用户 230 | await user.destroy() 231 | 232 | ctx.body = { 233 | code: 200, 234 | message: '删除成功' 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import config from '../config/index.js' 3 | import db from '../models/index.js' 4 | 5 | const { User, Role, Menu } = db 6 | 7 | export default async (ctx, next) => { 8 | try { 9 | // 获取 token 10 | const token = ctx.header.authorization ? ctx.header.authorization.split(' ')[1] : null 11 | 12 | if (!token) { 13 | ctx.status = 401 14 | ctx.body = { 15 | code: 401, 16 | message: '未登录或 token 已过期' 17 | } 18 | return 19 | } 20 | 21 | // 验证 token 22 | const decoded = jwt.verify(token, config.jwt.secret) 23 | 24 | // 查询用户信息 25 | const user = await User.findByPk(decoded.id, { 26 | include: [ 27 | { 28 | model: Role, 29 | through: { attributes: [] }, 30 | include: [ 31 | { 32 | model: Menu, 33 | through: { attributes: [] } 34 | } 35 | ] 36 | } 37 | ] 38 | }) 39 | 40 | if (!user) { 41 | ctx.status = 401 42 | ctx.body = { 43 | code: 401, 44 | message: '用户不存在' 45 | } 46 | return 47 | } 48 | 49 | if (!user.enabled) { 50 | ctx.status = 403 51 | ctx.body = { 52 | code: 403, 53 | message: '用户已被禁用' 54 | } 55 | return 56 | } 57 | 58 | // 将用户信息添加到上下文中 59 | ctx.state.user = user 60 | 61 | await next() 62 | } catch (err) { 63 | if (err.name === 'TokenExpiredError') { 64 | ctx.status = 401 65 | ctx.body = { 66 | code: 401, 67 | message: 'token 已过期' 68 | } 69 | } else if (err.name === 'JsonWebTokenError') { 70 | ctx.status = 401 71 | ctx.body = { 72 | code: 401, 73 | message: 'token 无效' 74 | } 75 | } else { 76 | throw err 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/middlewares/error.js: -------------------------------------------------------------------------------- 1 | export default async (ctx, next) => { 2 | try { 3 | await next() 4 | } catch (err) { 5 | console.error('服务器错误', err) 6 | 7 | // 设置状态码 8 | ctx.status = err.status || 500 9 | 10 | // 设置响应体 11 | ctx.body = { 12 | code: ctx.status, 13 | message: err.message || '服务器内部错误' 14 | } 15 | 16 | // 记录错误日志 17 | ctx.app.emit('error', err, ctx) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { DataTypes } from 'sequelize' 4 | import sequelize from '../config/db.js' 5 | 6 | const db = {} 7 | const __dirname = import.meta.dirname 8 | 9 | // 递归读取目录下的所有模型文件 10 | const readModelFiles = dir => { 11 | let files = [] 12 | const items = fs.readdirSync(dir) 13 | 14 | for (const item of items) { 15 | const fullPath = path.join(dir, item) 16 | const stat = fs.statSync(fullPath) 17 | 18 | if (stat.isDirectory()) { 19 | // 如果是目录,递归读取 20 | files = files.concat(readModelFiles(fullPath)) 21 | } else if (item.indexOf('.') !== 0 && item !== 'index.js' && item.slice(-3) === '.js') { 22 | // 如果是 JS 文件且不是 index.js 23 | files.push(fullPath) 24 | } 25 | } 26 | 27 | return files 28 | } 29 | 30 | // 导入模型文件并注册到db对象中 31 | const loadModels = async () => { 32 | const modelFiles = readModelFiles(__dirname) 33 | 34 | for (const file of modelFiles) { 35 | const module = await import(file) 36 | const model = module.default(sequelize, DataTypes) 37 | db[model.name] = model 38 | } 39 | 40 | // 建立模型之间的关联关系 41 | Object.keys(db).forEach(modelName => { 42 | if (db[modelName].associate) { 43 | db[modelName].associate(db) 44 | } 45 | }) 46 | } 47 | 48 | await loadModels() 49 | 50 | db.sequelize = sequelize 51 | 52 | export default db 53 | -------------------------------------------------------------------------------- /src/models/system/dept.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | // 定义部门模型 3 | const Dept = sequelize.define( 4 | 'Dept', 5 | { 6 | dept_id: { 7 | type: DataTypes.BIGINT, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | comment: '部门ID' 11 | }, 12 | pid: { 13 | type: DataTypes.BIGINT, 14 | comment: '上级部门' 15 | }, 16 | sub_count: { 17 | type: DataTypes.INTEGER, 18 | defaultValue: 0, 19 | comment: '子部门数目' 20 | }, 21 | name: { 22 | type: DataTypes.STRING(100), 23 | allowNull: false, 24 | comment: '部门名称' 25 | }, 26 | dept_sort: { 27 | type: DataTypes.INTEGER, 28 | defaultValue: 999, 29 | comment: '部门排序' 30 | }, 31 | enabled: { 32 | type: DataTypes.BOOLEAN, 33 | defaultValue: true, 34 | comment: '状态' 35 | }, 36 | create_by: { 37 | type: DataTypes.STRING(100), 38 | comment: '创建者' 39 | }, 40 | update_by: { 41 | type: DataTypes.STRING(100), 42 | comment: '更新者' 43 | }, 44 | create_time: { 45 | type: DataTypes.DATE, 46 | comment: '创建时间' 47 | }, 48 | update_time: { 49 | type: DataTypes.DATE, 50 | comment: '更新时间' 51 | } 52 | }, 53 | { 54 | tableName: 'sys_dept', 55 | comment: '系统部门' 56 | } 57 | ) 58 | 59 | Dept.associate = function (models) { 60 | // 部门与用户一对多关系 61 | Dept.hasMany(models.User, { 62 | foreignKey: 'dept_id' 63 | }) 64 | } 65 | 66 | return Dept 67 | } 68 | -------------------------------------------------------------------------------- /src/models/system/dict.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | // 定义字典模型 3 | const Dict = sequelize.define( 4 | 'Dict', 5 | { 6 | dict_id: { 7 | type: DataTypes.BIGINT, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | comment: '字典ID' 11 | }, 12 | name: { 13 | type: DataTypes.STRING, 14 | allowNull: false, 15 | unique: true, 16 | comment: '字典名称' 17 | }, 18 | description: { 19 | type: DataTypes.STRING, 20 | allowNull: false, 21 | comment: '字典描述' 22 | }, 23 | create_by: { 24 | type: DataTypes.STRING(100), 25 | comment: '创建者' 26 | }, 27 | update_by: { 28 | type: DataTypes.STRING(100), 29 | comment: '更新者' 30 | }, 31 | create_time: { 32 | type: DataTypes.DATE, 33 | comment: '创建时间' 34 | }, 35 | update_time: { 36 | type: DataTypes.DATE, 37 | comment: '更新时间' 38 | } 39 | }, 40 | { 41 | tableName: 'sys_dict', 42 | comment: '系统字典' 43 | } 44 | ) 45 | 46 | Dict.associate = function (models) { 47 | // // 字典与字典详情一对多关系 48 | Dict.hasMany(models.DictDetail, { 49 | foreignKey: 'dict_id' 50 | }) 51 | } 52 | 53 | return Dict 54 | } 55 | -------------------------------------------------------------------------------- /src/models/system/dictDetail.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | // 定义字典详情模型 3 | const DictDetail = sequelize.define( 4 | 'DictDetail', 5 | { 6 | detail_id: { 7 | type: DataTypes.BIGINT, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | comment: '字典详情ID' 11 | }, 12 | dict_id: { 13 | type: DataTypes.BIGINT, 14 | allowNull: false, 15 | comment: '字典ID' 16 | }, 17 | label: { 18 | type: DataTypes.STRING(100), 19 | allowNull: false, 20 | comment: '字典标签' 21 | }, 22 | value: { 23 | type: DataTypes.STRING(100), 24 | allowNull: false, 25 | comment: '字典值' 26 | }, 27 | dict_sort: { 28 | type: DataTypes.INTEGER, 29 | defaultValue: 999, 30 | comment: '排序' 31 | }, 32 | create_by: { 33 | type: DataTypes.STRING(100), 34 | comment: '创建者' 35 | }, 36 | update_by: { 37 | type: DataTypes.STRING(100), 38 | comment: '更新者' 39 | }, 40 | create_time: { 41 | type: DataTypes.DATE, 42 | comment: '创建时间' 43 | }, 44 | update_time: { 45 | type: DataTypes.DATE, 46 | comment: '更新时间' 47 | } 48 | }, 49 | { 50 | tableName: 'sys_dict_detail', 51 | comment: '系统字典详情' 52 | } 53 | ) 54 | 55 | DictDetail.associate = function (models) { 56 | // 字典详情与字典多对一关系 57 | DictDetail.belongsTo(models.Dict, { 58 | foreignKey: 'dict_id' 59 | }) 60 | } 61 | 62 | return DictDetail 63 | } 64 | -------------------------------------------------------------------------------- /src/models/system/job.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | // 定义岗位模型 3 | const Job = sequelize.define( 4 | 'Job', 5 | { 6 | job_id: { 7 | type: DataTypes.BIGINT, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | comment: '岗位ID' 11 | }, 12 | name: { 13 | type: DataTypes.STRING(100), 14 | allowNull: false, 15 | comment: '岗位名称' 16 | }, 17 | enabled: { 18 | type: DataTypes.BOOLEAN, 19 | defaultValue: true, 20 | comment: '岗位状态' 21 | }, 22 | job_sort: { 23 | type: DataTypes.INTEGER, 24 | defaultValue: 999, 25 | comment: '岗位排序' 26 | }, 27 | create_by: { 28 | type: DataTypes.STRING(100), 29 | comment: '创建者' 30 | }, 31 | update_by: { 32 | type: DataTypes.STRING(100), 33 | comment: '更新者' 34 | }, 35 | create_time: { 36 | type: DataTypes.DATE, 37 | comment: '创建时间' 38 | }, 39 | update_time: { 40 | type: DataTypes.DATE, 41 | comment: '更新时间' 42 | } 43 | }, 44 | { 45 | tableName: 'sys_job', 46 | comment: '系统岗位' 47 | } 48 | ) 49 | 50 | Job.associate = function (models) { 51 | // 岗位与用户多对多关系 52 | Job.belongsToMany(models.User, { 53 | through: 'sys_users_jobs', 54 | foreignKey: 'job_id', 55 | otherKey: 'user_id' 56 | }) 57 | } 58 | 59 | return Job 60 | } 61 | -------------------------------------------------------------------------------- /src/models/system/menu.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | // 定义菜单模型 3 | const Menu = sequelize.define( 4 | 'Menu', 5 | { 6 | menu_id: { 7 | type: DataTypes.BIGINT, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | comment: '菜单ID' 11 | }, 12 | pid: { 13 | type: DataTypes.BIGINT, 14 | comment: '上级菜单ID' 15 | }, 16 | sub_count: { 17 | type: DataTypes.INTEGER, 18 | defaultValue: 0, 19 | comment: '子菜单数目' 20 | }, 21 | type: { 22 | type: DataTypes.INTEGER, 23 | defaultValue: 0, 24 | comment: '菜单类型' 25 | }, 26 | title: { 27 | type: DataTypes.STRING(100), 28 | allowNull: false, 29 | comment: '菜单标题' 30 | }, 31 | name: { 32 | type: DataTypes.STRING(100), 33 | comment: '组件名称' 34 | }, 35 | component: { 36 | type: DataTypes.STRING, 37 | comment: '组件路径' 38 | }, 39 | menu_sort: { 40 | type: DataTypes.INTEGER, 41 | defaultValue: 999, 42 | comment: '菜单排序' 43 | }, 44 | icon: { 45 | type: DataTypes.STRING(100), 46 | comment: '菜单图标' 47 | }, 48 | path: { 49 | type: DataTypes.STRING, 50 | comment: '链接地址' 51 | }, 52 | iframe: { 53 | type: DataTypes.BOOLEAN, 54 | defaultValue: false, 55 | comment: '是否外链' 56 | }, 57 | cache: { 58 | type: DataTypes.BOOLEAN, 59 | defaultValue: false, 60 | comment: '是否缓存' 61 | }, 62 | hidden: { 63 | type: DataTypes.BOOLEAN, 64 | defaultValue: false, 65 | comment: '是否隐藏' 66 | }, 67 | permission: { 68 | type: DataTypes.STRING, 69 | comment: '权限标识' 70 | }, 71 | create_by: { 72 | type: DataTypes.STRING(100), 73 | comment: '创建者' 74 | }, 75 | update_by: { 76 | type: DataTypes.STRING(100), 77 | comment: '更新者' 78 | }, 79 | create_time: { 80 | type: DataTypes.DATE, 81 | comment: '创建时间' 82 | }, 83 | update_time: { 84 | type: DataTypes.DATE, 85 | comment: '更新时间' 86 | } 87 | }, 88 | { 89 | tableName: 'sys_menu', 90 | comment: '系统菜单' 91 | } 92 | ) 93 | 94 | Menu.associate = function (models) { 95 | // 菜单与角色多对多关系 96 | Menu.belongsToMany(models.Role, { 97 | through: 'sys_roles_menus', 98 | foreignKey: 'menu_id', 99 | otherKey: 'role_id' 100 | }) 101 | } 102 | 103 | return Menu 104 | } 105 | -------------------------------------------------------------------------------- /src/models/system/role.js: -------------------------------------------------------------------------------- 1 | export default (sequelize, DataTypes) => { 2 | // 定义角色模型 3 | const Role = sequelize.define( 4 | 'Role', 5 | { 6 | role_id: { 7 | type: DataTypes.BIGINT, 8 | primaryKey: true, 9 | autoIncrement: true, 10 | comment: '角色ID' 11 | }, 12 | name: { 13 | type: DataTypes.STRING(100), 14 | allowNull: false, 15 | unique: true, 16 | comment: '角色名称' 17 | }, 18 | level: { 19 | type: DataTypes.INTEGER, 20 | allowNull: false, 21 | comment: '角色级别' 22 | }, 23 | description: { 24 | type: DataTypes.STRING, 25 | comment: '角色描述' 26 | }, 27 | data_scope: { 28 | type: DataTypes.STRING, 29 | allowNull: false, 30 | comment: '数据权限' 31 | }, 32 | create_by: { 33 | type: DataTypes.STRING(100), 34 | comment: '创建者' 35 | }, 36 | update_by: { 37 | type: DataTypes.STRING(100), 38 | comment: '更新者' 39 | }, 40 | create_time: { 41 | type: DataTypes.DATE, 42 | comment: '创建时间' 43 | }, 44 | update_time: { 45 | type: DataTypes.DATE, 46 | comment: '更新时间' 47 | } 48 | }, 49 | { 50 | tableName: 'sys_role', 51 | comment: '系统角色' 52 | } 53 | ) 54 | 55 | Role.associate = function (models) { 56 | // 角色与用户多对多关系 57 | Role.belongsToMany(models.User, { 58 | through: 'sys_users_roles', 59 | foreignKey: 'role_id', 60 | otherKey: 'user_id' 61 | }) 62 | 63 | // 角色与菜单多对多关系 64 | Role.belongsToMany(models.Menu, { 65 | through: 'sys_roles_menus', 66 | foreignKey: 'role_id', 67 | otherKey: 'menu_id' 68 | }) 69 | } 70 | 71 | return Role 72 | } 73 | -------------------------------------------------------------------------------- /src/models/system/user.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | 3 | export default (sequelize, DataTypes) => { 4 | // 定义用户模型 5 | const User = sequelize.define( 6 | 'User', 7 | { 8 | user_id: { 9 | type: DataTypes.BIGINT, 10 | primaryKey: true, 11 | autoIncrement: true, 12 | comment: 'ID' 13 | }, 14 | dept_id: { 15 | type: DataTypes.BIGINT, 16 | comment: '部门名称' 17 | }, 18 | username: { 19 | type: DataTypes.STRING(100), 20 | unique: true, 21 | comment: '用户名' 22 | }, 23 | nick_name: { 24 | type: DataTypes.STRING(100), 25 | comment: '昵称' 26 | }, 27 | gender: { 28 | type: DataTypes.STRING(2), 29 | comment: '性别' 30 | }, 31 | phone: { 32 | type: DataTypes.STRING(180), 33 | comment: '手机号码' 34 | }, 35 | email: { 36 | type: DataTypes.STRING(180), 37 | unique: true, 38 | comment: '邮箱' 39 | }, 40 | avatar_name: { 41 | type: DataTypes.STRING, 42 | comment: '头像地址' 43 | }, 44 | avatar_path: { 45 | type: DataTypes.STRING, 46 | comment: '头像真实路径' 47 | }, 48 | password: { 49 | type: DataTypes.STRING, 50 | comment: '密码', 51 | set(value) { 52 | const salt = bcrypt.genSaltSync(10) 53 | const pwd = bcrypt.hashSync(value, salt) 54 | this.setDataValue('password', pwd) 55 | } 56 | }, 57 | is_admin: { 58 | type: DataTypes.BOOLEAN, 59 | defaultValue: 0, 60 | comment: '是否为admin账号' 61 | }, 62 | enabled: { 63 | type: DataTypes.BOOLEAN, 64 | defaultValue: 1, 65 | comment: '状态: 1启用、0禁用' 66 | }, 67 | create_by: { 68 | type: DataTypes.STRING(100), 69 | comment: '创建者' 70 | }, 71 | update_by: { 72 | type: DataTypes.STRING(100), 73 | comment: '更新者' 74 | }, 75 | pwd_reset_time: { 76 | type: DataTypes.DATE, 77 | comment: '修改密码的时间' 78 | }, 79 | create_time: { 80 | type: DataTypes.DATE, 81 | comment: '创建时间' 82 | }, 83 | update_time: { 84 | type: DataTypes.DATE, 85 | comment: '更新时间' 86 | } 87 | }, 88 | { 89 | tableName: 'sys_user', 90 | comment: '系统用户' 91 | } 92 | ) 93 | 94 | User.associate = function (models) { 95 | // 用户与角色多对多关系 96 | User.belongsToMany(models.Role, { 97 | through: 'sys_users_roles', 98 | foreignKey: 'user_id', 99 | otherKey: 'roles' 100 | }) 101 | 102 | // 用户与岗位多对多关系 103 | User.belongsToMany(models.Job, { 104 | through: 'sys_users_jobs', 105 | foreignKey: 'user_id', 106 | otherKey: 'job_id' 107 | }) 108 | 109 | // 用户与部门一对多关系 110 | User.belongsTo(models.Dept, { 111 | foreignKey: 'dept_id' 112 | }) 113 | } 114 | 115 | // 验证密码 116 | User.prototype.comparePassword = function (password) { 117 | return bcrypt.compareSync(password, this.password) 118 | } 119 | 120 | return User 121 | } 122 | -------------------------------------------------------------------------------- /src/routes/auth.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../middlewares/auth.js' 3 | import * as authController from '../controllers/auth.js' 4 | 5 | const router = new Router({ 6 | prefix: '/api/auth' 7 | }) 8 | 9 | router.get('/captcha', authController.captcha) 10 | router.post('/login', authController.login) 11 | router.get('/info', authMiddleware, authController.info) 12 | router.delete('/logout', authMiddleware, authController.logout) 13 | 14 | export default router 15 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import Router from 'koa-router' 4 | 5 | const router = new Router() 6 | const __dirname = import.meta.dirname 7 | 8 | // 动态导入路由文件 9 | const loadRoutes = async dir => { 10 | const files = fs.readdirSync(dir) 11 | 12 | for (const file of files) { 13 | const filePath = path.join(dir, file) 14 | const stat = fs.statSync(filePath) 15 | 16 | if (stat.isDirectory()) { 17 | // 如果是目录,递归加载 18 | await loadRoutes(filePath) 19 | } else if (file !== 'index.js' && file.endsWith('.js')) { 20 | // 如果是 JS 文件且不是 index.js,动态导入 21 | const routerModule = await import(filePath) 22 | const routerInstance = routerModule.default 23 | 24 | if (routerInstance && routerInstance.routes) { 25 | // 注册子路由 26 | router.use(routerInstance.routes(), routerInstance.allowedMethods()) 27 | console.log(`路由已加载: ${filePath}`) 28 | } 29 | } 30 | } 31 | } 32 | 33 | // 加载所有路由 34 | ;(async () => { 35 | try { 36 | await loadRoutes(__dirname) 37 | console.log('所有路由加载完成') 38 | } catch (error) { 39 | console.error('路由加载失败:', error) 40 | } 41 | })() 42 | 43 | export default router 44 | -------------------------------------------------------------------------------- /src/routes/system/dept.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as deptController from '../../controllers/system/dept.js' 4 | 5 | const router = new Router({ 6 | prefix: '/api/system/depts' 7 | }) 8 | 9 | router.get('/tree', authMiddleware, deptController.tree) 10 | router.get('/', authMiddleware, deptController.list) 11 | router.get('/all', authMiddleware, deptController.all) 12 | router.get('/superior', authMiddleware, deptController.superior) 13 | router.get('/:id', authMiddleware, deptController.detail) 14 | router.post('/', authMiddleware, deptController.create) 15 | router.put('/:id', authMiddleware, deptController.update) 16 | router.delete('/:id', authMiddleware, deptController.remove) 17 | 18 | export default router 19 | -------------------------------------------------------------------------------- /src/routes/system/dict.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as dictController from '../../controllers/system/dict.js' 4 | 5 | const router = new Router({ prefix: '/system/dict' }) 6 | 7 | // 字典管理路由 8 | router.get('/', authMiddleware, dictController.list) 9 | router.get('/all', authMiddleware, dictController.all) 10 | router.get('/:id', authMiddleware, dictController.detail) 11 | router.post('/', authMiddleware, dictController.create) 12 | router.put('/:id', authMiddleware, dictController.update) 13 | router.delete('/:id', authMiddleware, dictController.remove) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /src/routes/system/dictDetail.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as dictDetailController from '../../controllers/system/dictDetail.js' 4 | 5 | const router = new Router({ prefix: '/system/dictDetail' }) 6 | 7 | // 字典详情管理路由 8 | router.get('/', authMiddleware, dictDetailController.list) 9 | router.get('/:id', authMiddleware, dictDetailController.detail) 10 | router.get('/dict/:dictName', authMiddleware, dictDetailController.getByDictName) 11 | router.post('/', authMiddleware, dictDetailController.create) 12 | router.put('/:id', authMiddleware, dictDetailController.update) 13 | router.delete('/:id', authMiddleware, dictDetailController.remove) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /src/routes/system/job.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as jobController from '../../controllers/system/job.js' 4 | 5 | const router = new Router({ prefix: '/api/system/jobs' }) 6 | 7 | router.get('/', authMiddleware, jobController.list) 8 | router.get('/all', authMiddleware, jobController.all) 9 | router.get('/:id', authMiddleware, jobController.detail) 10 | router.post('/', authMiddleware, jobController.create) 11 | router.put('/:id', authMiddleware, jobController.update) 12 | router.delete('/:id', authMiddleware, jobController.remove) 13 | 14 | export default router 15 | -------------------------------------------------------------------------------- /src/routes/system/menu.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as menuController from '../../controllers/system/menu.js' 4 | 5 | const router = new Router({ 6 | prefix: '/api/system/menus' 7 | }) 8 | 9 | router.get('/tree', authMiddleware, menuController.tree) 10 | router.get('/', authMiddleware, menuController.list) 11 | router.get('/:id', authMiddleware, menuController.detail) 12 | router.post('/', authMiddleware, menuController.create) 13 | router.put('/:id', authMiddleware, menuController.update) 14 | router.delete('/:id', authMiddleware, menuController.remove) 15 | 16 | export default router 17 | -------------------------------------------------------------------------------- /src/routes/system/role.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as roleController from '../../controllers/system/role.js' 4 | 5 | const router = new Router({ 6 | prefix: '/api/system/roles' 7 | }) 8 | 9 | router.get('/', authMiddleware, roleController.list) 10 | router.get('/all', authMiddleware, roleController.all) 11 | router.get('/:id', authMiddleware, roleController.detail) 12 | router.get('/:id/menus', authMiddleware, roleController.menus) 13 | router.post('/', authMiddleware, roleController.create) 14 | router.put('/:id', authMiddleware, roleController.update) 15 | router.delete('/:id', authMiddleware, roleController.remove) 16 | 17 | export default router 18 | -------------------------------------------------------------------------------- /src/routes/system/user.js: -------------------------------------------------------------------------------- 1 | import Router from 'koa-router' 2 | import authMiddleware from '../../middlewares/auth.js' 3 | import * as userController from '../../controllers/system/user.js' 4 | 5 | const router = new Router({ 6 | prefix: '/api/system/users' 7 | }) 8 | 9 | router.get('/', authMiddleware, userController.list) 10 | router.get('/:id', authMiddleware, userController.detail) 11 | router.post('/', authMiddleware, userController.create) 12 | router.put('/:id', authMiddleware, userController.update) 13 | router.delete('/:id', authMiddleware, userController.remove) 14 | 15 | export default router 16 | --------------------------------------------------------------------------------