commit e704c8abcada401eacd75e7aff5dae3906b5316a Author: sunzhuangzhuang <961120009@qq.com> Date: Thu Sep 4 10:49:10 2025 +0800 初次提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..3099ef2 --- /dev/null +++ b/config/config.js @@ -0,0 +1,17 @@ +const mysql = require('mysql2') + +const sql = { + createConnection() { + return mysql.createPool({ + connectionLimit: 10, + host: '114.55.111.44', + user: 'test_mao', + password: 'nK2mPbWriBp25BRd', + database: 'test_mao', + charset: 'utf8mb4', + multipleStatements: true + + }) + } +} +module.exports = sql diff --git a/config/constants.js b/config/constants.js new file mode 100644 index 0000000..3272b44 --- /dev/null +++ b/config/constants.js @@ -0,0 +1,70 @@ +// 系统常量配置 +module.exports = { + // 转账类型 + TRANSFER_TYPES: { + USER_TO_USER: 'user_to_user', + SYSTEM_TO_USER: 'system_to_user', + USER_TO_SYSTEM: 'user_to_system' + }, + + // 转账状态 + TRANSFER_STATUS: { + PENDING: 'pending', + CONFIRMED: 'confirmed', + RECEIVED: 'received', + REJECTED: 'rejected', + CANCELLED: 'cancelled', + NOT_RECEIVED: 'not_received', + FAILED: 'failed' + }, + + // 用户角色 + USER_ROLES: { + ADMIN: 'admin', + USER: 'user' + }, + + // 订单状态 + ORDER_STATUS: { + PENDING: 'pending', + PAID: 'paid', + SHIPPED: 'shipped', + DELIVERED: 'delivered', + CANCELLED: 'cancelled' + }, + + // 错误代码 + ERROR_CODES: { + VALIDATION_ERROR: 'VALIDATION_ERROR', + AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR', + AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR', + NOT_FOUND: 'NOT_FOUND', + DUPLICATE_ENTRY: 'DUPLICATE_ENTRY', + DATABASE_ERROR: 'DATABASE_ERROR', + INTERNAL_ERROR: 'INTERNAL_ERROR' + }, + + // HTTP状态码 + HTTP_STATUS: { + OK: 200, + CREATED: 201, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + CONFLICT: 409, + INTERNAL_SERVER_ERROR: 500 + }, + + // 分页默认值 + PAGINATION: { + DEFAULT_PAGE: 1, + DEFAULT_LIMIT: 10, + MAX_LIMIT: 100 + }, + + // JWT配置 + JWT: { + EXPIRES_IN: '24h' + } +}; \ No newline at end of file diff --git a/config/database-init.js b/config/database-init.js new file mode 100644 index 0000000..119de5f --- /dev/null +++ b/config/database-init.js @@ -0,0 +1,1105 @@ +const mysql = require('mysql2/promise'); +const bcrypt = require('bcryptjs'); +const { initDB, getDB, dbConfig } = require('../database'); + +/** + * 数据库初始化函数 + * 创建所有必要的表结构和初始数据 + */ +async function initDatabase() { + try { + // 首先创建数据库(如果不存在) + const tempConnection = await mysql.createConnection({ + host: dbConfig.host, + user: dbConfig.user, + password: dbConfig.password + }); + + // 创建数据库 + await tempConnection.execute(`CREATE DATABASE IF NOT EXISTS ${dbConfig.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`); + await tempConnection.end(); + + // 初始化数据库连接池 + await initDB(); + console.log('数据库连接池初始化成功'); + + // 创建所有表 + await createTables(); + + // 添加字段(处理表结构升级) + await addMissingFields(); + + // 创建默认数据 + await createDefaultData(); + + console.log('数据库初始化完成'); + } catch (error) { + console.error('数据库初始化失败:', error); + throw error; + } +} + +/** + * 创建所有数据库表 + */ +async function createTables() { + // 用户表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + phone VARCHAR(20) UNIQUE, + password VARCHAR(255) NOT NULL, + role ENUM('user', 'admin') DEFAULT 'user', + avatar VARCHAR(255), + points INT DEFAULT 0, + rongdou INT DEFAULT 0, + balance DECIMAL(10,2) DEFAULT 0.00, + real_name VARCHAR(100), + id_card VARCHAR(18), + wechat_qr VARCHAR(255), + alipay_qr VARCHAR(255), + bank_card VARCHAR(30), + unionpay_qr VARCHAR(255), + business_license VARCHAR(500), + id_card_front VARCHAR(500), + id_card_back VARCHAR(500), + audit_status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending', + audit_note TEXT, + audited_by INT, + audited_at TIMESTAMP NULL, + city VARCHAR(50), + district_id INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (audited_by) REFERENCES users(id), + FOREIGN KEY (district_id) REFERENCES zhejiang_regions(id) + ) + `); + + // 商品表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS products ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + price INT NOT NULL, + points_price INT NOT NULL, + rongdou_price INT NOT NULL DEFAULT 0, + original_price INT, + stock INT DEFAULT 0, + sales INT DEFAULT 0, + rating DECIMAL(3,2) DEFAULT 5.00, + category VARCHAR(100), + image_url VARCHAR(500), + images JSON, + videos JSON, + details TEXT, + shop_name VARCHAR(255), + shop_avatar VARCHAR(500), + payment_methods JSON, + status ENUM('active', 'inactive') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + `); + + // 订单表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS orders ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + order_no VARCHAR(50) UNIQUE NOT NULL, + total_amount INT NOT NULL, + total_points INT NOT NULL, + total_rongdou INT NOT NULL DEFAULT 0, + status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled', 'pre_order') DEFAULT 'pending', + address JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + `); + + // 创建转账记录表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS transfers ( + id INT AUTO_INCREMENT PRIMARY KEY, + from_user_id INT NULL, + to_user_id INT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + transfer_type ENUM('initial', 'return', 'user_to_user', 'system_to_user', 'user_to_system') DEFAULT 'user_to_user', + status ENUM('pending', 'confirmed', 'rejected', 'received', 'not_received') DEFAULT 'pending', + voucher_url VARCHAR(500), + description TEXT, + batch_id VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (from_user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (to_user_id) REFERENCES users(id) ON DELETE CASCADE + ) + `); + + // 创建转账确认表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS transfer_confirmations ( + id INT AUTO_INCREMENT PRIMARY KEY, + transfer_id INT NOT NULL, + confirmer_id INT NOT NULL, + action ENUM('confirm', 'reject') NOT NULL, + note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (transfer_id) REFERENCES transfers(id) ON DELETE CASCADE, + FOREIGN KEY (confirmer_id) REFERENCES users(id) ON DELETE CASCADE + ) + `); + + // 订单商品表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS order_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + order_id INT NOT NULL, + product_id INT NOT NULL, + spec_combination_id INT NULL COMMENT '规格组合ID', + quantity INT NOT NULL, + price INT NOT NULL, + points INT NOT NULL, + points_price INT NOT NULL DEFAULT 0, + rongdou INT DEFAULT 0 COMMENT '融豆价格', + rongdou_price INT NOT NULL DEFAULT 0, + spec_info JSON COMMENT '规格信息快照', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (order_id) REFERENCES orders(id), + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (spec_combination_id) REFERENCES product_spec_combinations(id) ON DELETE SET NULL + ) + `); + + // 积分记录表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS points_history ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + type ENUM('earn', 'spend') NOT NULL, + amount INT NOT NULL, + description VARCHAR(255), + order_id INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (order_id) REFERENCES orders(id) + ) + `); + + // 融豆记录表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS rongdou_history ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + type ENUM('earn', 'spend') NOT NULL, + amount INT NOT NULL, + description VARCHAR(255), + order_id INT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (order_id) REFERENCES orders(id) + ) + `); + + // 管理员操作日志表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS admin_operation_logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + admin_id INT NOT NULL, + operation_type VARCHAR(50) NOT NULL, + target_type VARCHAR(50) NOT NULL, + target_id INT NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (admin_id) REFERENCES users(id) ON DELETE CASCADE + ) + `); + + // 商品评价表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS product_reviews ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL, + user_id INT NOT NULL, + order_id INT NOT NULL, + rating INT NOT NULL, + comment TEXT, + images JSON, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (order_id) REFERENCES orders(id) + ) + `); + + // 规格名称表(规格维度) + await getDB().execute(` + CREATE TABLE IF NOT EXISTS spec_names ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL COMMENT '规格名称,如:颜色、尺寸、材质', + display_name VARCHAR(100) NOT NULL COMMENT '显示名称', + sort_order INT DEFAULT 0 COMMENT '排序', + status ENUM('active', 'inactive') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY unique_name (name) + ) + `); + + // 规格值表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS spec_values ( + id INT AUTO_INCREMENT PRIMARY KEY, + spec_name_id INT NOT NULL COMMENT '规格名称ID', + value VARCHAR(100) NOT NULL COMMENT '规格值,如:红色、XL、棉质', + display_value VARCHAR(100) NOT NULL COMMENT '显示值', + color_code VARCHAR(20) COMMENT '颜色代码(仅颜色规格使用)', + image_url VARCHAR(500) COMMENT '规格图片', + sort_order INT DEFAULT 0 COMMENT '排序', + status ENUM('active', 'inactive') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (spec_name_id) REFERENCES spec_names(id) ON DELETE CASCADE, + UNIQUE KEY unique_spec_value (spec_name_id, value) + ) + `); + + // 商品规格组合表(笛卡尔积结果) + await getDB().execute(` + CREATE TABLE IF NOT EXISTS product_spec_combinations ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL COMMENT '商品ID', + combination_key VARCHAR(255) NOT NULL COMMENT '组合键,如:color_1-size_2-material_3', + spec_values JSON NOT NULL COMMENT '规格值组合,存储spec_value_id数组', + price_adjustment INT DEFAULT 0 COMMENT '价格调整', + points_adjustment INT DEFAULT 0 COMMENT '积分调整', + rongdou_adjustment INT DEFAULT 0 COMMENT '融豆调整', + stock INT DEFAULT 0 COMMENT '库存', + sku_code VARCHAR(100) COMMENT 'SKU编码', + barcode VARCHAR(100) COMMENT '条形码', + weight DECIMAL(8,3) COMMENT '重量(kg)', + volume DECIMAL(10,3) COMMENT '体积(cm³)', + status ENUM('active', 'inactive') DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, + UNIQUE KEY unique_product_combination (product_id, combination_key), + INDEX idx_product_status (product_id, status), + INDEX idx_sku_code (sku_code) + ) + `); + + // 商品规格关联表(定义商品使用哪些规格维度) + await getDB().execute(` + CREATE TABLE IF NOT EXISTS product_spec_names ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL COMMENT '商品ID', + spec_name_id INT NOT NULL COMMENT '规格名称ID', + is_required BOOLEAN DEFAULT TRUE COMMENT '是否必选规格', + sort_order INT DEFAULT 0 COMMENT '排序', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, + FOREIGN KEY (spec_name_id) REFERENCES spec_names(id) ON DELETE CASCADE, + UNIQUE KEY unique_product_spec_name (product_id, spec_name_id) + ) + `); + + // 商品属性表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS product_attributes ( + id INT AUTO_INCREMENT PRIMARY KEY, + product_id INT NOT NULL, + attribute_key VARCHAR(100) NOT NULL, + attribute_value TEXT NOT NULL, + sort_order INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE + ) + `); + + // 商品收藏表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS product_favorites ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + product_id INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, + UNIQUE KEY unique_user_product (user_id, product_id) + ) + `); + + // 购物车表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS cart_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + product_id INT NOT NULL, + quantity INT NOT NULL DEFAULT 1, + spec_combination_id INT NULL COMMENT '规格组合ID', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, + FOREIGN KEY (spec_combination_id) REFERENCES product_spec_combinations(id) ON DELETE CASCADE, + UNIQUE KEY unique_user_product_spec (user_id, product_id, spec_combination_id) + ) + `); + + // 用户收货地址表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS user_addresses ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + recipient_name VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + province_code VARCHAR(20), + province_name VARCHAR(50) NOT NULL, + city_code VARCHAR(20), + city_name VARCHAR(50) NOT NULL, + district_code VARCHAR(20), + district_name VARCHAR(50) NOT NULL, + detailed_address TEXT NOT NULL, + postal_code VARCHAR(10), + label_id INT, + is_default BOOLEAN DEFAULT FALSE, + deleted_at TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (label_id) REFERENCES address_labels(id) ON DELETE SET NULL + ) + `); + + // 地址标签表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS address_labels ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL, + color VARCHAR(20) DEFAULT '#1890ff', + user_id INT NULL COMMENT '用户ID,NULL表示系统标签', + is_system BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ) + `); + + // 全国省市区表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS china_regions ( + id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(20) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + parent_code VARCHAR(20), + level TINYINT NOT NULL COMMENT '1:省 2:市 3:区', + sort_order INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_parent_code (parent_code), + INDEX idx_level (level) + ) + `); + + // 匹配订单表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS matching_orders ( + id INT AUTO_INCREMENT PRIMARY KEY, + initiator_id INT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + status ENUM('pending', 'matching', 'completed', 'cancelled') DEFAULT 'pending', + cycle_count INT DEFAULT 0, + max_cycles INT DEFAULT 3, + matching_type ENUM('small', 'large', 'system_reverse') DEFAULT 'small', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (initiator_id) REFERENCES users(id) ON DELETE CASCADE + ) + `); + + // 匹配订单分配表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS order_allocations ( + id INT AUTO_INCREMENT PRIMARY KEY, + matching_order_id INT NOT NULL, + from_user_id INT NOT NULL, + to_user_id INT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + cycle_number INT NOT NULL, + status ENUM('pending', 'confirmed', 'rejected', 'completed') DEFAULT 'pending', + transfer_id INT, + outbound_date DATE, + return_date DATE, + can_return_after TIMESTAMP, + confirmed_at TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (matching_order_id) REFERENCES matching_orders(id) ON DELETE CASCADE, + FOREIGN KEY (from_user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (to_user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (transfer_id) REFERENCES transfers(id) ON DELETE SET NULL + ) + `); + + // 用户匹配池表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS user_matching_pool ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + available_amount DECIMAL(10,2) DEFAULT 0.00, + is_active BOOLEAN DEFAULT TRUE, + last_matched_at TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE KEY unique_user (user_id) + ) + `); + + // 匹配记录表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS matching_records ( + id INT AUTO_INCREMENT PRIMARY KEY, + matching_order_id INT NOT NULL, + user_id INT NOT NULL, + action ENUM('join', 'confirm', 'reject', 'complete') NOT NULL, + amount DECIMAL(10,2), + note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (matching_order_id) REFERENCES matching_orders(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ) + `); + + // 创建系统设置表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS system_settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + setting_key VARCHAR(100) NOT NULL UNIQUE, + setting_value TEXT, + description VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + `); + + // 创建激活码表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS registration_codes ( + id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(10) UNIQUE NOT NULL, + expires_at TIMESTAMP NOT NULL, + is_used BOOLEAN DEFAULT FALSE, + used_at TIMESTAMP NULL, + created_by_admin_id INT, + used_by_user_id INT, + agent_id INT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (created_by_admin_id) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (used_by_user_id) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (agent_id) REFERENCES users(id) ON DELETE SET NULL + ) + `); + + // 创建浙江省区域表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS zhejiang_regions ( + id INT AUTO_INCREMENT PRIMARY KEY, + city_name VARCHAR(50) NOT NULL, + district_name VARCHAR(50) NOT NULL, + region_code VARCHAR(20) UNIQUE NOT NULL, + is_available BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY unique_region (city_name, district_name) + ) + `); + + // 创建区域代理表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS regional_agents ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + region_id INT NOT NULL, + agent_code VARCHAR(20) UNIQUE NOT NULL, + status ENUM('pending', 'active', 'suspended', 'terminated') DEFAULT 'pending', + commission_rate DECIMAL(5,4) DEFAULT 0.1000, + total_earnings DECIMAL(10,2) DEFAULT 0.00, + recruited_merchants INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (region_id) REFERENCES zhejiang_regions(id) ON DELETE CASCADE, + UNIQUE KEY unique_agent_region (user_id, region_id) + ) + `); + + // 创建代理商户关系表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS agent_merchants ( + id INT AUTO_INCREMENT PRIMARY KEY, + agent_id INT NOT NULL, + merchant_id INT NOT NULL, + registration_code_id INT, + matching_count INT DEFAULT 0, + commission_earned DECIMAL(10,2) DEFAULT 0.00, + is_qualified BOOLEAN DEFAULT FALSE, + qualified_at TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE, + FOREIGN KEY (merchant_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (registration_code_id) REFERENCES registration_codes(id) ON DELETE SET NULL, + UNIQUE KEY unique_agent_merchant (agent_id, merchant_id) + ) + `); + + // 创建代理佣金记录表 + await getDB().execute(` + CREATE TABLE IF NOT EXISTS agent_commission_records ( + id INT AUTO_INCREMENT PRIMARY KEY, + agent_id INT NOT NULL, + merchant_id INT NOT NULL, + order_id INT, + commission_amount DECIMAL(10,2) NOT NULL, + commission_type ENUM('registration', 'matching') DEFAULT 'matching', + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE, + FOREIGN KEY (merchant_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (order_id) REFERENCES matching_orders(id) ON DELETE SET NULL + ) + `); +} + +/** + * 添加缺失的字段(处理数据库升级) + */ +async function addMissingFields() { + // 为现有的matching_orders表添加字段 + const matchingOrderFields = [ + { name: 'matching_type', sql: 'ALTER TABLE matching_orders ADD COLUMN matching_type ENUM(\'small\', \'large\') DEFAULT \'small\'' }, + { name: 'is_system_reverse', sql: 'ALTER TABLE matching_orders ADD COLUMN is_system_reverse BOOLEAN DEFAULT FALSE' } + ]; + + for (const field of matchingOrderFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加${field.name}字段错误:`, error.message); + } + } + } + + // 为现有的users表添加字段 + const userFields = [ + { name: 'balance', sql: 'ALTER TABLE users ADD COLUMN balance DECIMAL(10,2) DEFAULT 0.00' }, + { name: 'is_system_account', sql: 'ALTER TABLE users ADD COLUMN is_system_account BOOLEAN DEFAULT FALSE' }, + { name: 'points', sql: 'ALTER TABLE users ADD COLUMN points INT DEFAULT 0' }, + { name: 'avatar', sql: 'ALTER TABLE users ADD COLUMN avatar VARCHAR(255)' }, + { name: 'real_name', sql: 'ALTER TABLE users ADD COLUMN real_name VARCHAR(100)' }, + { name: 'id_card', sql: 'ALTER TABLE users ADD COLUMN id_card VARCHAR(18)' }, + { name: 'wechat_qr', sql: 'ALTER TABLE users ADD COLUMN wechat_qr VARCHAR(255)' }, + { name: 'alipay_qr', sql: 'ALTER TABLE users ADD COLUMN alipay_qr VARCHAR(255)' }, + { name: 'bank_card', sql: 'ALTER TABLE users ADD COLUMN bank_card VARCHAR(30)' }, + { name: 'unionpay_qr', sql: 'ALTER TABLE users ADD COLUMN unionpay_qr VARCHAR(255)' }, + { name: 'phone', sql: 'ALTER TABLE users ADD COLUMN phone VARCHAR(20) UNIQUE' }, + { name: 'completed_withdrawals', sql: 'ALTER TABLE users ADD COLUMN completed_withdrawals INT DEFAULT 0' }, + { name: 'business_license', sql: 'ALTER TABLE users ADD COLUMN business_license VARCHAR(500)' }, + { name: 'id_card_front', sql: 'ALTER TABLE users ADD COLUMN id_card_front VARCHAR(500)' }, + { name: 'id_card_back', sql: 'ALTER TABLE users ADD COLUMN id_card_back VARCHAR(500)' }, + { name: 'audit_status', sql: 'ALTER TABLE users ADD COLUMN audit_status ENUM(\'pending\', \'approved\', \'rejected\') DEFAULT \'pending\'' }, + { name: 'audit_note', sql: 'ALTER TABLE users ADD COLUMN audit_note TEXT' }, + { name: 'audited_by', sql: 'ALTER TABLE users ADD COLUMN audited_by INT' }, + { name: 'audited_at', sql: 'ALTER TABLE users ADD COLUMN audited_at TIMESTAMP NULL' }, + { name: 'city', sql: 'ALTER TABLE users ADD COLUMN city VARCHAR(50)' }, + { name: 'district_id', sql: 'ALTER TABLE users ADD COLUMN district_id INT' } + ]; + + for (const field of userFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加用户表${field.name}字段错误:`, error.message); + } + } + } + + // 为现有的products表添加字段 + const productFields = [ + { name: 'points_price', sql: 'ALTER TABLE products ADD COLUMN points_price INT NOT NULL DEFAULT 0' }, + { name: 'rongdou_price', sql: 'ALTER TABLE products ADD COLUMN rongdou_price INT NOT NULL DEFAULT 0' }, + { name: 'image_url', sql: 'ALTER TABLE products ADD COLUMN image_url VARCHAR(500)' }, + { name: 'images', sql: 'ALTER TABLE products ADD COLUMN images JSON' }, + { name: 'videos', sql: 'ALTER TABLE products ADD COLUMN videos JSON' }, + { name: 'details', sql: 'ALTER TABLE products ADD COLUMN details TEXT' }, + { name: 'shop_name', sql: 'ALTER TABLE products ADD COLUMN shop_name VARCHAR(255)' }, + { name: 'shop_avatar', sql: 'ALTER TABLE products ADD COLUMN shop_avatar VARCHAR(500)' }, + { name: 'payment_methods', sql: 'ALTER TABLE products ADD COLUMN payment_methods JSON' } + ]; + + for (const field of productFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加商品表${field.name}字段错误:`, error.message); + } + } + } + + // 为现有的transfers表添加字段 + const transferFields = [ + { name: 'is_overdue', sql: 'ALTER TABLE transfers ADD COLUMN is_overdue BOOLEAN DEFAULT FALSE' }, + { name: 'is_bad_debt', sql: 'ALTER TABLE transfers ADD COLUMN is_bad_debt BOOLEAN DEFAULT FALSE' }, + { name: 'confirmed_at', sql: 'ALTER TABLE transfers ADD COLUMN confirmed_at TIMESTAMP NULL' }, + { name: 'deadline_at', sql: 'ALTER TABLE transfers ADD COLUMN deadline_at TIMESTAMP NULL' }, + { name: 'admin_note', sql: 'ALTER TABLE transfers ADD COLUMN admin_note TEXT' }, + { name: 'admin_modified_at', sql: 'ALTER TABLE transfers ADD COLUMN admin_modified_at TIMESTAMP NULL' }, + { name: 'admin_modified_by', sql: 'ALTER TABLE transfers ADD COLUMN admin_modified_by INT' } + ]; + + for (const field of transferFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加转账表${field.name}字段错误:`, error.message); + } + } + } + + // 修改transfers表的字段类型 + try { + await getDB().execute(` + ALTER TABLE transfers + MODIFY COLUMN status ENUM('pending', 'confirmed', 'rejected', 'received', 'not_received', 'cancelled') DEFAULT 'pending' + `); + } catch (error) { + console.log('修改transfers状态字段错误:', error.message); + } + + try { + await getDB().execute(` + ALTER TABLE transfers + MODIFY COLUMN from_user_id INT NULL + `); + } catch (error) { + console.log('修改transfers from_user_id字段错误:', error.message); + } + + try { + await getDB().execute(` + ALTER TABLE transfers + MODIFY COLUMN transfer_type ENUM('initial', 'return', 'user_to_user', 'system_to_user', 'user_to_system') DEFAULT 'user_to_user' + `); + } catch (error) { + console.log('修改transfers transfer_type字段错误:', error.message); + } + + // 为现有的order_allocations表添加字段 + const allocationFields = [ + { name: 'confirmed_at', sql: 'ALTER TABLE order_allocations ADD COLUMN confirmed_at TIMESTAMP NULL' }, + { name: 'outbound_date', sql: 'ALTER TABLE order_allocations ADD COLUMN outbound_date DATE' }, + { name: 'return_date', sql: 'ALTER TABLE order_allocations ADD COLUMN return_date DATE' }, + { name: 'can_return_after', sql: 'ALTER TABLE order_allocations ADD COLUMN can_return_after TIMESTAMP' } + ]; + + for (const field of allocationFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加分配表${field.name}字段错误:`, error.message); + } + } + } + + // 为现有的regional_agents表添加字段 + const agentFields = [ + { name: 'approved_at', sql: 'ALTER TABLE regional_agents ADD COLUMN approved_at TIMESTAMP NULL' }, + { name: 'approved_by_admin_id', sql: 'ALTER TABLE regional_agents ADD COLUMN approved_by_admin_id INT' }, + // 提现相关字段 + { name: 'withdrawn_amount', sql: 'ALTER TABLE regional_agents ADD COLUMN withdrawn_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT "已提现金额"' }, + { name: 'pending_withdrawal', sql: 'ALTER TABLE regional_agents ADD COLUMN pending_withdrawal DECIMAL(10,2) DEFAULT 0.00 COMMENT "待审核提现金额"' }, + { name: 'payment_type', sql: 'ALTER TABLE regional_agents ADD COLUMN payment_type ENUM("bank", "wechat", "alipay", "unionpay") DEFAULT "bank" COMMENT "收款方式类型"' }, + { name: 'bank_name', sql: 'ALTER TABLE regional_agents ADD COLUMN bank_name VARCHAR(100) COMMENT "银行名称"' }, + { name: 'account_number', sql: 'ALTER TABLE regional_agents ADD COLUMN account_number VARCHAR(50) COMMENT "账号/银行账号"' }, + { name: 'account_holder', sql: 'ALTER TABLE regional_agents ADD COLUMN account_holder VARCHAR(100) COMMENT "持有人姓名"' }, + { name: 'qr_code_url', sql: 'ALTER TABLE regional_agents ADD COLUMN qr_code_url VARCHAR(255) COMMENT "收款码图片URL"' }, + { name: 'bank_account', sql: 'ALTER TABLE regional_agents ADD COLUMN bank_account VARCHAR(50) COMMENT "银行账号(兼容旧版本)"' } + ]; + + for (const field of agentFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加代理表${field.name}字段错误:`, error.message); + } + } + } + + // 为现有的registration_codes表添加字段 + const registrationCodeFields = [ + { name: 'agent_id', sql: 'ALTER TABLE registration_codes ADD COLUMN agent_id INT NULL' } + ]; + + for (const field of registrationCodeFields) { + try { + await getDB().execute(field.sql); + } catch (error) { + if (!error.message.includes('Duplicate column name')) { + console.log(`添加激活码表${field.name}字段错误:`, error.message); + } + } + } + + // 为registration_codes表的agent_id字段添加外键约束 + try { + await getDB().execute(` + ALTER TABLE registration_codes + ADD CONSTRAINT fk_registration_codes_agent_id + FOREIGN KEY (agent_id) REFERENCES users(id) ON DELETE SET NULL + `); + } catch (error) { + if (!error.message.includes('Duplicate foreign key constraint name')) { + console.log('添加激活码表agent_id外键约束错误:', error.message); + } + } + + // 注意:MySQL不支持带WHERE条件的唯一索引 + // 区域激活代理的唯一性通过应用层验证来确保 + // 每个区域只能有一个激活状态的代理,这在代理申请、审核和启用时都会进行验证 + + // 创建代理提现记录表 + try { + await getDB().execute(` + CREATE TABLE IF NOT EXISTS agent_withdrawals ( + id INT AUTO_INCREMENT PRIMARY KEY, + agent_id INT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型', + bank_name VARCHAR(100) COMMENT '银行名称', + account_number VARCHAR(50) COMMENT '账号/银行账号', + account_holder VARCHAR(100) COMMENT '持有人姓名', + qr_code_url VARCHAR(255) COMMENT '收款码图片URL', + status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending', + apply_note TEXT, + admin_note TEXT, + processed_by INT NULL, + processed_at TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)', + FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE, + FOREIGN KEY (processed_by) REFERENCES users(id) ON DELETE SET NULL + ) + `); + console.log('代理提现记录表创建成功'); + } catch (error) { + if (!error.message.includes('Table') || !error.message.includes('already exists')) { + console.log('创建代理提现记录表错误:', error.message); + } + } +} + +/** + * 创建默认数据 + */ +async function createDefaultData() { + // 创建默认管理员账号 + const defaultPassword = await bcrypt.hash('admin123', 10); + + try { + await getDB().execute(` + INSERT IGNORE INTO users (username, phone, password, role) + VALUES ('admin', '13800000000', ?, 'admin') + `, [defaultPassword]); + console.log('默认管理员账号已创建: 用户名: admin, 密码: admin123'); + } catch (error) { + console.log('默认管理员账号已存在或创建失败:', error.message); + } + + // 创建多个系统公户账号 + const publicPassword = await bcrypt.hash('public123', 10); + const systemAccounts = [ + { username: 'merchant_001', phone: '13800000001', real_name: '优选商户' }, + { username: 'merchant_002', phone: '13800000002', real_name: '品质商家' }, + { username: 'merchant_003', phone: '13800000003', real_name: '信誉商户' }, + { username: 'merchant_004', phone: '13800000004', real_name: '金牌商家' }, + { username: 'merchant_005', phone: '13800000005', real_name: '钻石商户' } + ]; + + for (const account of systemAccounts) { + try { + const [result] = await getDB().execute(` + INSERT IGNORE INTO users (username, phone, password, role, real_name, is_system_account) + VALUES (?, ?, ?, 'user', ?, TRUE) + `, [account.username, account.phone, publicPassword, account.real_name]); + + if (result.affectedRows > 0) { + // 为新创建的系统账户设置初始余额 + await getDB().execute(` + UPDATE users SET balance = 0.00 WHERE id = ? + `, [result.insertId]); + console.log(`系统账户已创建: ${account.real_name} (${account.username})`); + } else { + // 确保现有系统账户的is_system_account字段正确设置 + await getDB().execute(` + UPDATE users SET is_system_account = TRUE WHERE username = ? + `, [account.username]); + } + } catch (error) { + console.log(`系统账户${account.username}已存在或创建失败:`, error.message); + } + } + + // 初始化浙江省区域数据 + await initializeZhejiangRegions(); + + // 初始化默认地址标签 + await initializeDefaultAddressLabels(); + + // 初始化全国省市区数据 + await initializeChinaRegions(); +} + +/** + * 初始化浙江省区域数据 + */ +async function initializeZhejiangRegions() { + const zhejiangRegions = [ + // 杭州市 + { city: '杭州市', district: '上城区', code: 'HZ_SC' }, + { city: '杭州市', district: '拱墅区', code: 'HZ_GS' }, + { city: '杭州市', district: '西湖区', code: 'HZ_XH' }, + { city: '杭州市', district: '滨江区', code: 'HZ_BJ' }, + { city: '杭州市', district: '萧山区', code: 'HZ_XS' }, + { city: '杭州市', district: '余杭区', code: 'HZ_YH' }, + { city: '杭州市', district: '临平区', code: 'HZ_LP' }, + { city: '杭州市', district: '钱塘区', code: 'HZ_QT' }, + { city: '杭州市', district: '富阳区', code: 'HZ_FY' }, + { city: '杭州市', district: '临安区', code: 'HZ_LA' }, + { city: '杭州市', district: '桐庐县', code: 'HZ_TL' }, + { city: '杭州市', district: '淳安县', code: 'HZ_CA' }, + { city: '杭州市', district: '建德市', code: 'HZ_JD' }, + // 宁波市 + { city: '宁波市', district: '海曙区', code: 'NB_HS' }, + { city: '宁波市', district: '江北区', code: 'NB_JB' }, + { city: '宁波市', district: '北仑区', code: 'NB_BL' }, + { city: '宁波市', district: '镇海区', code: 'NB_ZH' }, + { city: '宁波市', district: '鄞州区', code: 'NB_YZ' }, + { city: '宁波市', district: '奉化区', code: 'NB_FH' }, + { city: '宁波市', district: '象山县', code: 'NB_XS' }, + { city: '宁波市', district: '宁海县', code: 'NB_NH' }, + { city: '宁波市', district: '余姚市', code: 'NB_YY' }, + { city: '宁波市', district: '慈溪市', code: 'NB_CX' }, + // 温州市 + { city: '温州市', district: '鹿城区', code: 'WZ_LC' }, + { city: '温州市', district: '龙湾区', code: 'WZ_LW' }, + { city: '温州市', district: '瓯海区', code: 'WZ_OH' }, + { city: '温州市', district: '洞头区', code: 'WZ_DT' }, + { city: '温州市', district: '永嘉县', code: 'WZ_YJ' }, + { city: '温州市', district: '平阳县', code: 'WZ_PY' }, + { city: '温州市', district: '苍南县', code: 'WZ_CN' }, + { city: '温州市', district: '文成县', code: 'WZ_WC' }, + { city: '温州市', district: '泰顺县', code: 'WZ_TS' }, + { city: '温州市', district: '瑞安市', code: 'WZ_RA' }, + { city: '温州市', district: '乐清市', code: 'WZ_LQ' }, + // 嘉兴市 + { city: '嘉兴市', district: '南湖区', code: 'JX_NH' }, + { city: '嘉兴市', district: '秀洲区', code: 'JX_XZ' }, + { city: '嘉兴市', district: '嘉善县', code: 'JX_JS' }, + { city: '嘉兴市', district: '海盐县', code: 'JX_HY' }, + { city: '嘉兴市', district: '海宁市', code: 'JX_HN' }, + { city: '嘉兴市', district: '平湖市', code: 'JX_PH' }, + { city: '嘉兴市', district: '桐乡市', code: 'JX_TX' }, + // 湖州市 + { city: '湖州市', district: '吴兴区', code: 'HuZ_WX' }, + { city: '湖州市', district: '南浔区', code: 'HuZ_NX' }, + { city: '湖州市', district: '德清县', code: 'HuZ_DQ' }, + { city: '湖州市', district: '长兴县', code: 'HuZ_CX' }, + { city: '湖州市', district: '安吉县', code: 'HuZ_AJ' }, + // 绍兴市 + { city: '绍兴市', district: '越城区', code: 'SX_YC' }, + { city: '绍兴市', district: '柯桥区', code: 'SX_KQ' }, + { city: '绍兴市', district: '上虞区', code: 'SX_SY' }, + { city: '绍兴市', district: '新昌县', code: 'SX_XC' }, + { city: '绍兴市', district: '诸暨市', code: 'SX_ZJ' }, + { city: '绍兴市', district: '嵊州市', code: 'SX_SZ' }, + // 金华市 + { city: '金华市', district: '婺城区', code: 'JH_WC' }, + { city: '金华市', district: '金东区', code: 'JH_JD' }, + { city: '金华市', district: '武义县', code: 'JH_WY' }, + { city: '金华市', district: '浦江县', code: 'JH_PJ' }, + { city: '金华市', district: '磐安县', code: 'JH_PA' }, + { city: '金华市', district: '兰溪市', code: 'JH_LX' }, + { city: '金华市', district: '义乌市', code: 'JH_YW' }, + { city: '金华市', district: '东阳市', code: 'JH_DY' }, + { city: '金华市', district: '永康市', code: 'JH_YK' }, + // 衢州市 + { city: '衢州市', district: '柯城区', code: 'QZ_KC' }, + { city: '衢州市', district: '衢江区', code: 'QZ_QJ' }, + { city: '衢州市', district: '常山县', code: 'QZ_CS' }, + { city: '衢州市', district: '开化县', code: 'QZ_KH' }, + { city: '衢州市', district: '龙游县', code: 'QZ_LY' }, + { city: '衢州市', district: '江山市', code: 'QZ_JS' }, + // 舟山市 + { city: '舟山市', district: '定海区', code: 'ZS_DH' }, + { city: '舟山市', district: '普陀区', code: 'ZS_PT' }, + { city: '舟山市', district: '岱山县', code: 'ZS_DS' }, + { city: '舟山市', district: '嵊泗县', code: 'ZS_SS' }, + // 台州市 + { city: '台州市', district: '椒江区', code: 'TZ_JJ' }, + { city: '台州市', district: '黄岩区', code: 'TZ_HY' }, + { city: '台州市', district: '路桥区', code: 'TZ_LQ' }, + { city: '台州市', district: '三门县', code: 'TZ_SM' }, + { city: '台州市', district: '天台县', code: 'TZ_TT' }, + { city: '台州市', district: '仙居县', code: 'TZ_XJ' }, + { city: '台州市', district: '温岭市', code: 'TZ_WL' }, + { city: '台州市', district: '临海市', code: 'TZ_LH' }, + { city: '台州市', district: '玉环市', code: 'TZ_YH' }, + // 丽水市 + { city: '丽水市', district: '莲都区', code: 'LS_LD' }, + { city: '丽水市', district: '青田县', code: 'LS_QT' }, + { city: '丽水市', district: '缙云县', code: 'LS_JY' }, + { city: '丽水市', district: '遂昌县', code: 'LS_SC' }, + { city: '丽水市', district: '松阳县', code: 'LS_SY' }, + { city: '丽水市', district: '云和县', code: 'LS_YH' }, + { city: '丽水市', district: '庆元县', code: 'LS_QY' }, + { city: '丽水市', district: '景宁县', code: 'LS_JN' }, + { city: '丽水市', district: '龙泉市', code: 'LS_LQ' } + ]; + + // 批量插入区域数据 + for (const region of zhejiangRegions) { + try { + await getDB().execute( + 'INSERT IGNORE INTO zhejiang_regions (city_name, district_name, region_code) VALUES (?, ?, ?)', + [region.city, region.district, region.code] + ); + } catch (error) { + console.log(`插入区域数据失败: ${region.city} ${region.district}`, error.message); + } + } +} + +/** + * 初始化默认地址标签 + */ +async function initializeDefaultAddressLabels() { + const defaultLabels = [ + { name: '家', color: '#52c41a' }, + { name: '公司', color: '#1890ff' }, + { name: '学校', color: '#722ed1' } + ]; + + for (const label of defaultLabels) { + try { + await getDB().execute(` + INSERT IGNORE INTO address_labels (name, color, user_id, is_system) + VALUES (?, ?, NULL, TRUE) + `, [label.name, label.color]); + } catch (error) { + console.log(`默认标签${label.name}创建失败:`, error.message); + } + } + console.log('默认地址标签初始化完成'); +} + +/** + * 初始化全国省市区数据 + */ +async function initializeChinaRegions() { + const regions = [ + // 省级 + { code: '110000', name: '北京市', parent_code: null, level: 1 }, + { code: '120000', name: '天津市', parent_code: null, level: 1 }, + { code: '130000', name: '河北省', parent_code: null, level: 1 }, + { code: '140000', name: '山西省', parent_code: null, level: 1 }, + { code: '150000', name: '内蒙古自治区', parent_code: null, level: 1 }, + { code: '210000', name: '辽宁省', parent_code: null, level: 1 }, + { code: '220000', name: '吉林省', parent_code: null, level: 1 }, + { code: '230000', name: '黑龙江省', parent_code: null, level: 1 }, + { code: '310000', name: '上海市', parent_code: null, level: 1 }, + { code: '320000', name: '江苏省', parent_code: null, level: 1 }, + { code: '330000', name: '浙江省', parent_code: null, level: 1 }, + { code: '340000', name: '安徽省', parent_code: null, level: 1 }, + { code: '350000', name: '福建省', parent_code: null, level: 1 }, + { code: '360000', name: '江西省', parent_code: null, level: 1 }, + { code: '370000', name: '山东省', parent_code: null, level: 1 }, + { code: '410000', name: '河南省', parent_code: null, level: 1 }, + { code: '420000', name: '湖北省', parent_code: null, level: 1 }, + { code: '430000', name: '湖南省', parent_code: null, level: 1 }, + { code: '440000', name: '广东省', parent_code: null, level: 1 }, + { code: '450000', name: '广西壮族自治区', parent_code: null, level: 1 }, + { code: '460000', name: '海南省', parent_code: null, level: 1 }, + { code: '500000', name: '重庆市', parent_code: null, level: 1 }, + { code: '510000', name: '四川省', parent_code: null, level: 1 }, + { code: '520000', name: '贵州省', parent_code: null, level: 1 }, + { code: '530000', name: '云南省', parent_code: null, level: 1 }, + { code: '540000', name: '西藏自治区', parent_code: null, level: 1 }, + { code: '610000', name: '陕西省', parent_code: null, level: 1 }, + { code: '620000', name: '甘肃省', parent_code: null, level: 1 }, + { code: '630000', name: '青海省', parent_code: null, level: 1 }, + { code: '640000', name: '宁夏回族自治区', parent_code: null, level: 1 }, + { code: '650000', name: '新疆维吾尔自治区', parent_code: null, level: 1 }, + + // 浙江省市级 + { code: '330100', name: '杭州市', parent_code: '330000', level: 2 }, + { code: '330200', name: '宁波市', parent_code: '330000', level: 2 }, + { code: '330300', name: '温州市', parent_code: '330000', level: 2 }, + { code: '330400', name: '嘉兴市', parent_code: '330000', level: 2 }, + { code: '330500', name: '湖州市', parent_code: '330000', level: 2 }, + { code: '330600', name: '绍兴市', parent_code: '330000', level: 2 }, + { code: '330700', name: '金华市', parent_code: '330000', level: 2 }, + { code: '330800', name: '衢州市', parent_code: '330000', level: 2 }, + { code: '330900', name: '舟山市', parent_code: '330000', level: 2 }, + { code: '331000', name: '台州市', parent_code: '330000', level: 2 }, + { code: '331100', name: '丽水市', parent_code: '330000', level: 2 }, + + // 杭州市区级 + { code: '330102', name: '上城区', parent_code: '330100', level: 3 }, + { code: '330105', name: '拱墅区', parent_code: '330100', level: 3 }, + { code: '330106', name: '西湖区', parent_code: '330100', level: 3 }, + { code: '330108', name: '滨江区', parent_code: '330100', level: 3 }, + { code: '330109', name: '萧山区', parent_code: '330100', level: 3 }, + { code: '330110', name: '余杭区', parent_code: '330100', level: 3 }, + { code: '330111', name: '富阳区', parent_code: '330100', level: 3 }, + { code: '330112', name: '临安区', parent_code: '330100', level: 3 }, + { code: '330113', name: '临平区', parent_code: '330100', level: 3 }, + { code: '330114', name: '钱塘区', parent_code: '330100', level: 3 }, + { code: '330122', name: '桐庐县', parent_code: '330100', level: 3 }, + { code: '330127', name: '淳安县', parent_code: '330100', level: 3 }, + { code: '330182', name: '建德市', parent_code: '330100', level: 3 } + ]; + + for (const region of regions) { + try { + await getDB().execute(` + INSERT IGNORE INTO china_regions (code, name, parent_code, level) + VALUES (?, ?, ?, ?) + `, [region.code, region.name, region.parent_code, region.level]); + } catch (error) { + console.log(`区域${region.name}创建失败:`, error.message); + } + } + console.log('全国省市区数据初始化完成'); +} + +module.exports = { + initDatabase, + createTables, + addMissingFields, + createDefaultData, + initializeZhejiangRegions, + initializeDefaultAddressLabels, + initializeChinaRegions +}; \ No newline at end of file diff --git a/config/dbv2.js b/config/dbv2.js new file mode 100644 index 0000000..e7e253b --- /dev/null +++ b/config/dbv2.js @@ -0,0 +1,363 @@ +class QueryBuilder { + constructor() { + this.conditions = {}; + this.limit = null; + this.offset = null; + this.groupBy = null; + } + + where(condition, ...params) { + this.conditions[condition] = params; + return this; + } + + setLimit(limit) { + this.limit = limit; + return this; + } + + setOffset(offset) { + this.offset = offset; + return this; + } + + setGroupBy(groupBy) { + this.groupBy = groupBy; + return this; + } + + sqdata(sql, params) { + return new Promise((resolve, reject) => { + global.sqlReq.query(sql, params, (err, result) => { + if (err) { + reject(err); + } + resolve(result); + }); + }); + } + + getParams() { + return Object.values(this.conditions).flat(); + } + + buildConditions() { + return Object.keys(this.conditions).map(condition => `${condition}`).join(' AND '); + } +} + +class SelectBuilder extends QueryBuilder { + constructor() { + super(); + this.selectFields = []; + this.tables = []; + this.orderByField = ''; + this.orderByDirection = 'ASC'; + this.subQueries = []; // 用于存储子查询 + this.unions = []; // 存储UNION查询 + } + // 添加UNION查询 + union(queryBuilder, type = 'UNION') { + this.unions.push({queryBuilder, type}); + return this; + } + + // 添加UNION ALL查询 + unionAll(queryBuilder) { + this.union(queryBuilder, 'UNION ALL'); + return this; + } + + // 构建主查询部分(不含ORDER BY/LIMIT/OFFSET) + buildMainQuery() { + const subQuerySQL = this.subQueries.map(({alias, subQuery}) => `(${subQuery}) AS ${alias}`); + const selectClause = this.selectFields.concat(subQuerySQL).join(', '); + + let sql = `SELECT ${selectClause} + FROM ${this.tables.join(' ')}`; + + const conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + + if (this.groupBy) { + sql += ` GROUP BY ${this.groupBy}`; + } + + const params = this.getParams(); + return {sql, params}; + } + + // 供UNION查询调用的构建方法 + buildForUnion() { + return this.buildMainQuery(); + } + + select(fields) { + this.selectFields = fields.split(',').map(field => field.trim()); + return this; + } + + // 添加子查询 + addSubQuery(alias, subQuery) { + this.subQueries.push({alias, subQuery}); + return this; + } + + whereLike(fields, keyword) { + const likeConditions = fields.map(field => `${field} LIKE ?`).join(' OR '); + this.conditions[likeConditions] = fields.map(() => `%${keyword}%`); + return this; + } + + from(table) { + this.tables.push(table); + return this; + } + + leftJoin(table, condition) { + this.tables.push(`LEFT JOIN ${table} ON ${condition}`); + return this; + } + + orderBy(field, direction = 'ASC') { + this.orderByField = field; + this.orderByDirection = direction.toUpperCase(); + return this; + } + + paginate(page, pageSize) { + if (page <= 0 || pageSize <= 0) { + throw new Error('分页参数必须大于0'); + } + this.limit = pageSize; + this.offset = (page - 1) * pageSize; + return this; + } + + async chidBuild() { + + let sql = `SELECT ${this.selectFields.join(', ')} + FROM ${this.tables.join(' ')}`; + let conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + if (this.orderByField) { + sql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`; + } + if (this.limit !== null) { + sql += ` LIMIT ${this.limit}`; + } + if (this.offset !== null) { + sql += ` OFFSET ${this.offset}`; + } + return sql; + } + + async build() { + const main = this.buildMainQuery(); + let fullSql = `(${main.sql})`; + const allParams = [...main.params]; + + // 处理UNION部分 + for (const union of this.unions) { + const unionBuilder = union.queryBuilder; + if (!(unionBuilder instanceof SelectBuilder)) { + throw new Error('UNION query must be a SelectBuilder instance'); + } + const unionResult = unionBuilder.buildForUnion(); + fullSql += ` ${union.type} (${unionResult.sql})`; + allParams.push(...unionResult.params); + } + + // 添加ORDER BY、LIMIT、OFFSET + if (this.orderByField) { + fullSql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`; + } + if (this.limit !== null) { + fullSql += ` LIMIT ${this.limit}`; + } + if (this.offset !== null) { + fullSql += ` OFFSET ${this.offset}`; + } + console.log(fullSql,allParams); + return await this.sqdata(fullSql, allParams); + } +} + + +class UpdateBuilder extends QueryBuilder { + constructor() { + super(); + this.table = ''; + this.updateFields = {}; + } + + update(table) { + this.table = table; + return this; + } + + set(field, value) { + if (value && value.increment && typeof value === 'object' ) { + this.updateFields[field] = {increment: value.increment}; + } else { + this.updateFields[field] = value; + } + return this; + } + + async build() { + let sql = `UPDATE ${this.table} + SET `; + let updateClauses = Object.keys(this.updateFields).map(field => { + const value = this.updateFields[field]; + if (value && value.increment && typeof value === 'object' ) { + return `${field} = ${field} + ?`; + } + return `${field} = ?`; + }).join(', '); + + sql += updateClauses; + + let conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + // 处理参数,确保自增字段也传入增量值 + const params = [ + ...Object.values(this.updateFields).map(value => + (value && value.increment && typeof value === 'object' ) ? value.increment : value + ), + ...this.getParams() + ]; + return await this.sqdata(sql, params); + } +} + +class InsertBuilder extends QueryBuilder { + constructor() { + super(); + this.table = ''; + this.insertValues = []; + this.updateValues = {}; + } + + insertInto(table) { + this.table = table; + return this; + } + + // 仍然保留单条记录的插入 + values(values) { + if (Array.isArray(values)) { + this.insertValues = values; + } else { + this.insertValues = [values]; // 将单条记录包装成数组 + } + return this; + } + + // 新增方法,支持一次插入多条记录 + valuesMultiple(records) { + if (!Array.isArray(records) || records.length === 0) { + throw new Error('Values must be a non-empty array'); + } + + // 确保每一条记录都是对象 + records.forEach(record => { + if (typeof record !== 'object') { + throw new Error('Each record must be an object'); + } + }); + + this.insertValues = records; + return this; + } + + // 新增 upsert 方法,支持更新或插入 + upsert(values, updateFields) { + // values: 要插入的记录 + // updateFields: 如果记录存在时,需要更新的字段 + if (!Array.isArray(values) || values.length === 0) { + throw new Error('Values must be a non-empty array'); + } + + // 检查每条记录是否是对象 + values.forEach(record => { + if (typeof record !== 'object') { + throw new Error('Each record must be an object'); + } + }); + + this.insertValues = values; + this.updateValues = updateFields || {}; + return this; + } + + async build() { + if (this.insertValues.length === 0) { + throw new Error("No values to insert"); + } + + // 获取表单列名,假设所有记录有相同的字段 + const columns = Object.keys(this.insertValues[0]); + + // 构建 VALUES 子句,支持批量插入 + const valuePlaceholders = this.insertValues.map(() => + `(${columns.map(() => '?').join(', ')})` + ).join(', '); + + // 展平所有的插入值 + const params = this.insertValues.flatMap(record => + columns.map(column => record[column]) + ); + + // 如果有 updateFields,构建 ON DUPLICATE KEY UPDATE 子句 + let updateClause = ''; + if (Object.keys(this.updateValues).length > 0) { + updateClause = ' ON DUPLICATE KEY UPDATE ' + + Object.keys(this.updateValues).map(field => { + return `${field} = VALUES(${field})`; + }).join(', '); + } + + // 生成 SQL 语句 + const sql = `INSERT INTO ${this.table} (${columns.join(', ')}) + VALUES ${valuePlaceholders} ${updateClause}`; + // 执行查询 + return await this.sqdata(sql, params); + } +} + + +class DeleteBuilder extends QueryBuilder { + constructor() { + super(); + this.table = ''; + } + + deleteFrom(table) { + this.table = table; + return this; + } + + async build() { + let sql = `DELETE + FROM ${this.table}`; + let conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + return await this.sqdata(sql, this.getParams()); + } +} + +module.exports = { + SelectBuilder, + UpdateBuilder, + InsertBuilder, + DeleteBuilder, +}; diff --git a/config/logger.js b/config/logger.js new file mode 100644 index 0000000..45cda51 --- /dev/null +++ b/config/logger.js @@ -0,0 +1,73 @@ +const winston = require('winston'); +const path = require('path'); + +// 创建日志目录 +const logDir = path.join(__dirname, '../logs'); + +// 日志格式配置 +const logFormat = winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + winston.format.errors({ stack: true }), + winston.format.json() +); + +// 控制台日志格式 +const consoleFormat = winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`; + }) +); + +// 创建logger实例 +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: logFormat, + defaultMeta: { service: 'integrated-system' }, + transports: [ + // 错误日志文件 + new winston.transports.File({ + filename: path.join(logDir, 'error.log'), + level: 'error', + maxsize: 5242880, // 5MB + maxFiles: 5 + }), + // 所有日志文件 + new winston.transports.File({ + filename: path.join(logDir, 'combined.log'), + maxsize: 5242880, // 5MB + maxFiles: 5 + }) + ] +}); + +// 开发环境添加控制台输出 +if (process.env.NODE_ENV !== 'production') { + logger.add(new winston.transports.Console({ + format: consoleFormat + })); +} + +// 审计日志记录器 +const auditLogger = winston.createLogger({ + level: 'info', + format: logFormat, + defaultMeta: { service: 'audit' }, + transports: [ + new winston.transports.File({ + filename: path.join(logDir, 'audit.log'), + maxsize: 5242880, // 5MB + maxFiles: 10 + }) + ] +}); + +module.exports = { + logger, + auditLogger +}; \ No newline at end of file diff --git a/config/minio.js b/config/minio.js new file mode 100644 index 0000000..f479bea --- /dev/null +++ b/config/minio.js @@ -0,0 +1,97 @@ +const Minio = require('minio'); +require('dotenv').config(); + +/** + * MinIO 配置 + * 用于对象存储服务配置 + */ +const minioConfig = { + // MinIO 服务器配置 + endPoint: process.env.MINIO_ENDPOINT || 'localhost', + port: parseInt(process.env.MINIO_PORT) || 9000, + useSSL: process.env.MINIO_USE_SSL === 'true' || false, + accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin', + secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin', + + // 存储桶配置 + buckets: { + uploads: process.env.MINIO_BUCKET_UPLOADS || 'uploads', + avatars: process.env.MINIO_BUCKET_AVATARS || 'avatars', + products: process.env.MINIO_BUCKET_PRODUCTS || 'products', + documents: process.env.MINIO_BUCKET_DOCUMENTS || 'documents' + }, + + // 文件访问配置 + publicUrl: process.env.MINIO_PUBLIC_URL || `http://localhost:9000` +}; + +/** + * 创建 MinIO 客户端实例 + */ +const createMinioClient = () => { + return new Minio.Client({ + endPoint: minioConfig.endPoint, + port: minioConfig.port, + useSSL: minioConfig.useSSL, + accessKey: minioConfig.accessKey, + secretKey: minioConfig.secretKey + }); +}; + +/** + * 初始化存储桶 + * 确保所有需要的存储桶都存在 + */ +const initializeBuckets = async () => { + const minioClient = createMinioClient(); + + try { + // 检查并创建存储桶 + for (const [key, bucketName] of Object.entries(minioConfig.buckets)) { + const exists = await minioClient.bucketExists(bucketName); + if (!exists) { + await minioClient.makeBucket(bucketName, 'us-east-1'); + console.log(`✅ 存储桶 '${bucketName}' 创建成功`); + + // 设置存储桶策略为公开读取(可选) + const policy = { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { AWS: ['*'] }, + Action: ['s3:GetObject'], + Resource: [`arn:aws:s3:::${bucketName}/*`] + } + ] + }; + + try { + await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy)); + console.log(`✅ 存储桶 '${bucketName}' 策略设置成功`); + } catch (policyError) { + console.warn(`⚠️ 存储桶 '${bucketName}' 策略设置失败:`, policyError.message); + } + } else { + console.log(`✅ 存储桶 '${bucketName}' 已存在`); + } + } + } catch (error) { + console.error('❌ 初始化存储桶失败:', error); + throw error; + } +}; + +/** + * 获取文件的公开访问URL + */ +const getPublicUrl = (bucketName, objectName) => { + return `${minioConfig.publicUrl}/${bucketName}/${objectName}`; +}; + +module.exports = { + minioConfig, + createMinioClient, + initializeBuckets, + getPublicUrl +}; \ No newline at end of file diff --git a/config/wechatPay.js b/config/wechatPay.js new file mode 100644 index 0000000..16b61f9 --- /dev/null +++ b/config/wechatPay.js @@ -0,0 +1,24 @@ +// 微信支付配置 +module.exports = { + // 微信支付配置 + wechatPay: { + appId: process.env.WECHAT_APP_ID || '', // 微信公众号AppID + mchId: process.env.WECHAT_MCH_ID || '', // 商户号 + apiKey: process.env.WECHAT_API_KEY || '', // API密钥 + apiV3Key: process.env.WECHAT_API_V3_KEY || '', // APIv3密钥 + notifyUrl: process.env.WECHAT_NOTIFY_URL || 'https://your-domain.com/api/wechat/notify', // 支付回调地址 + + // 证书路径(生产环境需要配置) + certPath: process.env.WECHAT_CERT_PATH || '', + keyPath: process.env.WECHAT_KEY_PATH || '', + + // 支付相关配置 + tradeType: { + h5: 'MWEB', // H5支付 + jsapi: 'JSAPI' // 公众号支付 + }, + + // 注册费用配置(单位:分) + registrationFee: 100 // 1元注册费 + } +}; \ No newline at end of file diff --git a/config/withdrawal-init.sql b/config/withdrawal-init.sql new file mode 100644 index 0000000..a42c3fe --- /dev/null +++ b/config/withdrawal-init.sql @@ -0,0 +1,34 @@ +-- 创建代理提现记录表 +CREATE TABLE IF NOT EXISTS agent_withdrawals ( + id INT AUTO_INCREMENT PRIMARY KEY, + agent_id INT NOT NULL, + amount DECIMAL(10,2) NOT NULL, + payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型', + bank_name VARCHAR(100) COMMENT '银行名称', + account_number VARCHAR(50) COMMENT '账号/银行账号', + account_holder VARCHAR(100) COMMENT '持有人姓名', + qr_code_url VARCHAR(255) COMMENT '收款码图片URL', + status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending', + apply_note TEXT, + admin_note TEXT, + processed_by INT NULL, + processed_at TIMESTAMP NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE, + FOREIGN KEY (processed_by) REFERENCES users(id) ON DELETE SET NULL, + -- 兼容旧字段 + bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)' +); + +-- 为regional_agents表添加提现相关字段 +ALTER TABLE regional_agents ADD COLUMN withdrawn_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '已提现金额'; +ALTER TABLE regional_agents ADD COLUMN pending_withdrawal DECIMAL(10,2) DEFAULT 0.00 COMMENT '待审核提现金额'; +ALTER TABLE regional_agents ADD COLUMN payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型'; +ALTER TABLE regional_agents ADD COLUMN bank_name VARCHAR(100) COMMENT '银行名称'; +ALTER TABLE regional_agents ADD COLUMN account_number VARCHAR(50) COMMENT '账号/银行账号'; +ALTER TABLE regional_agents ADD COLUMN account_holder VARCHAR(100) COMMENT '持有人姓名'; +ALTER TABLE regional_agents ADD COLUMN qr_code_url VARCHAR(255) COMMENT '收款码图片URL'; + +-- 兼容旧字段(可选,用于数据迁移) +ALTER TABLE regional_agents ADD COLUMN bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)'; \ No newline at end of file diff --git a/database.js b/database.js new file mode 100644 index 0000000..b4005d6 --- /dev/null +++ b/database.js @@ -0,0 +1,158 @@ +const mysql = require('mysql2/promise'); + +// 数据库配置 +const dbConfig = { + // host: process.env.DB_HOST || '114.55.111.44', + // user: process.env.DB_USER || 'maov2', + // password: process.env.DB_PASSWORD || '5fYhw8z6T62b7heS', + // database: process.env.DB_NAME || 'maov2', + host: '114.55.111.44', + user: 'test_mao', + password: 'nK2mPbWriBp25BRd', + database: 'test_mao', + charset: 'utf8mb4', + // 连接池配置 + connectionLimit: 20, // 连接池最大连接数 + queueLimit: 0, // 排队等待连接的最大数量,0表示无限制 + // 连接超时配置 + // acquireTimeout: 60000, // 获取连接超时时间 60秒 + // timeout: 60000, // 查询超时时间 60秒 + // reconnect: true, // 自动重连 + // 连接保活配置 + multipleStatements: true, + // 空闲连接超时配置 + idleTimeout: 300000, // 5分钟空闲超时 + // maxLifetime: 1800000, // 30分钟最大生命周期 + // 连接保活设置 + keepAliveInitialDelay: 0, // 开始保活探测前的延迟时间 + enableKeepAlive: true, // 启用TCP保活 + // 添加类型转换配置 + typeCast: function (field, next) { + if (field.type === 'TINY' && field.length === 1) { + return (field.string() === '1'); // 1 = true, 0 = false + } + return next(); + }, + // 确保参数正确处理 + supportBigNumbers: true, + bigNumberStrings: false +}; + +// 创建数据库连接池 +let pool; + +/** + * 初始化数据库连接池 + * @returns {Promise} 数据库连接池 + */ +async function initDB() { + if (!pool) { + try { + pool = mysql.createPool(dbConfig); + + // 添加连接池事件监听 + pool.on('connection', function (connection) { + console.log('新的数据库连接建立:', connection.threadId); + }); + + // 注释掉频繁的连接获取和释放日志,避免日志过多 + // pool.on('acquire', function (connection) { + // console.log('连接池获取连接:', connection.threadId); + // }); + + // pool.on('release', function (connection) { + // console.log('连接池释放连接:', connection.threadId); + // }); + + pool.on('error', function(err) { + console.error('数据库连接池错误:', err); + if(err.code === 'PROTOCOL_CONNECTION_LOST') { + console.log('数据库连接丢失,尝试重新连接...'); + } else if(err.code === 'ECONNRESET') { + console.log('数据库连接被重置,尝试重新连接...'); + } else if(err.code === 'ETIMEDOUT') { + console.log('数据库连接超时,尝试重新连接...'); + } + }); + + // 测试连接 + const connection = await pool.getConnection(); + console.log('数据库连接池初始化成功'); + connection.release(); + + } catch (error) { + console.error('数据库连接池初始化失败:', error); + throw error; + } + } + return pool; +} + +/** + * 获取数据库连接池 + * @returns {mysql.Pool} 数据库连接池 + */ +function getDB() { + if (!pool) { + throw new Error('数据库连接池未初始化,请先调用 initDB()'); + } + return pool; +} + +/** + * 执行数据库查询(带重试机制) + * @param {string} sql SQL查询语句 + * @param {Array} params 查询参数 + * @param {number} retries 重试次数 + * @returns {Promise} 查询结果 + */ +async function executeQuery(sql, params = [], retries = 3) { + for (let i = 0; i < retries; i++) { + try { + const connection = await pool.getConnection(); + try { + const [results] = await connection.execute(sql, params); + connection.release(); + return results; + } catch (error) { + connection.release(); + throw error; + } + } catch (error) { + console.error(`数据库查询失败 (尝试 ${i + 1}/${retries}):`, error.message); + + if (i === retries - 1) { + throw error; + } + + // 如果是连接相关错误,等待后重试 + if (error.code === 'PROTOCOL_CONNECTION_LOST' || + error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT') { + console.log(`等待 ${(i + 1) * 1000}ms 后重试...`); + await new Promise(resolve => setTimeout(resolve, (i + 1) * 1000)); + } else { + throw error; + } + } + } +} + +/** + * 关闭数据库连接池 + */ +async function closeDB() { + if (pool) { + await pool.end(); + pool = null; + console.log('数据库连接池已关闭'); + } +} + +module.exports = { + initDB, + getDB, + closeDB, + executeQuery, + dbConfig +}; \ No newline at end of file diff --git a/logs/audit.log b/logs/audit.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/combined.log b/logs/combined.log new file mode 100644 index 0000000..48af253 --- /dev/null +++ b/logs/combined.log @@ -0,0 +1,11 @@ +{"ip":"::1","level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:11:57","url":"/agent-admin?ide_webview_request_time=1756883517386","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"duration":"10ms","ip":"::1","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-03 15:11:57","url":"/agent-admin?ide_webview_request_time=1756883517386","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: 路径 /@vite/client 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /@vite/client 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\middleware\\errorHandler.js:107:17)\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9\n at Function.process_params (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:346:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:280:10)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\server.js:72:3\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9","timestamp":"2025-09-03 15:11:57","url":"/@vite/client","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"duration":"2ms","ip":"::1","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-03 15:11:57","url":"/@vite/client","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:13:56","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:13:59","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"duration":"3ms","ip":"::1","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-03 15:13:59","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:13:59","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"duration":"2ms","ip":"::1","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-03 15:13:59","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: 路径 /@vite/client 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /@vite/client 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\middleware\\errorHandler.js:107:17)\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9\n at Function.process_params (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:346:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:280:10)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\server.js:72:3\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9","timestamp":"2025-09-03 15:13:59","url":"/@vite/client","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"duration":"1ms","ip":"::1","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-03 15:13:59","url":"/@vite/client","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} diff --git a/logs/error.log b/logs/error.log new file mode 100644 index 0000000..f48df66 --- /dev/null +++ b/logs/error.log @@ -0,0 +1,6 @@ +{"ip":"::1","level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:11:57","url":"/agent-admin?ide_webview_request_time=1756883517386","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: 路径 /@vite/client 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /@vite/client 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\middleware\\errorHandler.js:107:17)\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9\n at Function.process_params (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:346:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:280:10)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\server.js:72:3\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9","timestamp":"2025-09-03 15:11:57","url":"/@vite/client","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:13:56","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:13:59","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","method":"GET","service":"integrated-system","stack":"Error: ENOENT: no such file or directory, stat 'D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\agent-admin\\dist\\index.html'","timestamp":"2025-09-03 15:13:59","url":"/agent-admin?ide_webview_request_time=1756883636612","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} +{"ip":"::1","level":"error","message":"Error occurred: 路径 /@vite/client 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /@vite/client 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\middleware\\errorHandler.js:107:17)\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9\n at Function.process_params (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:346:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:280:10)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\server.js:72:3\n at Layer.handle [as handle_request] (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\layer.js:95:5)\n at trim_prefix (D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:328:13)\n at D:\\work\\客户\\毛总\\code\\jurong_circle_agent_backend\\node_modules\\express\\lib\\router\\index.js:286:9","timestamp":"2025-09-03 15:13:59","url":"/@vite/client","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Trae/1.100.3 Chrome/132.0.6834.210 Electron/34.5.1 Safari/537.36"} diff --git a/middleware/agentAuth.js b/middleware/agentAuth.js new file mode 100644 index 0000000..e586887 --- /dev/null +++ b/middleware/agentAuth.js @@ -0,0 +1,171 @@ +const jwt = require('jsonwebtoken'); +const { getDB } = require('../database'); +const { logger } = require('../config/logger'); + +// JWT密钥 +const JWT_SECRET = process.env.JWT_SECRET || 'agent_jwt_secret_key_2024'; + +/** + * 代理身份验证中间件 + * 验证JWT token并确保用户是激活的代理 + */ +const agentAuth = async (req, res, next) => { + try { + const token = req.headers.authorization?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ + success: false, + message: '未提供认证令牌' + }); + } + + // 验证JWT token + const decoded = jwt.verify(token, JWT_SECRET); + + // 检查是否是代理角色 + if (decoded.role !== 'agent') { + return res.status(403).json({ + success: false, + message: '权限不足,需要代理身份' + }); + } + + // 查询代理信息确认状态 + const [agents] = await getDB().execute(` + SELECT + ra.id as agent_id, + ra.user_id, + ra.agent_code, + ra.status, + ra.region_id, + u.phone, + u.real_name + FROM regional_agents ra + LEFT JOIN users u ON ra.user_id = u.id + WHERE ra.id = ? + `, [decoded.agentId]); + + if (agents.length === 0) { + return res.status(401).json({ + success: false, + message: '代理账号不存在' + }); + } + + const agent = agents[0]; + + // 检查代理状态 + if (agent.status !== 'active') { + return res.status(403).json({ + success: false, + message: '代理账号已被禁用或未激活' + }); + } + + // 将代理信息添加到请求对象中 + req.agent = { + id: agent.agent_id, + userId: agent.user_id, + agentCode: agent.agent_code, + regionId: agent.region_id, + phone: agent.phone, + realName: agent.real_name + }; + + req.user = { + id: agent.user_id, + role: 'agent' + }; + + next(); + + } catch (error) { + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ + success: false, + message: '无效的认证令牌' + }); + } + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ + success: false, + message: '认证令牌已过期,请重新登录' + }); + } + + logger.error('代理身份验证失败', { + error: error.message, + stack: error.stack, + ip: req.ip + }); + + res.status(500).json({ + success: false, + message: '身份验证失败' + }); + } +}; + +/** + * 可选的代理身份验证中间件 + * 如果提供了token则验证,否则继续执行 + */ +const optionalAgentAuth = async (req, res, next) => { + try { + const token = req.headers.authorization?.replace('Bearer ', ''); + + if (!token) { + return next(); + } + + // 验证JWT token + const decoded = jwt.verify(token, JWT_SECRET); + + if (decoded.role === 'agent') { + // 查询代理信息 + const [agents] = await getDB().execute(` + SELECT + ra.id as agent_id, + ra.user_id, + ra.agent_code, + ra.status, + ra.region_id, + u.phone, + u.real_name + FROM regional_agents ra + LEFT JOIN users u ON ra.user_id = u.id + WHERE ra.id = ? AND ra.status = 'active' + `, [decoded.agentId]); + + if (agents.length > 0) { + const agent = agents[0]; + req.agent = { + id: agent.agent_id, + userId: agent.user_id, + agentCode: agent.agent_code, + regionId: agent.region_id, + phone: agent.phone, + realName: agent.real_name + }; + + req.user = { + id: agent.user_id, + role: 'agent' + }; + } + } + + next(); + + } catch (error) { + // 可选验证失败时不阻止请求继续 + next(); + } +}; + +module.exports = { + agentAuth, + optionalAgentAuth +}; \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..c4005b6 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,112 @@ +const jwt = require('jsonwebtoken'); +const { getDB } = require('../database'); + +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // 在生产环境中应该使用环境变量 + +/** + * 用户认证中间件 + * 验证JWT令牌并检查用户状态(包括是否被拉黑) + */ +const auth = async (req, res, next) => { + try { + const token = req.header('Authorization')?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ success: false, message: '未提供认证令牌' }); + } + + const decoded = jwt.verify(token, JWT_SECRET); + const db = getDB(); + const [users] = await db.execute('SELECT * FROM users WHERE id = ?', [decoded.userId]); + + if (users.length === 0) { + return res.status(401).json({ success: false, message: '用户不存在' }); + } + + const user = users[0]; + + // 检查用户是否被拉黑 + if (user.is_blacklisted) { + return res.status(403).json({ + success: false, + message: '账户已被拉黑,请联系管理员', + code: 'USER_BLACKLISTED' + }); + } + + // 检查支付状态(管理员除外) + if (user.role !== 'admin' && user.payment_status === 'unpaid') { + console.log(11111); + + return res.status(200).json({ + success: false, + message: '您的账户尚未激活,请完成支付后再使用', + code: 'PAYMENT_REQUIRED', + needPayment: true, + userId: user.id + }); + } + + req.user = user; + next(); + } catch (error) { + res.status(401).json({ success: false, message: '无效的认证令牌' }); + } +}; + +// 管理员认证中间件 +const adminAuth = (req, res, next) => { + if (req.user.role !== 'admin') { + return res.status(403).json({ success: false, message: '需要管理员权限' }); + } + next(); +}; + +/** + * 支付认证中间件 + * 只验证JWT令牌和用户状态,不检查支付状态 + * 用于支付相关接口,允许未支付用户创建支付订单 + */ +const paymentAuth = async (req, res, next) => { + try { + const token = req.header('Authorization')?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ success: false, message: '未提供认证令牌' }); + } + + const decoded = jwt.verify(token, JWT_SECRET); + const db = getDB(); + const [users] = await db.execute('SELECT * FROM users WHERE id = ?', [decoded.userId]); + + if (users.length === 0) { + return res.status(401).json({ success: false, message: '用户不存在' }); + } + + const user = users[0]; + + // 检查用户是否被拉黑 + if (user.is_blacklisted) { + return res.status(403).json({ + success: false, + message: '账户已被拉黑,请联系管理员', + code: 'USER_BLACKLISTED' + }); + } + + // 注意:这里不检查支付状态,允许未支付用户创建支付订单 + req.user = user; + next(); + } catch (error) { + console.error('支付认证失败:', error); + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ success: false, message: '无效的认证令牌' }); + } + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ success: false, message: '认证令牌已过期' }); + } + return res.status(500).json({ success: false, message: '认证失败' }); + } +}; + +module.exports = { auth, adminAuth, paymentAuth, JWT_SECRET }; \ No newline at end of file diff --git a/middleware/errorHandler.js b/middleware/errorHandler.js new file mode 100644 index 0000000..eb26f22 --- /dev/null +++ b/middleware/errorHandler.js @@ -0,0 +1,129 @@ +const { logger } = require('../config/logger'); +const { ERROR_CODES, HTTP_STATUS } = require('../config/constants'); + +// 全局错误处理中间件 +const errorHandler = (err, req, res, next) => { + let error = { ...err }; + error.message = err.message; + + // 记录错误日志 + logger.error('Error occurred:', { + message: err.message, + stack: err.stack, + url: req.originalUrl, + method: req.method, + ip: req.ip, + userAgent: req.get('User-Agent'), + userId: req.user?.id + }); + + // MySQL错误处理 + if (err.code) { + switch (err.code) { + case 'ER_DUP_ENTRY': + error.message = '数据已存在'; + error.statusCode = HTTP_STATUS.CONFLICT; + error.errorCode = ERROR_CODES.DUPLICATE_ENTRY; + break; + case 'ER_NO_REFERENCED_ROW_2': + error.message = '关联数据不存在'; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + break; + case 'ER_ROW_IS_REFERENCED_2': + error.message = '数据正在被使用,无法删除'; + error.statusCode = HTTP_STATUS.CONFLICT; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + break; + case 'ECONNREFUSED': + error.message = '数据库连接失败'; + error.statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR; + error.errorCode = ERROR_CODES.DATABASE_ERROR; + break; + default: + error.message = '数据库操作失败'; + error.statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR; + error.errorCode = ERROR_CODES.DATABASE_ERROR; + } + } + + // JWT错误处理 + if (err.name === 'JsonWebTokenError') { + error.message = '无效的访问令牌'; + error.statusCode = HTTP_STATUS.UNAUTHORIZED; + error.errorCode = ERROR_CODES.AUTHENTICATION_ERROR; + } + + if (err.name === 'TokenExpiredError') { + error.message = '访问令牌已过期'; + error.statusCode = HTTP_STATUS.UNAUTHORIZED; + error.errorCode = ERROR_CODES.AUTHENTICATION_ERROR; + } + + // 参数验证错误 + if (err.name === 'ValidationError' || err.isJoi) { + const message = err.details ? err.details.map(detail => detail.message).join(', ') : err.message; + error.message = `参数验证失败: ${message}`; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + } + + // 业务逻辑错误处理 + if (err.message === '余额不足') { + error.message = '用户积分余额不足,无法完成转账操作。请先为用户充值积分或选择其他用户。'; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + } + + if (err.message === '用户不存在') { + error.message = '指定的用户不存在,请检查用户信息后重试。'; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + } + + // 自定义错误 + if (err.statusCode) { + error.statusCode = err.statusCode; + error.errorCode = err.errorCode || ERROR_CODES.INTERNAL_ERROR; + } + + // 默认错误 + const statusCode = error.statusCode || HTTP_STATUS.INTERNAL_SERVER_ERROR; + const errorCode = error.errorCode || ERROR_CODES.INTERNAL_ERROR; + const message = error.message || '服务器内部错误'; + + res.status(statusCode).json({ + success: false, + error: { + code: errorCode, + message: message + }, + ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) + }); +}; + +// 404错误处理 +const notFound = (req, res, next) => { + const error = new Error(`路径 ${req.originalUrl} 未找到`); + error.statusCode = HTTP_STATUS.NOT_FOUND; + error.errorCode = ERROR_CODES.NOT_FOUND; + next(error); +}; + +// 自定义错误类 +class AppError extends Error { + constructor(message, statusCode, errorCode) { + super(message); + this.statusCode = statusCode; + this.errorCode = errorCode; + this.isOperational = true; + + Error.captureStackTrace(this, this.constructor); + } +} + +module.exports = { + errorHandler, + notFound, + AppError +}; \ No newline at end of file diff --git a/middleware/validation.js b/middleware/validation.js new file mode 100644 index 0000000..41e97f1 --- /dev/null +++ b/middleware/validation.js @@ -0,0 +1,230 @@ +const Joi = require('joi'); +const { AppError } = require('./errorHandler'); +const { ERROR_CODES, HTTP_STATUS } = require('../config/constants'); + +// 验证中间件工厂函数 +const validate = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.body, { abortEarly: false }); + if (error) { + const errorMessage = error.details.map(detail => detail.message).join(', '); + return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); + } + next(); + }; +}; + +// 查询参数验证中间件 +const validateQuery = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.query, { abortEarly: false }); + if (error) { + const errorMessage = error.details.map(detail => detail.message).join(', '); + return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); + } + next(); + }; +}; + +// 路径参数验证中间件 +const validateParams = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.params, { abortEarly: false }); + if (error) { + const errorMessage = error.details.map(detail => detail.message).join(', '); + return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); + } + next(); + }; +}; + +// 通用验证规则 +const commonSchemas = { + // ID验证 + id: Joi.number().integer().positive().required().messages({ + 'number.base': 'ID必须是数字', + 'number.integer': 'ID必须是整数', + 'number.positive': 'ID必须是正数', + 'any.required': 'ID是必需的' + }), + + // 分页验证 + pagination: Joi.object({ + page: Joi.number().integer().min(1).default(1).messages({ + 'number.base': '页码必须是数字', + 'number.integer': '页码必须是整数', + 'number.min': '页码必须大于0' + }), + limit: Joi.number().integer().min(1).max(100).default(10).messages({ + 'number.base': '每页数量必须是数字', + 'number.integer': '每页数量必须是整数', + 'number.min': '每页数量必须大于0', + 'number.max': '每页数量不能超过100' + }) + }) +}; + +// 用户相关验证规则 +const userSchemas = { + // 用户注册 + register: Joi.object({ + username: Joi.string().alphanum().min(3).max(30).required().messages({ + 'string.base': '用户名必须是字符串', + 'string.alphanum': '用户名只能包含字母和数字', + 'string.min': '用户名至少3个字符', + 'string.max': '用户名最多30个字符', + 'any.required': '用户名是必需的' + }), + password: Joi.string().min(6).max(128).required().messages({ + 'string.base': '密码必须是字符串', + 'string.min': '密码至少6个字符', + 'string.max': '密码最多128个字符', + 'any.required': '密码是必需的' + }), + phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required().messages({ + 'string.pattern.base': '手机号格式不正确', + 'any.required': '手机号是必需的' + }), + // 可选字段,注册时不需要填写 + real_name: Joi.string().max(50).allow('').optional().messages({ + 'string.max': '真实姓名最多50个字符' + }), + role: Joi.string().valid('admin', 'user').default('user').messages({ + 'any.only': '角色只能是admin或user' + }) + }), + + // 用户登录 + login: Joi.object({ + username: Joi.string().required().messages({ + 'any.required': '用户名是必需的' + }), + password: Joi.string().required().messages({ + 'any.required': '密码是必需的' + }) + }) +}; + +// 转账相关验证规则 +const transferSchemas = { + // 转账查询参数 + query: Joi.object({ + page: Joi.number().integer().min(1).default(1).messages({ + 'number.base': '页码必须是数字', + 'number.integer': '页码必须是整数', + 'number.min': '页码必须大于0' + }), + limit: Joi.number().integer().min(1).max(100).default(10).messages({ + 'number.base': '每页数量必须是数字', + 'number.integer': '每页数量必须是整数', + 'number.min': '每页数量必须大于0', + 'number.max': '每页数量不能超过100' + }), + status: Joi.string().valid('pending', 'confirmed', 'rejected', 'cancelled').allow('').messages({ + 'any.only': '状态值无效' + }), + type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').allow('').messages({ + 'any.only': '转账类型无效' + }), + search: Joi.string().allow('').max(100).messages({ + 'string.max': '搜索关键词最多100个字符' + }), + transfer_type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').allow('').messages({ + 'any.only': '转账类型无效' + }), + start_date: Joi.date().iso().allow('').messages({ + 'date.format': '开始日期格式不正确' + }), + end_date: Joi.date().iso().allow('').messages({ + 'date.format': '结束日期格式不正确' + }), + sort: Joi.string().valid('id', 'amount', 'created_at', 'updated_at', 'status').allow('').messages({ + 'any.only': '排序字段无效,只支持: id, amount, created_at, updated_at, status' + }), + order: Joi.string().valid('asc', 'desc').allow('').messages({ + 'any.only': '排序方向无效,只支持: asc, desc' + }), + // 优先显示待处理转账参数 + show_pending: Joi.alternatives().try( + Joi.boolean(), + Joi.string().valid('true', 'false', '') + ).allow('').messages({ + 'alternatives.match': 'show_pending参数只能是布尔值或字符串true/false' + }) + }), + + // 创建转账 + create: Joi.object({ + to_user_id: Joi.number().integer().positive().required().messages({ + 'number.base': '收款用户ID必须是数字', + 'number.integer': '收款用户ID必须是整数', + 'number.positive': '收款用户ID必须是正数', + 'any.required': '收款用户ID是必需的' + }), + amount: Joi.number().positive().precision(2).required().messages({ + 'number.base': '金额必须是数字', + 'number.positive': '金额必须是正数', + 'any.required': '金额是必需的' + }), + transfer_type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').required().messages({ + 'any.only': '转账类型无效', + 'any.required': '转账类型是必需的' + }), + description: Joi.string().max(500).allow('').messages({ + 'string.max': '描述最多500个字符' + }), + voucher_url: Joi.string().uri().allow('').messages({ + 'string.uri': '凭证URL格式不正确' + }) + }), + + // 确认转账 + confirm: Joi.object({ + transfer_id: Joi.number().integer().positive().required().messages({ + 'number.base': '转账ID必须是数字', + 'number.integer': '转账ID必须是整数', + 'number.positive': '转账ID必须是正数', + 'any.required': '转账ID是必需的' + }), + note: Joi.string().max(500).allow('').messages({ + 'string.max': '备注最多500个字符' + }) + }), + + // 拒绝转账 + reject: Joi.object({ + transfer_id: Joi.number().integer().positive().required().messages({ + 'number.base': '转账ID必须是数字', + 'number.integer': '转账ID必须是整数', + 'number.positive': '转账ID必须是正数', + 'any.required': '转账ID是必需的' + }), + note: Joi.string().max(500).allow('').messages({ + 'string.max': '备注最多500个字符' + }) + }) +}; +// 系统设置相关验证规则 +const systemSchemas = { + updateSettings: Joi.object({ + site_name: Joi.string().max(100).optional(), + site_description: Joi.string().max(500).optional(), + + contact_phone: Joi.string().max(20).optional(), + maintenance_mode: Joi.boolean().optional(), + max_transfer_amount: Joi.number().positive().optional(), + min_transfer_amount: Joi.number().positive().optional(), + transfer_fee_rate: Joi.number().min(0).max(1).optional() + }) +}; + +// 导出所有验证规则 +module.exports = { + validate, + validateQuery, + validateParams, + commonSchemas, + userSchemas, + transferSchemas, + systemSchemas +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..c173fa9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3750 @@ +{ + "name": "jurong-circle-agent-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jurong-circle-agent-backend", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@alicloud/dysmsapi20170525": "^4.1.2", + "@alicloud/openapi-client": "^0.4.15", + "axios": "^1.11.0", + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "dayjs": "^1.11.13", + "dotenv": "^17.2.1", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "express-validator": "^7.2.1", + "helmet": "^8.1.0", + "joi": "^17.13.3", + "jsonwebtoken": "^9.0.2", + "minio": "^8.0.5", + "multer": "^1.4.5-lts.1", + "mysql2": "^3.14.3", + "node-cron": "^4.2.1", + "qrcode": "^1.5.4", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "winston": "^3.17.0", + "xml2js": "^0.6.2" + }, + "devDependencies": { + "concurrently": "^8.2.2", + "nodemon": "^3.0.2" + } + }, + "node_modules/@alicloud/credentials": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@alicloud/credentials/-/credentials-2.4.4.tgz", + "integrity": "sha512-/eRAGSKcniLIFQ1UCpDhB/IrHUZisQ1sc65ws/c2avxUMpXwH1rWAohb76SVAUJhiF4mwvLzLJM1Mn1XL4Xe/Q==", + "license": "MIT", + "dependencies": { + "@alicloud/tea-typescript": "^1.8.0", + "httpx": "^2.3.3", + "ini": "^1.3.5", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/darabonba-array": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-array/-/darabonba-array-0.1.2.tgz", + "integrity": "sha512-ZPuQ+bJyjrd8XVVm55kl+ypk7OQoi1ZH/DiToaAEQaGvgEjrTcvQkg71//vUX/6cvbLIF5piQDvhrLb+lUEIPQ==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/darabonba-encode-util": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-encode-util/-/darabonba-encode-util-0.0.2.tgz", + "integrity": "sha512-mlsNctkeqmR0RtgE1Rngyeadi5snLOAHBCWEtYf68d7tyKskosXDTNeZ6VCD/UfrUu4N51ItO8zlpfXiOgeg3A==", + "license": "ISC", + "dependencies": { + "moment": "^2.29.1" + } + }, + "node_modules/@alicloud/darabonba-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-map/-/darabonba-map-0.0.1.tgz", + "integrity": "sha512-2ep+G3YDvuI+dRYVlmER1LVUQDhf9kEItmVB/bbEu1pgKzelcocCwAc79XZQjTcQGFgjDycf3vH87WLDGLFMlw==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/darabonba-signature-util": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-signature-util/-/darabonba-signature-util-0.0.4.tgz", + "integrity": "sha512-I1TtwtAnzLamgqnAaOkN0IGjwkiti//0a7/auyVThdqiC/3kyafSAn6znysWOmzub4mrzac2WiqblZKFcN5NWg==", + "license": "ISC", + "dependencies": { + "@alicloud/darabonba-encode-util": "^0.0.1" + } + }, + "node_modules/@alicloud/darabonba-signature-util/node_modules/@alicloud/darabonba-encode-util": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-encode-util/-/darabonba-encode-util-0.0.1.tgz", + "integrity": "sha512-Sl5vCRVAYMqwmvXpJLM9hYoCHOMsQlGxaWSGhGWulpKk/NaUBArtoO1B0yHruJf1C5uHhEJIaylYcM48icFHgw==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1", + "moment": "^2.29.1" + } + }, + "node_modules/@alicloud/darabonba-string": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-string/-/darabonba-string-1.0.3.tgz", + "integrity": "sha512-NyWwrU8cAIesWk3uHL1Q7pTDTqLkCI/0PmJXC4/4A0MFNAZ9Ouq0iFBsRqvfyUujSSM+WhYLuTfakQXiVLkTMA==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1" + } + }, + "node_modules/@alicloud/dysmsapi20170525": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@alicloud/dysmsapi20170525/-/dysmsapi20170525-4.1.2.tgz", + "integrity": "sha512-Wxg+wQjpBGmXCvmIf9QE0mBv9dcGI0q13NxzF48akLYjSf/Mpk7jbnYttqEzNZPpRMShi1wViANwo8q+WkvYfQ==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/openapi-core": "^1.0.0", + "@darabonba/typescript": "^1.0.0" + } + }, + "node_modules/@alicloud/endpoint-util": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@alicloud/endpoint-util/-/endpoint-util-0.0.1.tgz", + "integrity": "sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/gateway-pop": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@alicloud/gateway-pop/-/gateway-pop-0.0.6.tgz", + "integrity": "sha512-KF4I+JvfYuLKc3fWeWYIZ7lOVJ9jRW0sQXdXidZn1DKZ978ncfGf7i0LBfONGk4OxvNb/HD3/0yYhkgZgPbKtA==", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2", + "@alicloud/darabonba-array": "^0.1.0", + "@alicloud/darabonba-encode-util": "^0.0.2", + "@alicloud/darabonba-map": "^0.0.1", + "@alicloud/darabonba-signature-util": "^0.0.4", + "@alicloud/darabonba-string": "^1.0.2", + "@alicloud/endpoint-util": "^0.0.1", + "@alicloud/gateway-spi": "^0.0.8", + "@alicloud/openapi-util": "^0.3.2", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.4.8" + } + }, + "node_modules/@alicloud/gateway-spi": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz", + "integrity": "sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g==", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2", + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/openapi-client": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@alicloud/openapi-client/-/openapi-client-0.4.15.tgz", + "integrity": "sha512-4VE0/k5ZdQbAhOSTqniVhuX1k5DUeUMZv74degn3wIWjLY6Bq+hxjaGsaHYlLZ2gA5wUrs8NcI5TE+lIQS3iiA==", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2.4.2", + "@alicloud/gateway-spi": "^0.0.8", + "@alicloud/openapi-util": "^0.3.2", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "1.4.9", + "@alicloud/tea-xml": "0.0.3" + } + }, + "node_modules/@alicloud/openapi-core": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@alicloud/openapi-core/-/openapi-core-1.0.5.tgz", + "integrity": "sha512-ed4EKyqHjb9zwrXUs6IRthha/pRn3OUoOcUKuhYu4tllp0RpidA+JYswsweN6sh26H0WIs/LB6nzJEOvh1d3fg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "latest", + "@alicloud/gateway-pop": "0.0.6", + "@alicloud/gateway-spi": "^0.0.8", + "@darabonba/typescript": "^1.0.2" + } + }, + "node_modules/@alicloud/openapi-util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@alicloud/openapi-util/-/openapi-util-0.3.2.tgz", + "integrity": "sha512-EC2JvxdcOgMlBAEG0+joOh2IB1um8CPz9EdYuRfTfd1uP8Yc9D8QRUWVGjP6scnj6fWSOaHFlit9H6PrJSyFow==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.3.0", + "kitx": "^2.1.0", + "sm3": "^1.0.3" + } + }, + "node_modules/@alicloud/tea-typescript": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz", + "integrity": "sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ==", + "license": "ISC", + "dependencies": { + "@types/node": "^12.0.2", + "httpx": "^2.2.6" + } + }, + "node_modules/@alicloud/tea-util": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@alicloud/tea-util/-/tea-util-1.4.9.tgz", + "integrity": "sha512-S0wz76rGtoPKskQtRTGqeuqBHFj8BqUn0Vh+glXKun2/9UpaaaWmuJwcmtImk6bJZfLYEShDF/kxDmDJoNYiTw==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/tea-xml": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@alicloud/tea-xml/-/tea-xml-0.0.3.tgz", + "integrity": "sha512-+/9GliugjrLglsXVrd1D80EqqKgGpyA0eQ6+1ZdUOYCaRguaSwz44trX3PaxPu/HhIPJg9PsGQQ3cSLXWZjbAA==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1", + "@types/xml2js": "^0.4.5", + "xml2js": "^0.6.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@darabonba/typescript": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@darabonba/typescript/-/typescript-1.0.3.tgz", + "integrity": "sha512-/y2y6wf5TsxD7pCPIm0OvTC+5qV0Tk7HQYxwpIuWRLXQLB0CRDvr6qk4bR6rTLO/JglJa8z2uCGZsaLYpQNqFQ==", + "license": "Apache License 2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "httpx": "^2.3.2", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "xml2js": "^0.6.2" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-validator": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", + "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/httpx": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/httpx/-/httpx-2.3.3.tgz", + "integrity": "sha512-k1qv94u1b6e+XKCxVbLgYlOypVP9MPGpnN5G/vxFf6tDO4V3xpz3d6FUOY/s8NtPgaq5RBVVgSB+7IHpVxMYzw==", + "license": "MIT", + "dependencies": { + "@types/node": "^20", + "debug": "^4.1.1" + } + }, + "node_modules/httpx/node_modules/@types/node": { + "version": "20.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz", + "integrity": "sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/httpx/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/httpx/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kitx": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/kitx/-/kitx-2.2.0.tgz", + "integrity": "sha512-tBMwe6AALTBQJb0woQDD40734NKzb0Kzi3k7wQj9ar3AbP9oqhoVrdXPh7rk2r00/glIgd0YbToIUJsnxWMiIg==", + "license": "MIT", + "dependencies": { + "@types/node": "^22.5.4" + } + }, + "node_modules/kitx/node_modules/@types/node": { + "version": "22.18.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.0.tgz", + "integrity": "sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minio": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.5.tgz", + "integrity": "sha512-/vAze1uyrK2R/DSkVutE4cjVoAowvIQ18RAwn7HrqnLecLlMazFnY0oNBqfuoAWvu7mZIGX75AzpuV05TJeoHg==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^4.4.1", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml2js": "^0.5.0 || ^0.6.2" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/mysql2": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.4.tgz", + "integrity": "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sm3": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sm3/-/sm3-1.0.3.tgz", + "integrity": "sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==", + "license": "MIT" + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz", + "integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "license": "MIT", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c876cf0 --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "jurong-circle-agent-backend", + "version": "1.0.0", + "description": "炬融圈代理后台API服务", + "main": "server.js", + "scripts": { + "dev": "nodemon server.js" + }, + "dependencies": { + "@alicloud/dysmsapi20170525": "^4.1.2", + "@alicloud/openapi-client": "^0.4.15", + "axios": "^1.11.0", + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "dayjs": "^1.11.13", + "dotenv": "^17.2.1", + "express": "^4.18.2", + "express-rate-limit": "^7.1.5", + "express-validator": "^7.2.1", + "helmet": "^8.1.0", + "joi": "^17.13.3", + "jsonwebtoken": "^9.0.2", + "minio": "^8.0.5", + "multer": "^1.4.5-lts.1", + "mysql2": "^3.14.3", + "node-cron": "^4.2.1", + "qrcode": "^1.5.4", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "winston": "^3.17.0", + "xml2js": "^0.6.2" + }, + "devDependencies": { + "concurrently": "^8.2.2", + "nodemon": "^3.0.2" + }, + "keywords": [ + "vue3", + "nodejs", + "express", + "mysql", + "element-plus" + ], + "author": "", + "license": "MIT" +} diff --git a/routes/agent.js b/routes/agent.js new file mode 100644 index 0000000..18c863f --- /dev/null +++ b/routes/agent.js @@ -0,0 +1,294 @@ +const express = require('express'); +const router = express.Router(); +const { getDB } = require('../database'); +const { agentAuth } = require('../middleware/agentAuth'); +const { logger } = require('../config/logger'); + +/** + * 获取代理统计数据 + * GET /api/agent/stats + */ +router.get('/stats', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + + // 获取下级用户统计 + const [userStats] = await getDB().execute(` + SELECT + COUNT(*) as total_users, + COUNT(CASE WHEN DATE(created_at) = CURDATE() THEN 1 END) as today_new_users, + COUNT(CASE WHEN last_login_at >= DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 1 END) as active_users, + CAST(COALESCE(SUM(balance), 0) AS DECIMAL(10,2)) as total_balance + FROM agent_merchants + WHERE agent_id = ? + `, [agentId]); + + // 获取佣金统计 + const [commissionStats] = await getDB().execute(` + SELECT + CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10,2)) as total_commission, + CAST(COALESCE(SUM(CASE WHEN DATE(created_at) = CURDATE() THEN commission_amount ELSE 0 END), 0) AS DECIMAL(10,2)) as today_commission, + CAST(COALESCE(SUM(CASE WHEN status = 'paid' THEN commission_amount ELSE 0 END), 0) AS DECIMAL(10,2)) as paid_commission, + CAST(COALESCE(SUM(CASE WHEN status = 'pending' THEN commission_amount ELSE 0 END), 0) AS DECIMAL(10,2)) as pending_commission + FROM agent_commission_records + WHERE agent_id = ? + `, [agentId]); + + // 获取转账统计 + const [transferStats] = await getDB().execute(` + SELECT + COUNT(*) as total_transfers, + COUNT(CASE WHEN DATE(created_at) = CURDATE() THEN 1 END) as today_transfers, + CAST(COALESCE(SUM(amount), 0) AS DECIMAL(10,2)) as total_amount, + CAST(COALESCE(SUM(CASE WHEN DATE(created_at) = CURDATE() THEN amount ELSE 0 END), 0) AS DECIMAL(10,2)) as today_amount + FROM transfers t + INNER JOIN agent_merchants am ON (t.from_user_id = am.merchant_id OR t.to_user_id = am.merchant_id) + WHERE am.agent_id = ? + `, [agentId]); + + const stats = { + users: userStats[0] || { + total_users: 0, + today_new_users: 0, + active_users: 0, + total_balance: '0.00' + }, + commissions: commissionStats[0] || { + total_commission: '0.00', + today_commission: '0.00', + paid_commission: '0.00', + pending_commission: '0.00' + }, + transfers: transferStats[0] || { + total_transfers: 0, + today_transfers: 0, + total_amount: '0.00', + today_amount: '0.00' + } + }; + + res.json({ + success: true, + data: stats + }); + + } catch (error) { + logger.error('获取代理统计数据失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取统计数据失败' + }); + } +}); + +/** + * 获取用户增长趋势数据 + * GET /api/agent/user-growth-trend + */ +router.get('/user-growth-trend', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { days = 7 } = req.query; + + const [trendData] = await getDB().execute(` + SELECT + DATE(created_at) as date, + COUNT(*) as count + FROM agent_merchants + WHERE agent_id = ? + AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) + GROUP BY DATE(created_at) + ORDER BY date ASC + `, [agentId, parseInt(days)]); + + res.json({ + success: true, + data: trendData + }); + + } catch (error) { + logger.error('获取用户增长趋势失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取趋势数据失败' + }); + } +}); + +/** + * 获取佣金收入趋势数据 + * GET /api/agent/commission-trend + */ +router.get('/commission-trend', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { days = 7 } = req.query; + + const [trendData] = await getDB().execute(` + SELECT + DATE(created_at) as date, + CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10,2)) as amount + FROM agent_commission_records + WHERE agent_id = ? + AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) + GROUP BY DATE(created_at) + ORDER BY date ASC + `, [agentId, parseInt(days)]); + + res.json({ + success: true, + data: trendData + }); + + } catch (error) { + logger.error('获取佣金趋势失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取趋势数据失败' + }); + } +}); + +/** + * 获取佣金类型分布数据 + * GET /api/agent/commission-distribution + */ +router.get('/commission-distribution', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + + const [distributionData] = await getDB().execute(` + SELECT + commission_type as type, + COUNT(*) as count, + CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10,2)) as amount + FROM agent_commission_records + WHERE agent_id = ? + GROUP BY commission_type + ORDER BY amount DESC + `, [agentId]); + + res.json({ + success: true, + data: distributionData + }); + + } catch (error) { + logger.error('获取佣金分布失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取分布数据失败' + }); + } +}); + +/** + * 获取最新下级用户 + * GET /api/agent/recent-users + */ +router.get('/recent-users', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { limit = 10 } = req.query; + + const [recentUsers] = await getDB().execute(` + SELECT + u.id, + u.username, + u.real_name, + u.phone, + u.avatar, + u.balance, + u.created_at, + am.created_at as join_date + FROM agent_merchants am + LEFT JOIN users u ON am.merchant_id = u.id + WHERE am.agent_id = ? + ORDER BY am.created_at DESC + LIMIT ? + `, [agentId, parseInt(limit)]); + + res.json({ + success: true, + data: recentUsers + }); + + } catch (error) { + logger.error('获取最新用户失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取最新用户失败' + }); + } +}); + +/** + * 获取最新佣金记录 + * GET /api/agent/recent-commissions + */ +router.get('/recent-commissions', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { limit = 10 } = req.query; + + const [recentCommissions] = await getDB().execute(` + SELECT + acr.id, + acr.commission_type, + acr.commission_amount, + acr.status, + acr.created_at, + u.username, + u.real_name + FROM agent_commission_records acr + LEFT JOIN users u ON acr.merchant_id = u.id + WHERE acr.agent_id = ? + ORDER BY acr.created_at DESC + LIMIT ? + `, [agentId, parseInt(limit)]); + + res.json({ + success: true, + data: recentCommissions + }); + + } catch (error) { + logger.error('获取最新佣金失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取最新佣金失败' + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..9f21c22 --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,226 @@ +const express = require('express'); +const router = express.Router(); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const { getDB } = require('../database'); +const { logger } = require('../config/logger'); + +// JWT密钥 +const JWT_SECRET = process.env.JWT_SECRET || 'agent_jwt_secret_key_2024'; + +/** + * 代理登录 + * POST /api/auth/login + */ +router.post('/login', async (req, res) => { + try { + const { phone, password } = req.body; + + if (!phone || !password) { + return res.status(400).json({ + success: false, + message: '请输入手机号和密码' + }); + } + + // 查询代理信息 + const [agents] = await getDB().execute(` + SELECT + ra.id as agent_id, + ra.user_id, + ra.agent_code, + ra.status as agent_status, + ra.region_id, + u.id as user_id, + u.username, + u.phone, + u.password, + u.real_name, + u.avatar, + zr.city_name, + zr.district_name + FROM regional_agents ra + LEFT JOIN users u ON ra.user_id = u.id + LEFT JOIN zhejiang_regions zr ON ra.region_id = zr.id + WHERE u.phone = ? AND ra.status = 'active' + `, [phone]); + + if (agents.length === 0) { + return res.status(401).json({ + success: false, + message: '手机号不存在或代理账号未激活' + }); + } + + const agent = agents[0]; + + // 验证密码 + const isPasswordValid = await bcrypt.compare(password, agent.password); + if (!isPasswordValid) { + return res.status(401).json({ + success: false, + message: '密码错误' + }); + } + + // 生成JWT token + const token = jwt.sign( + { + userId: agent.user_id, + agentId: agent.agent_id, + phone: agent.phone, + role: 'agent' + }, + JWT_SECRET, + { expiresIn: '24h' } + ); + + // 记录登录日志 + logger.info('代理登录成功', { + agentId: agent.agent_id, + phone: agent.phone, + ip: req.ip + }); + + // 返回登录成功信息 + res.json({ + success: true, + message: '登录成功', + data: { + token, + agent: { + id: agent.agent_id, + userId: agent.user_id, + agentCode: agent.agent_code, + phone: agent.phone, + realName: agent.real_name, + avatar: agent.avatar, + region: { + id: agent.region_id, + cityName: agent.city_name, + districtName: agent.district_name + } + } + } + }); + + } catch (error) { + logger.error('代理登录失败', { + error: error.message, + stack: error.stack, + ip: req.ip + }); + + res.status(500).json({ + success: false, + message: '登录失败,请稍后重试' + }); + } +}); + +/** + * 获取当前代理信息 + * GET /api/auth/me + */ +router.get('/me', async (req, res) => { + try { + const token = req.headers.authorization?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ + success: false, + message: '未提供认证令牌' + }); + } + + // 验证token + const decoded = jwt.verify(token, JWT_SECRET); + + // 查询代理信息 + const [agents] = await getDB().execute(` + SELECT + ra.id as agent_id, + ra.user_id, + ra.agent_code, + ra.status as agent_status, + ra.region_id, + u.phone, + u.real_name, + u.avatar, + zr.city_name, + zr.district_name + FROM regional_agents ra + LEFT JOIN users u ON ra.user_id = u.id + LEFT JOIN zhejiang_regions zr ON ra.region_id = zr.id + WHERE ra.id = ? AND ra.status = 'active' + `, [decoded.agentId]); + + if (agents.length === 0) { + return res.status(401).json({ + success: false, + message: '代理账号不存在或已被禁用' + }); + } + + const agent = agents[0]; + + res.json({ + success: true, + data: { + agent: { + id: agent.agent_id, + userId: agent.user_id, + agentCode: agent.agent_code, + phone: agent.phone, + realName: agent.real_name, + avatar: agent.avatar, + region: { + id: agent.region_id, + cityName: agent.city_name, + districtName: agent.district_name + } + } + } + }); + + } catch (error) { + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ + success: false, + message: '无效的认证令牌' + }); + } + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ + success: false, + message: '认证令牌已过期' + }); + } + + logger.error('获取代理信息失败', { + error: error.message, + stack: error.stack + }); + + res.status(500).json({ + success: false, + message: '获取用户信息失败' + }); + } +}); + +/** + * 代理登出 + * POST /api/auth/logout + */ +router.post('/logout', (req, res) => { + // 由于使用JWT,登出主要在前端处理(删除token) + // 这里只是提供一个标准的登出接口 + res.json({ + success: true, + message: '登出成功' + }); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/commissions.js b/routes/commissions.js new file mode 100644 index 0000000..c988a31 --- /dev/null +++ b/routes/commissions.js @@ -0,0 +1,540 @@ +const express = require('express'); +const router = express.Router(); +const { getDB } = require('../database'); +const { agentAuth } = require('../middleware/agentAuth'); +const { logger } = require('../config/logger'); + +/** + * 获取代理佣金记录列表 + * GET /api/commissions + */ +router.get('/', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { + page = 1, + limit = 20, + search, + status, + commission_type, + start_date, + end_date, + min_amount, + max_amount, + sort_by = 'created_at', + sort_order = 'desc' + } = req.query; + + const pageNum = parseInt(page) || 1; + const limitNum = parseInt(limit) || 20; + const offset = (pageNum - 1) * limitNum; + + // 构建查询条件 + let whereConditions = ['acr.agent_id = ?']; + let queryParams = [agentId]; + + if (search) { + whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ? OR u.phone LIKE ?)'); + queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`); + } + + if (status) { + whereConditions.push('acr.status = ?'); + queryParams.push(status); + } + + if (commission_type) { + whereConditions.push('acr.commission_type = ?'); + queryParams.push(commission_type); + } + + if (start_date) { + whereConditions.push('DATE(acr.created_at) >= ?'); + queryParams.push(start_date); + } + + if (end_date) { + whereConditions.push('DATE(acr.created_at) <= ?'); + queryParams.push(end_date); + } + + if (min_amount) { + whereConditions.push('acr.commission_amount >= ?'); + queryParams.push(parseFloat(min_amount)); + } + + if (max_amount) { + whereConditions.push('acr.commission_amount <= ?'); + queryParams.push(parseFloat(max_amount)); + } + + const whereClause = whereConditions.join(' AND '); + + // 验证排序字段 + const allowedSortFields = ['created_at', 'commission_amount', 'status', 'commission_type']; + const sortBy = allowedSortFields.includes(sort_by) ? sort_by : 'created_at'; + const sortOrder = sort_order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; + + // 查询佣金记录列表 + const commissionsQuery = ` + SELECT + acr.id, + acr.agent_id, + acr.merchant_id, + acr.commission_type, + acr.commission_amount, + acr.status, + acr.description, + acr.reference_id, + acr.created_at, + acr.updated_at, + acr.paid_at, + u.username, + u.real_name, + u.phone, + u.avatar + FROM agent_commission_records acr + LEFT JOIN users u ON acr.merchant_id = u.id + WHERE ${whereClause} + ORDER BY acr.${sortBy} ${sortOrder} + LIMIT ${limitNum} OFFSET ${offset} + `; + + const [commissions] = await getDB().execute(commissionsQuery, queryParams); + + // 查询总数 + const countQuery = ` + SELECT COUNT(*) as total + FROM agent_commission_records acr + LEFT JOIN users u ON acr.merchant_id = u.id + WHERE ${whereClause} + `; + + const [countResult] = await getDB().execute(countQuery, queryParams); + const total = countResult[0]?.total || 0; + + // 查询统计信息 + const [statsResult] = await getDB().execute(` + SELECT + COUNT(*) as total_commissions, + COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_commissions, + COUNT(CASE WHEN status = 'paid' THEN 1 END) as paid_commissions, + CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10,2)) as total_amount, + CAST(COALESCE(SUM(CASE WHEN status = 'pending' THEN commission_amount ELSE 0 END), 0) AS DECIMAL(10,2)) as pending_amount, + CAST(COALESCE(SUM(CASE WHEN status = 'paid' THEN commission_amount ELSE 0 END), 0) AS DECIMAL(10,2)) as paid_amount, + CAST(COALESCE(SUM(CASE WHEN DATE(created_at) = CURDATE() THEN commission_amount ELSE 0 END), 0) AS DECIMAL(10,2)) as today_amount + FROM agent_commission_records + WHERE agent_id = ? + `, [agentId]); + + const stats = statsResult[0] || { + total_commissions: 0, + pending_commissions: 0, + paid_commissions: 0, + total_amount: '0.00', + pending_amount: '0.00', + paid_amount: '0.00', + today_amount: '0.00' + }; + + res.json({ + success: true, + data: { + commissions, + pagination: { + current_page: pageNum, + per_page: limitNum, + total, + total_pages: Math.ceil(total / limitNum) + }, + stats + } + }); + + } catch (error) { + logger.error('获取佣金记录失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取佣金记录失败' + }); + } +}); + +/** + * 获取单个佣金记录详情 + * GET /api/commissions/:id + */ +router.get('/:id', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const commissionId = req.params.id; + + // 查询佣金记录详情 + const [commissions] = await getDB().execute(` + SELECT + acr.id, + acr.agent_id, + acr.merchant_id, + acr.commission_type, + acr.commission_amount, + acr.status, + acr.description, + acr.reference_id, + acr.created_at, + acr.updated_at, + acr.paid_at, + u.username, + u.real_name, + u.phone, + u.avatar, + u.city, + u.district + FROM agent_commission_records acr + LEFT JOIN users u ON acr.merchant_id = u.id + WHERE acr.id = ? AND acr.agent_id = ? + `, [commissionId, agentId]); + + if (commissions.length === 0) { + return res.status(404).json({ + success: false, + message: '佣金记录不存在或无权限查看' + }); + } + + const commission = commissions[0]; + + res.json({ + success: true, + data: commission + }); + + } catch (error) { + logger.error('获取佣金记录详情失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id, + commissionId: req.params.id + }); + + res.status(500).json({ + success: false, + message: '获取佣金记录详情失败' + }); + } +}); + +/** + * 申请佣金发放(单个) + * POST /api/commissions/:id/request-payment + */ +router.post('/:id/request-payment', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const commissionId = req.params.id; + + // 检查佣金记录是否存在且属于当前代理 + const [commissions] = await getDB().execute(` + SELECT id, status, commission_amount + FROM agent_commission_records + WHERE id = ? AND agent_id = ? + `, [commissionId, agentId]); + + if (commissions.length === 0) { + return res.status(404).json({ + success: false, + message: '佣金记录不存在或无权限操作' + }); + } + + const commission = commissions[0]; + + if (commission.status !== 'pending') { + return res.status(400).json({ + success: false, + message: '只能申请待发放状态的佣金' + }); + } + + // 更新佣金状态为申请中 + await getDB().execute(` + UPDATE agent_commission_records + SET status = 'requested', updated_at = NOW() + WHERE id = ? + `, [commissionId]); + + logger.info('代理申请佣金发放', { + agentId, + commissionId, + amount: commission.commission_amount + }); + + res.json({ + success: true, + message: '佣金发放申请已提交,请等待审核' + }); + + } catch (error) { + logger.error('申请佣金发放失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id, + commissionId: req.params.id + }); + + res.status(500).json({ + success: false, + message: '申请佣金发放失败' + }); + } +}); + +/** + * 批量申请佣金发放 + * POST /api/commissions/batch-request-payment + */ +router.post('/batch-request-payment', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { commission_ids } = req.body; + + if (!commission_ids || !Array.isArray(commission_ids) || commission_ids.length === 0) { + return res.status(400).json({ + success: false, + message: '请选择要申请发放的佣金记录' + }); + } + + // 检查所有佣金记录是否存在且属于当前代理 + const placeholders = commission_ids.map(() => '?').join(','); + const [commissions] = await getDB().execute(` + SELECT id, status, commission_amount + FROM agent_commission_records + WHERE id IN (${placeholders}) AND agent_id = ? + `, [...commission_ids, agentId]); + + if (commissions.length !== commission_ids.length) { + return res.status(400).json({ + success: false, + message: '部分佣金记录不存在或无权限操作' + }); + } + + // 检查状态 + const invalidCommissions = commissions.filter(c => c.status !== 'pending'); + if (invalidCommissions.length > 0) { + return res.status(400).json({ + success: false, + message: '只能申请待发放状态的佣金' + }); + } + + // 批量更新状态 + await getDB().execute(` + UPDATE agent_commission_records + SET status = 'requested', updated_at = NOW() + WHERE id IN (${placeholders}) AND agent_id = ? + `, [...commission_ids, agentId]); + + const totalAmount = commissions.reduce((sum, c) => sum + parseFloat(c.commission_amount), 0); + + logger.info('代理批量申请佣金发放', { + agentId, + commissionIds: commission_ids, + count: commission_ids.length, + totalAmount + }); + + res.json({ + success: true, + message: `已提交${commission_ids.length}条佣金发放申请,总金额${totalAmount.toFixed(2)}元` + }); + + } catch (error) { + logger.error('批量申请佣金发放失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '批量申请佣金发放失败' + }); + } +}); + +/** + * 获取佣金趋势数据 + * GET /api/commissions/trend + */ +router.get('/trend/data', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { days = 7, type = 'amount' } = req.query; + + let selectField = 'CAST(COALESCE(SUM(commission_amount), 0) AS DECIMAL(10,2)) as value'; + if (type === 'count') { + selectField = 'COUNT(*) as value'; + } + + const [trendData] = await getDB().execute(` + SELECT + DATE(created_at) as date, + ${selectField} + FROM agent_commission_records + WHERE agent_id = ? + AND created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) + GROUP BY DATE(created_at) + ORDER BY date ASC + `, [agentId, parseInt(days)]); + + res.json({ + success: true, + data: trendData + }); + + } catch (error) { + logger.error('获取佣金趋势失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取佣金趋势失败' + }); + } +}); + +/** + * 导出佣金记录 + * GET /api/commissions/export + */ +router.get('/export/data', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { + format = 'json', + search, + status, + commission_type, + start_date, + end_date, + min_amount, + max_amount + } = req.query; + + // 构建查询条件 + let whereConditions = ['acr.agent_id = ?']; + let queryParams = [agentId]; + + if (search) { + whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ? OR u.phone LIKE ?)'); + queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`); + } + + if (status) { + whereConditions.push('acr.status = ?'); + queryParams.push(status); + } + + if (commission_type) { + whereConditions.push('acr.commission_type = ?'); + queryParams.push(commission_type); + } + + if (start_date) { + whereConditions.push('DATE(acr.created_at) >= ?'); + queryParams.push(start_date); + } + + if (end_date) { + whereConditions.push('DATE(acr.created_at) <= ?'); + queryParams.push(end_date); + } + + if (min_amount) { + whereConditions.push('acr.commission_amount >= ?'); + queryParams.push(parseFloat(min_amount)); + } + + if (max_amount) { + whereConditions.push('acr.commission_amount <= ?'); + queryParams.push(parseFloat(max_amount)); + } + + const whereClause = whereConditions.join(' AND '); + + // 查询佣金记录 + const [commissions] = await getDB().execute(` + SELECT + acr.id, + acr.commission_type, + acr.commission_amount, + acr.status, + acr.description, + acr.reference_id, + acr.created_at, + acr.paid_at, + u.username, + u.real_name, + u.phone + FROM agent_commission_records acr + LEFT JOIN users u ON acr.merchant_id = u.id + WHERE ${whereClause} + ORDER BY acr.created_at DESC + `, queryParams); + + if (format === 'csv') { + // 生成CSV格式 + const csvHeader = 'ID,佣金类型,佣金金额,状态,描述,关联ID,用户名,真实姓名,手机号,创建时间,发放时间\n'; + const csvData = commissions.map(commission => { + return [ + commission.id, + commission.commission_type || '', + commission.commission_amount, + commission.status || '', + (commission.description || '').replace(/,/g, ','), // 替换逗号避免CSV格式问题 + commission.reference_id || '', + commission.username || '', + commission.real_name || '', + commission.phone || '', + commission.created_at || '', + commission.paid_at || '' + ].join(','); + }).join('\n'); + + res.setHeader('Content-Type', 'text/csv; charset=utf-8'); + res.setHeader('Content-Disposition', `attachment; filename="commissions_${Date.now()}.csv"`); + res.send(csvHeader + csvData); + } else { + // 默认JSON格式 + res.json({ + success: true, + data: commissions, + exported_at: new Date().toISOString(), + total: commissions.length + }); + } + + } catch (error) { + logger.error('导出佣金记录失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '导出佣金记录失败' + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/transfers.js b/routes/transfers.js new file mode 100644 index 0000000..3fb8ad4 --- /dev/null +++ b/routes/transfers.js @@ -0,0 +1,434 @@ +const express = require('express'); +const router = express.Router(); +const { getDB } = require('../database'); +const { agentAuth } = require('../middleware/agentAuth'); +const { logger } = require('../config/logger'); + +/** + * 获取代理下级用户转账记录列表 + * GET /api/transfers + */ +router.get('/', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { + page = 1, + limit = 20, + search, + status, + type, + start_date, + end_date, + min_amount, + max_amount, + sort_by = 'created_at', + sort_order = 'desc' + } = req.query; + + const pageNum = parseInt(page) || 1; + const limitNum = parseInt(limit) || 20; + const offset = (pageNum - 1) * limitNum; + + // 构建查询条件 + let whereConditions = [ + '(am1.agent_id = ? OR am2.agent_id = ?)' // 转出方或转入方属于当前代理 + ]; + let queryParams = [agentId, agentId]; + + if (search) { + whereConditions.push('(u1.username LIKE ? OR u1.real_name LIKE ? OR u1.phone LIKE ? OR u2.username LIKE ? OR u2.real_name LIKE ? OR u2.phone LIKE ?)'); + queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`); + } + + if (status) { + whereConditions.push('t.status = ?'); + queryParams.push(status); + } + + if (type) { + whereConditions.push('t.type = ?'); + queryParams.push(type); + } + + if (start_date) { + whereConditions.push('DATE(t.created_at) >= ?'); + queryParams.push(start_date); + } + + if (end_date) { + whereConditions.push('DATE(t.created_at) <= ?'); + queryParams.push(end_date); + } + + if (min_amount) { + whereConditions.push('t.amount >= ?'); + queryParams.push(parseFloat(min_amount)); + } + + if (max_amount) { + whereConditions.push('t.amount <= ?'); + queryParams.push(parseFloat(max_amount)); + } + + const whereClause = whereConditions.join(' AND '); + + // 验证排序字段 + const allowedSortFields = ['created_at', 'amount', 'status']; + const sortBy = allowedSortFields.includes(sort_by) ? sort_by : 'created_at'; + const sortOrder = sort_order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; + + // 查询转账记录列表 + const transfersQuery = ` + SELECT + t.id, + t.from_user_id, + t.to_user_id, + t.amount, + t.type, + t.status, + t.description, + t.transaction_id, + t.created_at, + t.updated_at, + u1.username as from_username, + u1.real_name as from_real_name, + u1.phone as from_phone, + u1.avatar as from_avatar, + u2.username as to_username, + u2.real_name as to_real_name, + u2.phone as to_phone, + u2.avatar as to_avatar, + CASE + WHEN am1.agent_id = ? THEN 'out' + WHEN am2.agent_id = ? THEN 'in' + ELSE 'both' + END as direction + FROM transfers t + LEFT JOIN users u1 ON t.from_user_id = u1.id + LEFT JOIN users u2 ON t.to_user_id = u2.id + LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id + LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id + WHERE ${whereClause} + ORDER BY t.${sortBy} ${sortOrder} + LIMIT ${limitNum} OFFSET ${offset} + `; + + const [transfers] = await getDB().execute(transfersQuery, [agentId, agentId, ...queryParams]); + + // 查询总数 + const countQuery = ` + SELECT COUNT(*) as total + FROM transfers t + LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id + LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id + LEFT JOIN users u1 ON t.from_user_id = u1.id + LEFT JOIN users u2 ON t.to_user_id = u2.id + WHERE ${whereClause} + `; + + const [countResult] = await getDB().execute(countQuery, [agentId, agentId, ...queryParams]); + const total = countResult[0]?.total || 0; + + // 查询统计信息 + const [statsResult] = await getDB().execute(` + SELECT + COUNT(*) as total_transfers, + COUNT(CASE WHEN t.status = 'completed' THEN 1 END) as completed_transfers, + COUNT(CASE WHEN t.status = 'pending' THEN 1 END) as pending_transfers, + COUNT(CASE WHEN t.status = 'failed' THEN 1 END) as failed_transfers, + CAST(COALESCE(SUM(CASE WHEN t.status = 'completed' THEN t.amount ELSE 0 END), 0) AS DECIMAL(10,2)) as total_amount, + CAST(COALESCE(SUM(CASE WHEN t.status = 'completed' AND DATE(t.created_at) = CURDATE() THEN t.amount ELSE 0 END), 0) AS DECIMAL(10,2)) as today_amount, + COUNT(CASE WHEN DATE(t.created_at) = CURDATE() THEN 1 END) as today_transfers + FROM transfers t + LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id + LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id + WHERE (am1.agent_id = ? OR am2.agent_id = ?) + `, [agentId, agentId]); + + const stats = statsResult[0] || { + total_transfers: 0, + completed_transfers: 0, + pending_transfers: 0, + failed_transfers: 0, + total_amount: '0.00', + today_amount: '0.00', + today_transfers: 0 + }; + + res.json({ + success: true, + data: { + transfers, + pagination: { + current_page: pageNum, + per_page: limitNum, + total, + total_pages: Math.ceil(total / limitNum) + }, + stats + } + }); + + } catch (error) { + logger.error('获取转账记录失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取转账记录失败' + }); + } +}); + +/** + * 获取单个转账记录详情 + * GET /api/transfers/:id + */ +router.get('/:id', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const transferId = req.params.id; + + // 查询转账记录详情 + const [transfers] = await getDB().execute(` + SELECT + t.id, + t.from_user_id, + t.to_user_id, + t.amount, + t.type, + t.status, + t.description, + t.transaction_id, + t.created_at, + t.updated_at, + u1.username as from_username, + u1.real_name as from_real_name, + u1.phone as from_phone, + u1.avatar as from_avatar, + u1.city as from_city, + u1.district as from_district, + u2.username as to_username, + u2.real_name as to_real_name, + u2.phone as to_phone, + u2.avatar as to_avatar, + u2.city as to_city, + u2.district as to_district + FROM transfers t + LEFT JOIN users u1 ON t.from_user_id = u1.id + LEFT JOIN users u2 ON t.to_user_id = u2.id + LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id + LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id + WHERE t.id = ? AND (am1.agent_id = ? OR am2.agent_id = ?) + `, [transferId, agentId, agentId]); + + if (transfers.length === 0) { + return res.status(404).json({ + success: false, + message: '转账记录不存在或无权限查看' + }); + } + + const transfer = transfers[0]; + + res.json({ + success: true, + data: transfer + }); + + } catch (error) { + logger.error('获取转账记录详情失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id, + transferId: req.params.id + }); + + res.status(500).json({ + success: false, + message: '获取转账记录详情失败' + }); + } +}); + +/** + * 获取转账趋势数据 + * GET /api/transfers/trend + */ +router.get('/trend/data', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { days = 7, type = 'amount' } = req.query; + + let selectField = 'CAST(COALESCE(SUM(t.amount), 0) AS DECIMAL(10,2)) as value'; + if (type === 'count') { + selectField = 'COUNT(*) as value'; + } + + const [trendData] = await getDB().execute(` + SELECT + DATE(t.created_at) as date, + ${selectField} + FROM transfers t + LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id + LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id + WHERE (am1.agent_id = ? OR am2.agent_id = ?) + AND t.status = 'completed' + AND t.created_at >= DATE_SUB(CURDATE(), INTERVAL ? DAY) + GROUP BY DATE(t.created_at) + ORDER BY date ASC + `, [agentId, agentId, parseInt(days)]); + + res.json({ + success: true, + data: trendData + }); + + } catch (error) { + logger.error('获取转账趋势失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取转账趋势失败' + }); + } +}); + +/** + * 导出转账记录 + * GET /api/transfers/export + */ +router.get('/export/data', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { + format = 'json', + search, + status, + type, + start_date, + end_date, + min_amount, + max_amount + } = req.query; + + // 构建查询条件 + let whereConditions = ['(am1.agent_id = ? OR am2.agent_id = ?)']; + let queryParams = [agentId, agentId]; + + if (search) { + whereConditions.push('(u1.username LIKE ? OR u1.real_name LIKE ? OR u2.username LIKE ? OR u2.real_name LIKE ?)'); + queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`); + } + + if (status) { + whereConditions.push('t.status = ?'); + queryParams.push(status); + } + + if (type) { + whereConditions.push('t.type = ?'); + queryParams.push(type); + } + + if (start_date) { + whereConditions.push('DATE(t.created_at) >= ?'); + queryParams.push(start_date); + } + + if (end_date) { + whereConditions.push('DATE(t.created_at) <= ?'); + queryParams.push(end_date); + } + + if (min_amount) { + whereConditions.push('t.amount >= ?'); + queryParams.push(parseFloat(min_amount)); + } + + if (max_amount) { + whereConditions.push('t.amount <= ?'); + queryParams.push(parseFloat(max_amount)); + } + + const whereClause = whereConditions.join(' AND '); + + // 查询转账记录 + const [transfers] = await getDB().execute(` + SELECT + t.id, + t.amount, + t.type, + t.status, + t.description, + t.transaction_id, + t.created_at, + u1.username as from_username, + u1.real_name as from_real_name, + u1.phone as from_phone, + u2.username as to_username, + u2.real_name as to_real_name, + u2.phone as to_phone + FROM transfers t + LEFT JOIN users u1 ON t.from_user_id = u1.id + LEFT JOIN users u2 ON t.to_user_id = u2.id + LEFT JOIN agent_merchants am1 ON t.from_user_id = am1.merchant_id + LEFT JOIN agent_merchants am2 ON t.to_user_id = am2.merchant_id + WHERE ${whereClause} + ORDER BY t.created_at DESC + `, queryParams); + + if (format === 'csv') { + // 生成CSV格式 + const csvHeader = 'ID,金额,类型,状态,描述,交易ID,转出用户,转出手机,转入用户,转入手机,创建时间\n'; + const csvData = transfers.map(transfer => { + return [ + transfer.id, + transfer.amount, + transfer.type || '', + transfer.status || '', + (transfer.description || '').replace(/,/g, ','), // 替换逗号避免CSV格式问题 + transfer.transaction_id || '', + transfer.from_real_name || transfer.from_username || '', + transfer.from_phone || '', + transfer.to_real_name || transfer.to_username || '', + transfer.to_phone || '', + transfer.created_at || '' + ].join(','); + }).join('\n'); + + res.setHeader('Content-Type', 'text/csv; charset=utf-8'); + res.setHeader('Content-Disposition', `attachment; filename="transfers_${Date.now()}.csv"`); + res.send(csvHeader + csvData); + } else { + // 默认JSON格式 + res.json({ + success: true, + data: transfers, + exported_at: new Date().toISOString(), + total: transfers.length + }); + } + + } catch (error) { + logger.error('导出转账记录失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '导出转账记录失败' + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/upload.js b/routes/upload.js new file mode 100644 index 0000000..62c37e8 --- /dev/null +++ b/routes/upload.js @@ -0,0 +1,474 @@ +const express = require('express'); +const router = express.Router(); +const multer = require('multer'); +const path = require('path'); +const fs = require('fs'); +const { agentAuth } = require('../middleware/agentAuth'); +const { logger } = require('../config/logger'); + +// 确保上传目录存在 +const uploadDir = path.join(__dirname, '../uploads'); +if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); +} + +// 配置multer存储 +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + // 根据文件类型创建不同的子目录 + let subDir = 'others'; + + if (file.fieldname === 'avatar') { + subDir = 'avatars'; + } else if (file.fieldname === 'qr_code') { + subDir = 'qrcodes'; + } else if (file.fieldname === 'id_card_front' || file.fieldname === 'id_card_back') { + subDir = 'idcards'; + } else if (file.fieldname === 'business_license') { + subDir = 'licenses'; + } + + const targetDir = path.join(uploadDir, subDir); + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); + } + + cb(null, targetDir); + }, + filename: function (req, file, cb) { + // 生成唯一文件名 + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); + const ext = path.extname(file.originalname); + cb(null, file.fieldname + '-' + uniqueSuffix + ext); + } +}); + +// 文件过滤器 +const fileFilter = (req, file, cb) => { + // 允许的图片类型 + const allowedImageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp']; + + if (allowedImageTypes.includes(file.mimetype)) { + cb(null, true); + } else { + cb(new Error('只允许上传图片文件 (JPEG, PNG, GIF, WebP)'), false); + } +}; + +// 配置multer +const upload = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 5 * 1024 * 1024, // 5MB限制 + files: 10 // 最多10个文件 + } +}); + +/** + * 单文件上传 + * POST /api/upload/single + */ +router.post('/single', agentAuth, upload.single('file'), async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ + success: false, + message: '请选择要上传的文件' + }); + } + + const file = req.file; + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + logger.info('文件上传成功', { + agentId: req.agent.id, + filename: file.filename, + originalname: file.originalname, + size: file.size, + path: fileUrl + }); + + res.json({ + success: true, + message: '文件上传成功', + data: { + filename: file.filename, + originalname: file.originalname, + size: file.size, + mimetype: file.mimetype, + url: fileUrl, + path: fileUrl // 兼容前端可能使用的字段名 + } + }); + + } catch (error) { + logger.error('文件上传失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: error.message || '文件上传失败' + }); + } +}); + +/** + * 多文件上传 + * POST /api/upload/multiple + */ +router.post('/multiple', agentAuth, upload.array('files', 10), async (req, res) => { + try { + if (!req.files || req.files.length === 0) { + return res.status(400).json({ + success: false, + message: '请选择要上传的文件' + }); + } + + const files = req.files.map(file => { + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + return { + filename: file.filename, + originalname: file.originalname, + size: file.size, + mimetype: file.mimetype, + url: fileUrl, + path: fileUrl + }; + }); + + logger.info('多文件上传成功', { + agentId: req.agent.id, + count: files.length, + totalSize: req.files.reduce((sum, file) => sum + file.size, 0) + }); + + res.json({ + success: true, + message: `成功上传${files.length}个文件`, + data: files + }); + + } catch (error) { + logger.error('多文件上传失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: error.message || '文件上传失败' + }); + } +}); + +/** + * 头像上传 + * POST /api/upload/avatar + */ +router.post('/avatar', agentAuth, upload.single('avatar'), async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ + success: false, + message: '请选择头像文件' + }); + } + + const file = req.file; + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + logger.info('头像上传成功', { + agentId: req.agent.id, + filename: file.filename, + size: file.size + }); + + res.json({ + success: true, + message: '头像上传成功', + data: { + filename: file.filename, + originalname: file.originalname, + size: file.size, + url: fileUrl, + path: fileUrl + } + }); + + } catch (error) { + logger.error('头像上传失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: error.message || '头像上传失败' + }); + } +}); + +/** + * 收款码上传 + * POST /api/upload/qrcode + */ +router.post('/qrcode', agentAuth, upload.single('qr_code'), async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ + success: false, + message: '请选择收款码文件' + }); + } + + const file = req.file; + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + logger.info('收款码上传成功', { + agentId: req.agent.id, + filename: file.filename, + size: file.size + }); + + res.json({ + success: true, + message: '收款码上传成功', + data: { + filename: file.filename, + originalname: file.originalname, + size: file.size, + url: fileUrl, + path: fileUrl + } + }); + + } catch (error) { + logger.error('收款码上传失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: error.message || '收款码上传失败' + }); + } +}); + +/** + * 身份证上传 + * POST /api/upload/idcard + */ +router.post('/idcard', agentAuth, upload.fields([ + { name: 'id_card_front', maxCount: 1 }, + { name: 'id_card_back', maxCount: 1 } +]), async (req, res) => { + try { + if (!req.files || (!req.files.id_card_front && !req.files.id_card_back)) { + return res.status(400).json({ + success: false, + message: '请选择身份证文件' + }); + } + + const result = {}; + + if (req.files.id_card_front) { + const file = req.files.id_card_front[0]; + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + result.front = { + filename: file.filename, + originalname: file.originalname, + size: file.size, + url: fileUrl, + path: fileUrl + }; + } + + if (req.files.id_card_back) { + const file = req.files.id_card_back[0]; + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + result.back = { + filename: file.filename, + originalname: file.originalname, + size: file.size, + url: fileUrl, + path: fileUrl + }; + } + + logger.info('身份证上传成功', { + agentId: req.agent.id, + hasFront: !!result.front, + hasBack: !!result.back + }); + + res.json({ + success: true, + message: '身份证上传成功', + data: result + }); + + } catch (error) { + logger.error('身份证上传失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: error.message || '身份证上传失败' + }); + } +}); + +/** + * 营业执照上传 + * POST /api/upload/license + */ +router.post('/license', agentAuth, upload.single('business_license'), async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ + success: false, + message: '请选择营业执照文件' + }); + } + + const file = req.file; + const relativePath = path.relative(path.join(__dirname, '../'), file.path).replace(/\\/g, '/'); + const fileUrl = `/uploads/${relativePath.split('/').slice(1).join('/')}`; + + logger.info('营业执照上传成功', { + agentId: req.agent.id, + filename: file.filename, + size: file.size + }); + + res.json({ + success: true, + message: '营业执照上传成功', + data: { + filename: file.filename, + originalname: file.originalname, + size: file.size, + url: fileUrl, + path: fileUrl + } + }); + + } catch (error) { + logger.error('营业执照上传失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: error.message || '营业执照上传失败' + }); + } +}); + +/** + * 删除文件 + * DELETE /api/upload/file + */ +router.delete('/file', agentAuth, async (req, res) => { + try { + const { path: filePath } = req.body; + + if (!filePath) { + return res.status(400).json({ + success: false, + message: '请提供文件路径' + }); + } + + // 构建完整的文件路径 + const fullPath = path.join(__dirname, '../', filePath); + + // 检查文件是否存在 + if (!fs.existsSync(fullPath)) { + return res.status(404).json({ + success: false, + message: '文件不存在' + }); + } + + // 删除文件 + fs.unlinkSync(fullPath); + + logger.info('文件删除成功', { + agentId: req.agent.id, + filePath: filePath + }); + + res.json({ + success: true, + message: '文件删除成功' + }); + + } catch (error) { + logger.error('文件删除失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '文件删除失败' + }); + } +}); + +// 错误处理中间件 +router.use((error, req, res, next) => { + if (error instanceof multer.MulterError) { + if (error.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ + success: false, + message: '文件大小超过限制(最大5MB)' + }); + } + if (error.code === 'LIMIT_FILE_COUNT') { + return res.status(400).json({ + success: false, + message: '文件数量超过限制(最多10个)' + }); + } + if (error.code === 'LIMIT_UNEXPECTED_FILE') { + return res.status(400).json({ + success: false, + message: '意外的文件字段' + }); + } + } + + res.status(500).json({ + success: false, + message: error.message || '文件上传失败' + }); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..36ece8e --- /dev/null +++ b/routes/users.js @@ -0,0 +1,354 @@ +const express = require('express'); +const router = express.Router(); +const { getDB } = require('../database'); +const { agentAuth } = require('../middleware/agentAuth'); +const { logger } = require('../config/logger'); + +/** + * 获取代理下级用户列表 + * GET /api/users + */ +router.get('/', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { + page = 1, + limit = 20, + search, + role, + sort_by = 'created_at', + sort_order = 'desc', + city, + district + } = req.query; + + const pageNum = parseInt(page) || 1; + const limitNum = parseInt(limit) || 20; + const offset = (pageNum - 1) * limitNum; + + // 构建查询条件 + let whereConditions = ['am.agent_id = ?']; + let queryParams = [agentId]; + + if (search) { + whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ? OR u.phone LIKE ?)'); + queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`); + } + + if (role) { + whereConditions.push('u.role = ?'); + queryParams.push(role); + } + + if (city) { + whereConditions.push('u.city = ?'); + queryParams.push(city); + } + + if (district) { + whereConditions.push('u.district = ?'); + queryParams.push(district); + } + + const whereClause = whereConditions.join(' AND '); + + // 验证排序字段 + const allowedSortFields = ['created_at', 'updated_at', 'balance', 'username', 'real_name']; + const sortBy = allowedSortFields.includes(sort_by) ? sort_by : 'created_at'; + const sortOrder = sort_order.toLowerCase() === 'asc' ? 'ASC' : 'DESC'; + + // 查询用户列表 + const usersQuery = ` + SELECT + u.id, + u.username, + u.real_name, + u.phone, + u.email, + u.avatar, + u.role, + u.city, + u.district, + u.account_type, + u.balance, + u.points, + u.status, + u.last_login_at, + u.created_at, + u.updated_at, + am.created_at as join_date, + ( + SELECT CAST(COALESCE(SUM(amount), 0) AS DECIMAL(10,2)) + FROM transfers + WHERE from_user_id = u.id + AND DATE(created_at) = CURDATE() + ) as today_transfer_out, + ( + SELECT CAST(COALESCE(SUM(amount), 0) AS DECIMAL(10,2)) + FROM transfers + WHERE to_user_id = u.id + AND DATE(created_at) = CURDATE() + ) as today_transfer_in + FROM agent_merchants am + LEFT JOIN users u ON am.merchant_id = u.id + WHERE ${whereClause} + ORDER BY u.${sortBy} ${sortOrder} + LIMIT ${limitNum} OFFSET ${offset} + `; + + const [users] = await getDB().execute(usersQuery, queryParams); + + // 查询总数 + const countQuery = ` + SELECT COUNT(*) as total + FROM agent_merchants am + LEFT JOIN users u ON am.merchant_id = u.id + WHERE ${whereClause} + `; + + const [countResult] = await getDB().execute(countQuery, queryParams); + const total = countResult[0]?.total || 0; + + // 查询统计信息 + const [statsResult] = await getDB().execute(` + SELECT + COUNT(*) as total_users, + COUNT(CASE WHEN u.status = 'active' THEN 1 END) as active_users, + CAST(COALESCE(SUM(u.balance), 0) AS DECIMAL(10,2)) as total_balance, + COUNT(CASE WHEN DATE(am.created_at) = CURDATE() THEN 1 END) as today_new_users + FROM agent_merchants am + LEFT JOIN users u ON am.merchant_id = u.id + WHERE am.agent_id = ? + `, [agentId]); + + const stats = statsResult[0] || { + total_users: 0, + active_users: 0, + total_balance: '0.00', + today_new_users: 0 + }; + + res.json({ + success: true, + data: { + users, + pagination: { + current_page: pageNum, + per_page: limitNum, + total, + total_pages: Math.ceil(total / limitNum) + }, + stats + } + }); + + } catch (error) { + logger.error('获取用户列表失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '获取用户列表失败' + }); + } +}); + +/** + * 获取单个用户详情 + * GET /api/users/:id + */ +router.get('/:id', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const userId = req.params.id; + + // 验证用户是否属于当前代理 + const [users] = await getDB().execute(` + SELECT + u.id, + u.username, + u.real_name, + u.phone, + u.email, + u.avatar, + u.role, + u.city, + u.district, + u.account_type, + u.balance, + u.points, + u.status, + u.id_card, + u.business_license, + u.payment_qr_code, + u.last_login_at, + u.created_at, + u.updated_at, + am.created_at as join_date + FROM agent_merchants am + LEFT JOIN users u ON am.merchant_id = u.id + WHERE am.agent_id = ? AND u.id = ? + `, [agentId, userId]); + + if (users.length === 0) { + return res.status(404).json({ + success: false, + message: '用户不存在或不属于当前代理' + }); + } + + const user = users[0]; + + // 获取用户转账统计 + const [transferStats] = await getDB().execute(` + SELECT + COUNT(*) as total_transfers, + CAST(COALESCE(SUM(CASE WHEN from_user_id = ? THEN amount ELSE 0 END), 0) AS DECIMAL(10,2)) as total_transfer_out, + CAST(COALESCE(SUM(CASE WHEN to_user_id = ? THEN amount ELSE 0 END), 0) AS DECIMAL(10,2)) as total_transfer_in, + COUNT(CASE WHEN DATE(created_at) = CURDATE() THEN 1 END) as today_transfers, + CAST(COALESCE(SUM(CASE WHEN from_user_id = ? AND DATE(created_at) = CURDATE() THEN amount ELSE 0 END), 0) AS DECIMAL(10,2)) as today_transfer_out, + CAST(COALESCE(SUM(CASE WHEN to_user_id = ? AND DATE(created_at) = CURDATE() THEN amount ELSE 0 END), 0) AS DECIMAL(10,2)) as today_transfer_in + FROM transfers + WHERE from_user_id = ? OR to_user_id = ? + `, [userId, userId, userId, userId, userId, userId]); + + user.transfer_stats = transferStats[0] || { + total_transfers: 0, + total_transfer_out: '0.00', + total_transfer_in: '0.00', + today_transfers: 0, + today_transfer_out: '0.00', + today_transfer_in: '0.00' + }; + + res.json({ + success: true, + data: user + }); + + } catch (error) { + logger.error('获取用户详情失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id, + userId: req.params.id + }); + + res.status(500).json({ + success: false, + message: '获取用户详情失败' + }); + } +}); + +/** + * 导出用户数据 + * GET /api/users/export + */ +router.get('/export/data', agentAuth, async (req, res) => { + try { + const agentId = req.agent.id; + const { format = 'json', search, role, city, district } = req.query; + + // 构建查询条件 + let whereConditions = ['am.agent_id = ?']; + let queryParams = [agentId]; + + if (search) { + whereConditions.push('(u.username LIKE ? OR u.real_name LIKE ? OR u.phone LIKE ?)'); + queryParams.push(`%${search}%`, `%${search}%`, `%${search}%`); + } + + if (role) { + whereConditions.push('u.role = ?'); + queryParams.push(role); + } + + if (city) { + whereConditions.push('u.city = ?'); + queryParams.push(city); + } + + if (district) { + whereConditions.push('u.district = ?'); + queryParams.push(district); + } + + const whereClause = whereConditions.join(' AND '); + + // 查询用户数据 + const [users] = await getDB().execute(` + SELECT + u.id, + u.username, + u.real_name, + u.phone, + u.email, + u.role, + u.city, + u.district, + u.account_type, + u.balance, + u.points, + u.status, + u.created_at, + am.created_at as join_date + FROM agent_merchants am + LEFT JOIN users u ON am.merchant_id = u.id + WHERE ${whereClause} + ORDER BY u.created_at DESC + `, queryParams); + + if (format === 'csv') { + // 生成CSV格式 + const csvHeader = 'ID,用户名,真实姓名,手机号,邮箱,角色,城市,地区,账户类型,余额,积分,状态,注册时间,加入时间\n'; + const csvData = users.map(user => { + return [ + user.id, + user.username || '', + user.real_name || '', + user.phone || '', + user.email || '', + user.role || '', + user.city || '', + user.district || '', + user.account_type || '', + user.balance || '0.00', + user.points || 0, + user.status || '', + user.created_at || '', + user.join_date || '' + ].join(','); + }).join('\n'); + + res.setHeader('Content-Type', 'text/csv; charset=utf-8'); + res.setHeader('Content-Disposition', `attachment; filename="users_${Date.now()}.csv"`); + res.send(csvHeader + csvData); + } else { + // 默认JSON格式 + res.json({ + success: true, + data: users, + exported_at: new Date().toISOString(), + total: users.length + }); + } + + } catch (error) { + logger.error('导出用户数据失败', { + error: error.message, + stack: error.stack, + agentId: req.agent?.id + }); + + res.status(500).json({ + success: false, + message: '导出用户数据失败' + }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..699bfa4 --- /dev/null +++ b/server.js @@ -0,0 +1,196 @@ +// 加载环境变量配置 +require('dotenv').config(); + +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const path = require('path'); +const mysql = require('mysql2/promise'); +const rateLimit = require('express-rate-limit'); +const helmet = require('helmet'); +const { initDB, getDB, dbConfig } = require('./database'); +const { logger } = require('./config/logger'); +const { errorHandler, notFound } = require('./middleware/errorHandler'); +const fs = require('fs'); + +const app = express(); +const PORT = process.env.AGENT_PORT || 3001; + +// 确保日志目录存在 +const logDir = path.join(__dirname, 'logs'); +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); +} + +// 安全中间件 +app.use(helmet({ + contentSecurityPolicy: false, // 为了支持前端应用 + crossOriginEmbedderPolicy: false, + crossOriginOpenerPolicy: false, // 禁用 COOP 头部以避免非 HTTPS 环境的警告 + originAgentCluster: false // 禁用Origin-Agent-Cluster头部 +})); + +// 中间件配置 +// CORS配置 - 允许代理前端访问 +app.use(cors({ + origin: [ + 'http://localhost:5173', + 'http://localhost:5176', + 'http://localhost:5174', + 'http://localhost:3002', + 'https://agent.zrbjr.com', + 'https://www.zrbjr.com', + 'https://zrbjr.com' + ], + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] +})); +app.use(bodyParser.json({ limit: '10mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); + +// 请求日志中间件 +app.use((req, res, next) => { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + + // 只记录非正常状态码的请求日志(过滤掉200、304等正常返回) + if (res.statusCode >= 400 || res.statusCode < 200) { + logger.info('HTTP Request', { + method: req.method, + url: req.originalUrl, + statusCode: res.statusCode, + duration: `${duration}ms`, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + } + }); + + next(); +}); + +// 限流中间件 +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15分钟 + max: 1000, // 限制每个IP 15分钟内最多1000个请求 + message: { + success: false, + error: { + code: 'RATE_LIMIT_EXCEEDED', + message: '请求过于频繁,请稍后再试' + } + } +}); +app.use('/api', limiter); + +// 静态文件服务 - 为代理后台前端提供服务 +app.use('/agent-admin', express.static(path.join(__dirname, 'agent-admin/dist'), { + setHeaders: (res, filePath) => { + // 设置CORS头部 + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + // 设置缓存策略 + if (filePath.includes('.js') || filePath.includes('.css')) { + res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年缓存 + } else { + res.setHeader('Cache-Control', 'public, max-age=86400'); // 1天缓存 + } + } +})); + +// 上传文件静态服务 +app.use('/uploads', express.static(path.join(__dirname, 'uploads'), { + setHeaders: (res, filePath) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + } +})); + +// 代理后端共用现有数据库,无需初始化 + +// API路由 - 代理专用路由 +app.use('/api/auth', require('./routes/auth')); +app.use('/api/agent', require('./routes/agent')); +app.use('/api/users', require('./routes/users')); +app.use('/api/transfers', require('./routes/transfers')); +app.use('/api/commissions', require('./routes/commissions')); +app.use('/api/upload', require('./routes/upload')); + +// 代理后台首页路由 +app.get('/', (req, res) => { + res.json({ + message: '炬融圈代理后台API服务', + version: '1.0.0', + status: 'running' + }); +}); + +// 代理后台前端路由 +app.get('/agent-admin*', (req, res) => { + res.sendFile(path.join(__dirname, 'agent-admin/dist/index.html')); +}); + +// 404处理 +app.use(notFound); + +// 错误处理 +app.use(errorHandler); + +// 导出app供测试使用 +module.exports = { + app, getDB +}; + +// 启动服务器 +app.listen(PORT, async () => { + try { + // 初始化数据库连接 + await initDB(); + console.log(`代理后台API服务器运行在端口 ${PORT}`); + console.log(`代理后台管理界面: http://localhost:${PORT}/agent-admin`); + + // 代理后端共用现有数据库,无需初始化表结构 + + } catch (error) { + console.error('服务器启动失败:', error); + process.exit(1); + } +}); + +// 优雅关闭处理 +process.on('SIGTERM', async () => { + console.log('收到SIGTERM信号,正在关闭服务器...'); + try { + await closeDB(); + console.log('数据库连接已关闭'); + } catch (error) { + console.error('关闭数据库连接时出错:', error); + } + process.exit(0); +}); + +process.on('SIGINT', async () => { + console.log('收到SIGINT信号,正在关闭服务器...'); + try { + await closeDB(); + console.log('数据库连接已关闭'); + } catch (error) { + console.error('关闭数据库连接时出错:', error); + } + process.exit(0); +}); + +process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', reason); +}); + +process.on('uncaughtException', (error) => { + console.error('未捕获的异常:', error); + process.exit(1); +}); \ No newline at end of file