From 67c9330659947ae4585522c4ab6c42c30ccf52cf Mon Sep 17 00:00:00 2001 From: Kyaru Date: Thu, 25 Jun 2026 21:18:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9E=AA=E7=9C=BC=E6=B3=95=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db.js | 18 ++ middleware/permissions.js | 4 +- routes/afterSales.js | 43 ++- routes/contracts.js | 117 +++++-- routes/customers.js | 27 +- routes/employees.js | 56 +++- routes/products.js | 9 +- routes/suppliers.js | 9 +- routes/users.js | 42 ++- server.js | 1 + yarn.lock | 642 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 919 insertions(+), 49 deletions(-) create mode 100644 yarn.lock diff --git a/db.js b/db.js index 76fcac0..199e470 100644 --- a/db.js +++ b/db.js @@ -152,6 +152,24 @@ async function initDB() { KEY idx_after_sales_status (handle_status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='售后信息表' `) + + // ============ 2.2.1 添加缺失的字段(兼容已存在的表) ============ + // contracts 表允许 customer_id 为空(采购合同可不关联客户) + try { + await pool.query(`ALTER TABLE contracts MODIFY COLUMN customer_id INT DEFAULT NULL COMMENT '客户ID (关联客户表)'`) + console.log('[init] contracts 表 customer_id 已允许为空') + } catch (e) { + // 修改失败则忽略 + } + + // contracts 表添加 supplier_id(采购合同关联供应商) + try { + await pool.query(`ALTER TABLE contracts ADD COLUMN supplier_id INT DEFAULT NULL COMMENT '供应商ID (关联供应商表)' AFTER customer_id`) + console.log('[init] contracts 表已添加 supplier_id 字段') + } catch (e) { + // 字段已存在则忽略 + } + console.log('[init] 数据表初始化完成') // ============ 2.3 RBAC 权限相关表 ============ diff --git a/middleware/permissions.js b/middleware/permissions.js index ebcce0b..f5e95b0 100644 --- a/middleware/permissions.js +++ b/middleware/permissions.js @@ -39,10 +39,10 @@ function getDataScope(user, resource) { case 'contracts': if (roleName === 'finance') return {} if (roleName === 'procurement_manager') { - return { where: 'c.type = ?', values: ['supply'] } + return { where: 'type = ?', values: ['supply'] } } if (roleName === 'franchise_manager' || roleName === 'operations_manager') { - return { where: 'c.responsible_user_id = ? AND c.type = ?', values: [userId, 'franchise'] } + return { where: 'responsible_user_id = ? AND type = ?', values: [userId, 'franchise'] } } return {} diff --git a/routes/afterSales.js b/routes/afterSales.js index 99f17c4..e37d655 100644 --- a/routes/afterSales.js +++ b/routes/afterSales.js @@ -2,6 +2,24 @@ const { pool } = require('../db') const { getDataScope } = require('../middleware/permissions') +// 格式化日期为 YYYY-MM-DD +function formatDate(dateStr) { + if (!dateStr) return null + if (typeof dateStr === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { + return dateStr + } + try { + const date = new Date(dateStr) + if (isNaN(date.getTime())) return null + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + } catch { + return null + } +} + function pagination(query) { const page = Math.max(Number(query.page) || 1, 1) const pageSize = Math.min(Math.max(Number(query.pageSize) || 10, 1), 100) @@ -23,7 +41,7 @@ const LIST_SELECT = ` async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { handle_status, customer_name } = req.query + const { handle_status, customer_name, id, customer_id, feedback } = req.query let where = 'WHERE 1=1' const params = [] @@ -35,11 +53,23 @@ async function list(req, res) { } if (scope.where) { // list 查询有 JOIN,需要表别名前缀避免列名歧义 - const scopeCol = scope.tableAlias ? scope.where.replace('responsible_user_id', `${scope.tableAlias}.responsible_user_id`) : scope.where + const scopeCol = scope.tableAlias ? scope.where.replace(/\bresponsible_user_id\b/g, `${scope.tableAlias}.responsible_user_id`) : scope.where where += ' AND ' + scopeCol params.push(...scope.values) } + if (id) { + where += ' AND a.id = ?' + params.push(id) + } + if (customer_id) { + where += ' AND a.customer_id = ?' + params.push(customer_id) + } + if (feedback) { + where += ' AND a.feedback LIKE ?' + params.push(`%${feedback}%`) + } if (handle_status) { where += ' AND a.handle_status = ?' params.push(handle_status) @@ -145,7 +175,7 @@ async function create(req, res) { employee_id || null, handle_method || null, handle_status || '待处理', - service_date || null, + formatDate(service_date), remark || null, ownerId] ) @@ -201,7 +231,12 @@ async function update(req, res) { for (const f of fields) { if (req.body[f] !== undefined) { sets.push(`${f} = ?`) - params.push(req.body[f]) + // 日期字段需要格式化 + if (f === 'service_date') { + params.push(formatDate(req.body[f])) + } else { + params.push(req.body[f]) + } } } if (sets.length === 0) { diff --git a/routes/contracts.js b/routes/contracts.js index 0210ccd..b8dc0ac 100644 --- a/routes/contracts.js +++ b/routes/contracts.js @@ -2,6 +2,24 @@ const { pool } = require('../db') const { getDataScope } = require('../middleware/permissions') +// 格式化日期为 YYYY-MM-DD +function formatDate(dateStr) { + if (!dateStr) return null + if (typeof dateStr === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { + return dateStr + } + try { + const date = new Date(dateStr) + if (isNaN(date.getTime())) return null + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + } catch { + return null + } +} + function pagination(query) { const page = Math.max(Number(query.page) || 1, 1) const pageSize = Math.min(Math.max(Number(query.pageSize) || 10, 1), 100) @@ -9,14 +27,16 @@ function pagination(query) { return { page, pageSize, offset } } -// 列表查询时 JOIN 客户名和员工名 +// 列表查询时 JOIN 客户名、供应商名和员工名 const LIST_SELECT = ` SELECT c.*, cu.name AS customer_name, + s.name AS supplier_name, e.name AS employee_name FROM contracts c LEFT JOIN customers cu ON c.customer_id = cu.id + LEFT JOIN suppliers s ON c.supplier_id = s.id LEFT JOIN employees e ON c.employee_id = e.id ` @@ -27,25 +47,35 @@ const DETAIL_SELECT = LIST_SELECT async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { status, customer_name, contract_no, employee_name } = req.query + const { status, customer_name, contract_no, contract_name, employee_name, id, effective_date_start, effective_date_end } = req.query let where = 'WHERE 1=1' const params = [] - // 数据范围过滤 + // 数据范围过滤(列表查询使用表别名 c,需要加前缀) const scope = getDataScope(req.user, 'contracts') if (scope.deny) { return res.status(403).json({ code: 403, message: '无权访问此资源' }) } if (scope.where) { - where += ' AND ' + scope.where + // 给 scope.where 中的字段加上表别名 c. + const scopedWhere = scope.where.replace(/\b(responsible_user_id|type)\b/g, 'c.$1') + where += ' AND ' + scopedWhere params.push(...scope.values) } + if (id) { + where += ' AND c.id = ?' + params.push(id) + } if (status) { where += ' AND c.status = ?' params.push(status) } + if (contract_name) { + where += ' AND c.contract_name LIKE ?' + params.push(`%${contract_name}%`) + } if (customer_name) { where += ' AND cu.name LIKE ?' params.push(`%${customer_name}%`) @@ -58,10 +88,19 @@ async function list(req, res) { where += ' AND e.name LIKE ?' params.push(`%${employee_name}%`) } + if (effective_date_start) { + where += ' AND c.effective_date >= ?' + params.push(effective_date_start) + } + if (effective_date_end) { + where += ' AND c.effective_date <= ?' + params.push(effective_date_end) + } const [[{ total }]] = await pool.query( `SELECT COUNT(*) AS total FROM contracts c LEFT JOIN customers cu ON c.customer_id = cu.id + LEFT JOIN suppliers s ON c.supplier_id = s.id LEFT JOIN employees e ON c.employee_id = e.id ${where}`, params @@ -100,7 +139,9 @@ async function detail(req, res) { return res.status(403).json({ code: 403, message: '无权访问此资源' }) } if (scope.where) { - sql += ' AND ' + scope.where + // detail 查询使用表别名 c,需要给 scope.where 加上前缀 + const scopedWhere = scope.where.replace(/\b(responsible_user_id|type)\b/g, 'c.$1') + sql += ' AND ' + scopedWhere params.push(...scope.values) } @@ -118,22 +159,43 @@ async function detail(req, res) { // POST /api/contracts —— 新增 async function create(req, res) { const { - customer_id, contract_name, contract_no, contract_content, + customer_id, supplier_id, contract_name, contract_no, contract_content, amount, effective_date, expiry_date, employee_id, status, remark, } = req.body || {} - if (!customer_id) { - return res.status(400).json({ code: 400, message: '客户ID必填' }) + // 根据角色限制合同类型 + const contractType = req.body.type || (req.user.roleName === 'procurement_manager' ? 'supply' : 'franchise') + if (req.user.roleName === 'procurement_manager' && contractType !== 'supply') { + return res.status(403).json({ code: 403, message: '采购经理只能创建采购合同' }) + } + if (req.user.roleName === 'franchise_manager' && contractType !== 'franchise') { + return res.status(403).json({ code: 403, message: '招商经理只能创建加盟合同' }) + } + + if (contractType === 'supply' && !supplier_id) { + return res.status(400).json({ code: 400, message: '采购合同必须选择供应商' }) + } + if (contractType !== 'supply' && !customer_id) { + return res.status(400).json({ code: 400, message: '加盟合同必须选择客户' }) } if (!contract_name) { return res.status(400).json({ code: 400, message: '合同名称必填' }) } try { - // 校验客户是否存在 - const [cust] = await pool.query('SELECT id FROM customers WHERE id = ?', [customer_id]) - if (cust.length === 0) { - return res.status(400).json({ code: 400, message: '关联客户不存在' }) + // 校验客户是否存在(非采购合同必填) + if (customer_id) { + const [cust] = await pool.query('SELECT id FROM customers WHERE id = ?', [customer_id]) + if (cust.length === 0) { + return res.status(400).json({ code: 400, message: '关联客户不存在' }) + } + } + // 校验供应商是否存在(采购合同必填) + if (supplier_id) { + const [sup] = await pool.query('SELECT id FROM suppliers WHERE id = ?', [supplier_id]) + if (sup.length === 0) { + return res.status(400).json({ code: 400, message: '关联供应商不存在' }) + } } // 校验业务员(如果填了) if (employee_id) { @@ -145,18 +207,18 @@ async function create(req, res) { const [result] = await pool.query( `INSERT INTO contracts - (customer_id, contract_name, contract_no, contract_content, amount, effective_date, expiry_date, employee_id, status, remark, type, responsible_user_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [customer_id, contract_name, + (customer_id, supplier_id, contract_name, contract_no, contract_content, amount, effective_date, expiry_date, employee_id, status, remark, type, responsible_user_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [customer_id || null, supplier_id || null, contract_name, contract_no || null, contract_content || null, amount !== undefined ? amount : null, - effective_date || null, - expiry_date || null, + formatDate(effective_date), + formatDate(expiry_date), employee_id || null, - status || '生效', + status || '生效中', remark || null, - req.body.type || (req.user.roleName === 'procurement_manager' ? 'supply' : 'franchise'), + contractType, req.body.responsible_user_id || req.user.id] ) @@ -172,12 +234,12 @@ async function create(req, res) { async function update(req, res) { const { id } = req.params const fields = [ - 'customer_id', 'contract_name', 'contract_no', 'contract_content', + 'customer_id', 'supplier_id', 'contract_name', 'contract_no', 'contract_content', 'amount', 'effective_date', 'expiry_date', 'employee_id', 'status', 'remark', 'type', 'responsible_user_id', ] try { - // 确认记录存在且在数据范围内 + // 确认记录存在且在数据范围内(detail/update/delete 不用表别名) let checkSql = 'SELECT id FROM contracts WHERE id = ?' const checkParams = [id] const scope = getDataScope(req.user, 'contracts') @@ -206,13 +268,24 @@ async function update(req, res) { return res.status(400).json({ code: 400, message: '关联业务员不存在' }) } } + if (req.body.supplier_id) { + const [sup] = await pool.query('SELECT id FROM suppliers WHERE id = ?', [req.body.supplier_id]) + if (sup.length === 0) { + return res.status(400).json({ code: 400, message: '关联供应商不存在' }) + } + } const sets = [] const params = [] for (const f of fields) { if (req.body[f] !== undefined) { sets.push(`${f} = ?`) - params.push(req.body[f]) + // 日期字段需要格式化 + if (['effective_date', 'expiry_date'].includes(f)) { + params.push(formatDate(req.body[f])) + } else { + params.push(req.body[f]) + } } } if (sets.length === 0) { diff --git a/routes/customers.js b/routes/customers.js index 1bc6a7e..eb65a52 100644 --- a/routes/customers.js +++ b/routes/customers.js @@ -14,7 +14,7 @@ function pagination(query) { async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { name, phone, province, city } = req.query + const { name, phone, province, city, id } = req.query let where = 'WHERE 1=1' const params = [] @@ -29,32 +29,40 @@ async function list(req, res) { params.push(...scope.values) } + if (id) { + where += ' AND c.id = ?' + params.push(id) + } if (name) { - where += ' AND name LIKE ?' + where += ' AND c.name LIKE ?' params.push(`%${name}%`) } if (phone) { - where += ' AND phone LIKE ?' + where += ' AND c.phone LIKE ?' params.push(`%${phone}%`) } if (province) { - where += ' AND province = ?' + where += ' AND c.province = ?' params.push(province) } if (city) { - where += ' AND city = ?' + where += ' AND c.city = ?' params.push(city) } // 查总数 const [[{ total }]] = await pool.query( - `SELECT COUNT(*) AS total FROM customers ${where}`, + `SELECT COUNT(*) AS total FROM customers c ${where}`, params ) - // 查分页数据 + // 查分页数据(JOIN users+employees 获取负责人姓名) const [rows] = await pool.query( - `SELECT * FROM customers ${where} ORDER BY id DESC LIMIT ? OFFSET ?`, + `SELECT c.*, e.name AS responsible_user_name + FROM customers c + LEFT JOIN users u ON c.responsible_user_id = u.id + LEFT JOIN employees e ON u.employee_id = e.id + ${where} ORDER BY c.id DESC LIMIT ? OFFSET ?`, [...params, pageSize, offset] ) @@ -201,6 +209,9 @@ async function remove(req, res) { res.json({ code: 0, message: 'ok' }) } catch (e) { console.error('[customers delete] error:', e) + if (e.code === 'ER_ROW_IS_REFERENCED_2') { + return res.status(400).json({ code: 400, message: '该客户被合同或售后记录引用,无法删除' }) + } res.status(500).json({ code: 500, message: e.message }) } } diff --git a/routes/employees.js b/routes/employees.js index 0cd6904..73b3960 100644 --- a/routes/employees.js +++ b/routes/employees.js @@ -2,6 +2,24 @@ const { pool } = require('../db') const { getDataScope } = require('../middleware/permissions') +// 格式化日期为 YYYY-MM-DD +function formatDate(dateStr) { + if (!dateStr) return null + if (typeof dateStr === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { + return dateStr // 已经是正确格式 + } + try { + const date = new Date(dateStr) + if (isNaN(date.getTime())) return null + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + return `${year}-${month}-${day}` + } catch { + return null + } +} + function pagination(query) { const page = Math.max(Number(query.page) || 1, 1) const pageSize = Math.min(Math.max(Number(query.pageSize) || 10, 1), 100) @@ -13,7 +31,7 @@ function pagination(query) { async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { name, department, status } = req.query + const { id, name, status, department } = req.query let where = 'WHERE 1=1' const params = [] @@ -28,18 +46,22 @@ async function list(req, res) { params.push(...scope.values) } + if (id) { + where += ' AND id = ?' + params.push(Number(id)) + } if (name) { where += ' AND name LIKE ?' params.push(`%${name}%`) } - if (department) { - where += ' AND department LIKE ?' - params.push(`%${department}%`) - } if (status !== undefined && status !== '') { where += ' AND status = ?' params.push(Number(status)) } + if (department) { + where += ' AND department LIKE ?' + params.push(`%${department}%`) + } const [[{ total }]] = await pool.query( `SELECT COUNT(*) AS total FROM employees ${where}`, @@ -114,7 +136,7 @@ async function create(req, res) { age !== undefined ? age : null, education || null, department || null, - entry_date || null, + formatDate(entry_date), position || null, salary !== undefined ? salary : null, phone || null, @@ -160,7 +182,12 @@ async function update(req, res) { for (const f of fields) { if (req.body[f] !== undefined) { sets.push(`${f} = ?`) - params.push(req.body[f]) + // 日期字段需要格式化 + if (f === 'entry_date') { + params.push(formatDate(req.body[f])) + } else { + params.push(req.body[f]) + } } } if (sets.length === 0) { @@ -170,6 +197,11 @@ async function update(req, res) { params.push(id) await pool.query(`UPDATE employees SET ${sets.join(', ')} WHERE id = ?`, params) + // 同步 department 到关联的 users 表 + if (req.body.department !== undefined) { + await pool.query('UPDATE users SET department = ? WHERE employee_id = ?', [req.body.department, id]) + } + const [rows] = await pool.query('SELECT * FROM employees WHERE id = ?', [id]) res.json({ code: 0, message: 'ok', data: rows[0] }) } catch (e) { @@ -196,10 +228,20 @@ async function remove(req, res) { if (existing.length === 0) { return res.status(404).json({ code: 404, message: '员工不存在或无权操作' }) } + // 禁止删除自己关联的员工 + const [selfCheck] = await pool.query('SELECT id FROM users WHERE employee_id = ? AND id = ?', [id, req.user.id]) + if (selfCheck.length > 0) { + return res.status(400).json({ code: 400, message: '不能删除自己的员工账号' }) + } + // 同步删除关联的用户账号 + await pool.query('DELETE FROM users WHERE employee_id = ?', [id]) await pool.query('DELETE FROM employees WHERE id = ?', [id]) res.json({ code: 0, message: 'ok' }) } catch (e) { console.error('[employees delete] error:', e) + if (e.code === 'ER_ROW_IS_REFERENCED_2') { + return res.status(400).json({ code: 400, message: '该员工被合同或售后记录引用,无法删除' }) + } res.status(500).json({ code: 500, message: e.message }) } } diff --git a/routes/products.js b/routes/products.js index 601766d..685fec5 100644 --- a/routes/products.js +++ b/routes/products.js @@ -12,11 +12,15 @@ function pagination(query) { async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { name, type, supplier } = req.query + const { name, type, supplier, id } = req.query let where = 'WHERE 1=1' const params = [] + if (id) { + where += ' AND id = ?' + params.push(id) + } if (name) { where += ' AND name LIKE ?' params.push(`%${name}%`) @@ -150,6 +154,9 @@ async function remove(req, res) { res.json({ code: 0, message: 'ok' }) } catch (e) { console.error('[products delete] error:', e) + if (e.code === 'ER_ROW_IS_REFERENCED_2') { + return res.status(400).json({ code: 400, message: '该产品被引用,无法删除' }) + } res.status(500).json({ code: 500, message: e.message }) } } diff --git a/routes/suppliers.js b/routes/suppliers.js index 2df8a08..7de150f 100644 --- a/routes/suppliers.js +++ b/routes/suppliers.js @@ -12,11 +12,15 @@ function pagination(query) { async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { name, type, status } = req.query + const { id, name, type, status } = req.query let where = 'WHERE 1=1' const params = [] + if (id) { + where += ' AND id = ?' + params.push(Number(id)) + } if (name) { where += ' AND name LIKE ?' params.push(`%${name}%`) @@ -140,6 +144,9 @@ async function remove(req, res) { res.json({ code: 0, message: 'ok' }) } catch (e) { console.error('[suppliers delete] error:', e) + if (e.code === 'ER_ROW_IS_REFERENCED_2') { + return res.status(400).json({ code: 400, message: '该供应商被合同引用,无法删除' }) + } res.status(500).json({ code: 500, message: e.message }) } } diff --git a/routes/users.js b/routes/users.js index b6027e0..f4c5fcc 100644 --- a/routes/users.js +++ b/routes/users.js @@ -118,6 +118,21 @@ async function info(req, res) { } } +// GET /api/user/list —— 简易用户列表(用于下拉选择负责人,仅需登录) +async function simpleList(req, res) { + try { + const [rows] = await pool.query( + `SELECT u.id, u.username, e.name AS real_name + FROM users u LEFT JOIN employees e ON u.employee_id = e.id + WHERE u.is_active = 1 ORDER BY u.id` + ) + res.json({ code: 0, message: 'ok', data: rows }) + } catch (e) { + console.error('[user simpleList] error:', e) + res.status(500).json({ code: 500, message: e.message }) + } +} + // POST /api/user/logout —— 登出(需要 token,仅做应答) async function logout(req, res) { res.json({ code: 0, message: 'ok' }) @@ -162,7 +177,7 @@ async function changePassword(req, res) { async function list(req, res) { try { const { page, pageSize, offset } = pagination(req.query) - const { username, role_id, is_active } = req.query + const { username, real_name, id, role_id, is_active } = req.query let where = 'WHERE 1=1' const params = [] @@ -171,6 +186,14 @@ async function list(req, res) { where += ' AND u.username LIKE ?' params.push(`%${username}%`) } + if (real_name) { + where += ' AND e.name LIKE ?' + params.push(`%${real_name}%`) + } + if (id) { + where += ' AND u.id = ?' + params.push(Number(id)) + } if (role_id) { where += ' AND u.role_id = ?' params.push(Number(role_id)) @@ -181,7 +204,9 @@ async function list(req, res) { } const [[{ total }]] = await pool.query( - `SELECT COUNT(*) AS total FROM users u ${where}`, + `SELECT COUNT(*) AS total FROM users u + LEFT JOIN employees e ON u.employee_id = e.id + ${where}`, params ) @@ -369,11 +394,20 @@ async function remove(req, res) { return res.status(400).json({ code: 400, message: '不能删除自己' }) } - const [existing] = await pool.query('SELECT id FROM users WHERE id = ?', [id]) + const [existing] = await pool.query('SELECT id, role_id FROM users WHERE id = ?', [id]) if (existing.length === 0) { return res.status(404).json({ code: 404, message: '用户不存在' }) } + // 禁止删除最后一个管理员 + const [adminRole] = await pool.query('SELECT id FROM roles WHERE name = ?', ['admin']) + if (adminRole.length > 0 && existing[0].role_id === adminRole[0].id) { + const [[{ cnt }]] = await pool.query('SELECT COUNT(*) AS cnt FROM users WHERE role_id = ?', [adminRole[0].id]) + if (cnt <= 1) { + return res.status(400).json({ code: 400, message: '不能删除最后一个管理员' }) + } + } + await pool.query('DELETE FROM users WHERE id = ?', [id]) res.json({ code: 0, message: 'ok' }) } catch (e) { @@ -382,4 +416,4 @@ async function remove(req, res) { } } -module.exports = { login, info, logout, changePassword, list, detail, create, update, remove } +module.exports = { login, info, simpleList, logout, changePassword, list, detail, create, update, remove } diff --git a/server.js b/server.js index 208007f..a14d0fe 100644 --- a/server.js +++ b/server.js @@ -35,6 +35,7 @@ function auth(req, res, next) { // ============ 登录/登出/个人信息 ============ app.post('/api/user/login', users.login) app.get('/api/user/info', auth, users.info) +app.get('/api/user/list', auth, checkPermission('user', 'manage'), users.simpleList) app.post('/api/user/logout', auth, users.logout) app.put('/api/user/password', auth, users.changePassword) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..774952f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,642 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +aws-ssl-profiles@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" + integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== + +bcryptjs@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.3.tgz#4b93d6a398c48bfc9f32ee65d301174a8a8ea56f" + integrity sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g== + +body-parser@^2.2.1: + version "2.3.0" + resolved "https://registry.npmmirror.com/body-parser/-/body-parser-2.3.0.tgz#6d8662f4d8c336028b8ac9aa24251b0ca64ba437" + integrity sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw== + dependencies: + bytes "^3.1.2" + content-type "^2.0.0" + debug "^4.4.3" + http-errors "^2.0.1" + iconv-lite "^0.7.2" + on-finished "^2.4.1" + qs "^6.15.2" + raw-body "^3.0.2" + type-is "^2.1.0" + +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +bytes@^3.1.2, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +content-disposition@^1.0.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.1.0.tgz#f3db789c752d45564cc7e9e1e0b31790d4a38e17" + integrity sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g== + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +content-type@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/content-type/-/content-type-2.0.0.tgz#2fb3ede69dffa0af78ca7c4ce7589680638b56df" + integrity sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ== + +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1: + version "0.7.2" + resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +dotenv@^17.4.2: + version "17.4.2" + resolved "https://registry.npmmirror.com/dotenv/-/dotenv-17.4.2.tgz#c07e54a746e11eba021dd9e1047ced5afdc1c034" + integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.2" + resolved "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" + integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== + dependencies: + es-errors "^1.3.0" + +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +express@^5.2.1: + version "5.2.1" + resolved "https://registry.npmmirror.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + dependencies: + accepts "^2.0.0" + body-parser "^2.2.1" + content-disposition "^1.0.0" + content-type "^1.0.5" + cookie "^0.7.1" + cookie-signature "^1.2.1" + debug "^4.4.0" + depd "^2.0.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + finalhandler "^2.1.0" + fresh "^2.0.0" + http-errors "^2.0.0" + merge-descriptors "^2.0.0" + mime-types "^3.0.0" + on-finished "^2.4.1" + once "^1.4.0" + parseurl "^1.3.3" + proxy-addr "^2.0.7" + qs "^6.14.0" + range-parser "^1.2.1" + router "^2.2.0" + send "^1.1.0" + serve-static "^2.2.0" + statuses "^2.0.1" + type-is "^2.0.1" + vary "^1.1.2" + +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + dependencies: + debug "^4.4.0" + encodeurl "^2.0.0" + escape-html "^1.0.3" + on-finished "^2.4.1" + parseurl "^1.3.3" + statuses "^2.0.1" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +hasown@^2.0.2: + version "2.0.4" + resolved "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" + integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== + dependencies: + function-bind "^1.1.2" + +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +iconv-lite@^0.7.2, iconv-lite@~0.7.0: + version "0.7.2" + resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +inherits@~2.0.4: + version "2.0.4" + resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + +jsonwebtoken@^9.0.3: + version "9.0.3" + resolved "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz#6cd57ab01e9b0ac07cb847d53d3c9b6ee31f7ae2" + integrity sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g== + dependencies: + jws "^4.0.1" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + +jwa@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804" + integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/jws/-/jws-4.0.1.tgz#07edc1be8fac20e677b283ece261498bd38f0690" + integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== + dependencies: + jwa "^2.0.1" + safe-buffer "^5.0.1" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +long@^5.3.2: + version "5.3.2" + resolved "https://registry.npmmirror.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + +lru.min@^1.1.0, lru.min@^1.1.4: + version "1.1.4" + resolved "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.4.tgz#6ea1737a8c1ba2300cc87ad46910a4bdffa0117b" + integrity sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mysql2@^3.22.5: + version "3.22.5" + resolved "https://registry.npmmirror.com/mysql2/-/mysql2-3.22.5.tgz#26c51c035ac577579ad239168015ad1eec321679" + integrity sha512-95uZ2TrPWAZdwpB3vvvDbmEMcNG8yIeNCyu6GUcr/QnWEE/wXm7+mhOCsdQfWQDTV7qYT/PDUZ4U4UPP4AsXqQ== + dependencies: + aws-ssl-profiles "^1.1.2" + denque "^2.1.0" + generate-function "^2.3.1" + iconv-lite "^0.7.2" + long "^5.3.2" + lru.min "^1.1.4" + named-placeholders "^1.1.6" + sql-escaper "^1.3.3" + +named-placeholders@^1.1.6: + version "1.1.6" + resolved "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.6.tgz#c50c6920b43f258f59c16add1e56654f5cc02bb5" + integrity sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w== + dependencies: + lru.min "^1.1.0" + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-to-regexp@^8.0.0: + version "8.4.2" + resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.2.tgz#795c420c4f7ca45c5b887366f622ee0c9852cccd" + integrity sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA== + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +qs@^6.14.0, qs@^6.15.2: + version "6.15.3" + resolved "https://registry.npmmirror.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b" + integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== + dependencies: + es-define-property "^1.0.1" + side-channel "^1.1.1" + +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" + +router@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + +safe-buffer@^5.0.1: + version "5.2.1" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.5.4: + version "7.8.5" + resolved "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69" + integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA== + +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.npmmirror.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" + +serve-static@^2.2.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel-list@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" + integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + side-channel-list "^1.0.1" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +sql-escaper@^1.3.3: + version "1.3.3" + resolved "https://registry.npmmirror.com/sql-escaper/-/sql-escaper-1.3.3.tgz#65faf89f048d26bb9a75566b82b5990ddf8a5b7f" + integrity sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw== + +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-is@^2.0.1, type-is@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/type-is/-/type-is-2.1.0.tgz#71d1a7053293582e16ac9f3ebaf1ab9aa49e5570" + integrity sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA== + dependencies: + content-type "^2.0.0" + media-typer "^1.1.0" + mime-types "^3.0.0" + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +vary@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==