From 2206faefe02fbcee541c6806dfc4099cf566e6ce Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:10:56 +0800 Subject: [PATCH 01/13] ADD file via upload --- server.js | 279 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 server.js diff --git a/server.js b/server.js new file mode 100644 index 0000000..68dd165 --- /dev/null +++ b/server.js @@ -0,0 +1,279 @@ +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +// 导入工具类 +const UserManager = require('./utils/user-manager'); +const MultiEmailService = require('./utils/multi-email-service'); +const MathQuestionGenerator = require('./utils/question-generator'); + +const app = express(); +const PORT = 8080; + +// 中间件 +app.use(cors()); +app.use(bodyParser.json()); +app.use(express.static(path.join(__dirname, '../frontend'))); + +// 实例化管理器 +const userManager = new UserManager(); +const emailConfig = require('./email-config'); +const emailService = new MultiEmailService(); + +// 存储验证码(内存存储,重启后失效) +const verificationCodes = new Map(); + +// 生成验证码 +function generateVerificationCode() { + return Math.floor(100000 + Math.random() * 900000).toString(); +} + +// 验证邮箱格式 +function validateEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); +} + +// API路由 + +// 健康检查 +app.get('/api/health', (req, res) => { + res.json({ + ok: true, + message: '服务正常运行', + emailService: emailService.transporter ? '已配置' : '测试模式' + }); +}); + +// 发送验证码 +app.post('/api/send-code', async (req, res) => { + try { + const { email, username } = req.body; + + console.log('收到发送验证码请求:', { email, username }); + + if (!email || !username) { + return res.json({ ok: false, message: '邮箱和用户名不能为空' }); + } + + // 验证邮箱格式 + if (!validateEmail(email)) { + return res.json({ ok: false, message: '邮箱格式不正确' }); + } + + // 检查用户名是否已存在 + if (userManager.findUserByUsername(username)) { + return res.json({ ok: false, message: '用户名已存在' }); + } + + // 检查邮箱是否已注册 + if (userManager.findUserByEmail(email)) { + return res.json({ ok: false, message: '邮箱已被注册' }); + } + + const code = generateVerificationCode(); + verificationCodes.set(email, { + code, + username, + timestamp: Date.now() + }); + + console.log(`为邮箱 ${email} 生成验证码: ${code}`); + + // 发送邮件 + const emailResult = await emailService.sendVerificationCode(email, code, username); + + if (emailResult.success) { + res.json({ ok: true, message: emailResult.message }); + } else { + // 如果邮件发送失败,但在开发模式下提供了验证码,仍然算成功 + if (emailResult.debug) { + res.json({ ok: true, message: emailResult.message + ' ' + emailResult.debug }); + } else { + res.json({ ok: false, message: emailResult.message }); + } + } + } catch (error) { + console.error('发送验证码错误:', error); + res.json({ ok: false, message: '服务器内部错误' }); + } +}); + +// 注册 +app.post('/api/register', (req, res) => { + try { + const { email, username, code } = req.body; + + console.log('收到注册请求:', { email, username, code }); + + if (!email || !username || !code) { + return res.json({ ok: false, message: '请填写完整信息' }); + } + + const storedData = verificationCodes.get(email); + + if (!storedData) { + return res.json({ ok: false, message: '请先获取验证码' }); + } + + // 验证码10分钟过期 + if (Date.now() - storedData.timestamp > 10 * 60 * 1000) { + verificationCodes.delete(email); + return res.json({ ok: false, message: '验证码已过期,请重新获取' }); + } + + if (storedData.code !== code) { + return res.json({ ok: false, message: '验证码不正确' }); + } + + if (storedData.username !== username) { + return res.json({ ok: false, message: '用户名与验证时不一致' }); + } + + // 创建用户 + userManager.createUser(email, username); + + // 清除验证码 + verificationCodes.delete(email); + + res.json({ ok: true, message: '注册成功,请设置密码' }); + } catch (error) { + console.error('注册错误:', error); + res.json({ ok: false, message: error.message }); + } +}); + +// 设置密码 +app.post('/api/set-password', (req, res) => { + try { + const { email, password } = req.body; + + if (!email || !password) { + return res.json({ ok: false, message: '请填写完整信息' }); + } + + userManager.setPassword(email, password); + res.json({ ok: true, message: '密码设置成功' }); + } catch (error) { + console.error('设置密码错误:', error); + res.json({ ok: false, message: error.message }); + } +}); + +// 登录 +app.post('/api/login', (req, res) => { + try { + const { account, password } = req.body; + + if (!account || !password) { + return res.json({ ok: false, message: '请填写账号和密码' }); + } + + // 通过邮箱或用户名查找用户 + let user = userManager.findUserByEmail(account); + if (!user) { + user = userManager.findUserByUsername(account); + } + + if (!user || !user.hasPassword) { + return res.json({ ok: false, message: '用户不存在' }); + } + + if (!userManager.verifyPassword(user.email, password)) { + return res.json({ ok: false, message: '密码不正确' }); + } + + res.json({ + ok: true, + message: '登录成功', + data: { + email: user.email, + username: user.username + } + }); + } catch (error) { + console.error('登录错误:', error); + res.json({ ok: false, message: error.message }); + } +}); + +// 修改密码 +app.post('/api/change-password', (req, res) => { + try { + const { email, oldPassword, newPassword } = req.body; + + if (!email || !oldPassword || !newPassword) { + return res.json({ ok: false, message: '请填写完整信息' }); + } + + userManager.changePassword(email, oldPassword, newPassword); + res.json({ ok: true, message: '密码修改成功' }); + } catch (error) { + console.error('修改密码错误:', error); + res.json({ ok: false, message: error.message }); + } +}); + +// 修改用户名 +app.post('/api/change-username', (req, res) => { + try { + const { email, username } = req.body; + + if (!email || !username) { + return res.json({ ok: false, message: '请填写完整信息' }); + } + + userManager.changeUsername(email, username); + res.json({ ok: true, message: '用户名修改成功' }); + } catch (error) { + console.error('修改用户名错误:', error); + res.json({ ok: false, message: error.message }); + } +}); + +// 获取题目 +app.get('/api/questions', (req, res) => { + try { + const { grade, count } = req.query; + + if (!grade || !count) { + return res.json({ ok: false, message: '请选择年级和题目数量' }); + } + + const countNum = parseInt(count); + if (isNaN(countNum) || countNum < 10 || countNum > 30) { + return res.json({ ok: false, message: '题目数量需在10-30之间' }); + } + + const generator = new MathQuestionGenerator(); + const questions = generator.generateQuestions(grade, countNum); + res.json({ + ok: true, + data: questions, + message: '题目生成成功' + }); + } catch (error) { + console.error('生成题目失败:', error); + res.json({ ok: false, message: '生成题目失败' }); + } +}); + +// 提供前端页面 +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '../frontend/index.html')); +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`========================================`); + console.log(`🎓 数学学习软件后端服务已启动`); + console.log(`📍 服务地址: http://localhost:${PORT}`); + console.log(`🌐 前端页面: http://localhost:${PORT}/`); + console.log(`🔧 API文档: http://localhost:${PORT}/api/health`); + console.log(`📧 邮箱服务: ${emailService.transporter ? '已配置' : '测试模式'}`); + console.log(`========================================`); +}); + +module.exports = app; \ No newline at end of file -- 2.34.1 From ed4d540e8a7d8df6cdf8ab0b38fe2a46ed97c673 Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:11:26 +0800 Subject: [PATCH 02/13] ADD file via upload --- package-lock.json | 1243 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1243 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..01a37dc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1243 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "math-learning-backend", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } + }, + "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/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/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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "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/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==", + "dev": true, + "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/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-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/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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "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/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/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/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/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/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/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/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/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/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/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/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/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/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/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-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": "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/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/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/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/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/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/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/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-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-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/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "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/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/nodemailer": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "license": "MIT-0", + "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.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/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/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-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/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/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/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/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/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/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/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/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "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/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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "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-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/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/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/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/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/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/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/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/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" + } + } + } +} -- 2.34.1 From b01bd6e8f3fc43c5ae49f9f9d2bab79a410797c7 Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:11:32 +0800 Subject: [PATCH 03/13] ADD file via upload --- package.json | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..f7947b2 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "test": "echo \"后端服务测试模式\" && node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "body-parser": "^1.20.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": [ + "education", + "math", + "learning", + "email", + "qq", + "163" + ], + "author": "数学学习软件团队", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/your-repo/math-learning-software" + } +} \ No newline at end of file -- 2.34.1 From 4e5ee3537a4e38b9ef3fda4c706f9b7eb4bfc7dd Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:11:38 +0800 Subject: [PATCH 04/13] ADD file via upload --- email-config.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 email-config.js diff --git a/email-config.js b/email-config.js new file mode 100644 index 0000000..ac8293c --- /dev/null +++ b/email-config.js @@ -0,0 +1,30 @@ +// ============================================= +// ѧѧϰ - ļ +// ============================================= + + +// ==================== ѡ1: QQ ==================== +module.exports = { + service: 'qq', + auth: { + user: '3454934335@qq.com', // 滻ΪQQ + pass: 'vxhsswmmqiyvchhh' // 滻ΪQQȨ루16λ + } +}; + +// ==================== ѡ2: 163 ==================== +module.exports = { + service: '163', + auth: { + user: '18950579895@163.com',// 滻Ϊ163 + pass: 'UCgJrE7yzzd4Uz3g' // 滻Ϊ163Ȩ + } +}; + + +// ==================== ѡ3: ģʽ ==================== +// ʵʼ֤ڿ̨ʾ +// ڿͲԻ +/* +module.exports = {}; +*/ -- 2.34.1 From 7e86f0a4d4601d102a1dcdce4fe7512b58941d5e Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:12:19 +0800 Subject: [PATCH 05/13] ADD file via upload --- utils/multi-email-service.js | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 utils/multi-email-service.js diff --git a/utils/multi-email-service.js b/utils/multi-email-service.js new file mode 100644 index 0000000..f7fae43 --- /dev/null +++ b/utils/multi-email-service.js @@ -0,0 +1,111 @@ +const nodemailer = require('nodemailer'); + +class MultiEmailService { + constructor() { + this.transporters = new Map(); + this.initTransporters(); + } + + initTransporters() { + // QQ邮箱配置 + try { + const qqTransporter = nodemailer.createTransport({ + service: 'qq', + auth: { + user: '3454934335@qq.com', + pass: 'vxhsswmmqiyvchhh' + } + }); + this.transporters.set('qq', qqTransporter); + console.log('✅ QQ邮箱服务已初始化'); + } catch (error) { + console.error('❌ QQ邮箱初始化失败:', error); + } + + // 163邮箱配置 + try { + const mail163Transporter = nodemailer.createTransport({ + service: '163', + auth: { + user: '18950579895@163.com', + pass: 'UCgJrE7yzzd4Uz3g' + } + }); + this.transporters.set('163', mail163Transporter); + console.log('✅ 163邮箱服务已初始化'); + } catch (error) { + console.error('❌ 163邮箱初始化失败:', error); + } + } + + async sendVerificationCode(email, code, username) { + // 根据邮箱域名选择发件箱 + let transporter; + if (email.includes('@qq.com')) { + transporter = this.transporters.get('qq'); + } else if (email.includes('@163.com')) { + transporter = this.transporters.get('163'); + } else { + // 默认使用QQ邮箱 + transporter = this.transporters.get('qq'); + } + + if (!transporter) { + console.log(`🔧 测试模式 - 邮箱: ${email}, 验证码: ${code}`); + return { + success: true, + message: '验证码已生成(测试模式)', + debug: `验证码: ${code}` + }; + } + + const mailOptions = { + from: transporter.options.auth.user, + to: email, + subject: '数学学习软件 - 注册验证码', + html: this.generateEmailTemplate(code, username) + }; + + try { + await transporter.sendMail(mailOptions); + console.log(`✅ 验证码邮件已发送到: ${email}`); + return { success: true, message: '验证码已发送到您的邮箱' }; + } catch (error) { + console.error('❌ 邮件发送失败:', error); + return { + success: false, + message: '邮件发送失败,请稍后重试', + debug: `验证码: ${code} (请使用此验证码完成注册)` + }; + } + } + + generateEmailTemplate(code, username) { + return ` + + + + + + + +
+

🎓 数学学习软件

+

亲爱的 ${username},您好!

+

您正在注册数学学习软件,验证码为:

+
${code}
+

验证码有效期为10分钟,请尽快完成注册。

+

如果这不是您本人的操作,请忽略此邮件。

+
+ + + `; + } +} + +module.exports = MultiEmailService; \ No newline at end of file -- 2.34.1 From 727a9f670be2440c3c2ef2e7ebf06bce2945f4e6 Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:12:33 +0800 Subject: [PATCH 06/13] ADD file via upload --- utils/question-generator.js | 475 ++++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 utils/question-generator.js diff --git a/utils/question-generator.js b/utils/question-generator.js new file mode 100644 index 0000000..17c1852 --- /dev/null +++ b/utils/question-generator.js @@ -0,0 +1,475 @@ +const fs = require('fs'); +const path = require('path'); + +class MathQuestionGenerator { + constructor() { + // 不再保存已使用的题目到文件 + // 只在内存中记录当前试卷的题目,试卷完成后自动清除 + this.currentSessionQuestions = new Set(); + } + + generateQuestions(grade, count) { + console.log(`正在生成${grade} ${count}道题目...`); + const questions = []; + this.currentSessionQuestions.clear(); // 清除上一份试卷的记录 + + const maxAttempts = count * 20; // 防止无限循环 + let attempts = 0; + + while (questions.length < count && attempts < maxAttempts) { + attempts++; + const question = this.generateQuestion(grade); + if (!question) continue; + + const questionKey = `${grade}-${question.stem}`; + + // 只检查当前试卷内是否重复 + if (!this.currentSessionQuestions.has(questionKey)) { + questions.push(question); + this.currentSessionQuestions.add(questionKey); + console.log(`✅ 生成第${questions.length}题: ${question.stem}`); + } + } + + if (questions.length < count) { + console.warn(`⚠️ 只生成了${questions.length}道题目,未能达到要求的${count}道`); + } + + return questions; + } + + generateQuestion(grade) { + try { + switch (grade) { + case '小学': + return this.generatePrimaryQuestion(); + case '初中': + return this.generateMiddleSchoolQuestion(); + case '高中': + return this.generateHighSchoolQuestion(); + default: + return this.generatePrimaryQuestion(); + } + } catch (error) { + console.error(`生成${grade}题目时出错:`, error); + return null; + } + } + + generatePrimaryQuestion() { + // 小学题目:2-5个操作数,只能有+,-,*,/,不能有负数,结果整数 + const operations = ['+', '-', '×', '÷']; + const numOperands = Math.floor(Math.random() * 4) + 2; // 2-5个操作数 + + let expression = ''; + let correctAnswer = 0; + let attempts = 0; + + do { + expression = ''; + correctAnswer = 0; + let isValid = true; + + for (let i = 0; i < numOperands; i++) { + const num = Math.floor(Math.random() * 50) + 1; // 1-50 + if (i === 0) { + expression = num.toString(); + correctAnswer = num; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + + if (op === '÷') { + // 确保除法结果是整数 + const divisors = this.getDivisors(correctAnswer); + if (divisors.length > 0) { + const divisor = divisors[Math.floor(Math.random() * divisors.length)]; + expression += ` ÷ ${divisor}`; + correctAnswer = correctAnswer / divisor; + } else { + expression += ` × ${num}`; + correctAnswer = correctAnswer * num; + } + } else if (op === '-') { + // 确保减法结果非负 + if (correctAnswer > num) { + expression += ` - ${num}`; + correctAnswer -= num; + } else { + expression += ` + ${num}`; + correctAnswer += num; + } + } else { + expression += ` ${op} ${num}`; + if (op === '+') correctAnswer += num; + if (op === '×') correctAnswer *= num; + } + } + } + + // 验证结果是否符合要求 + if (correctAnswer < 0 || !Number.isInteger(correctAnswer)) { + isValid = false; + } + + attempts++; + if (isValid) break; + + } while (attempts < 10); + + // 如果仍然无法生成有效题目,使用简单题目 + if (correctAnswer < 0 || !Number.isInteger(correctAnswer)) { + expression = "10 + 5"; + correctAnswer = 15; + } + + const options = this.generateOptions(correctAnswer, 4, false, false); + + return { + id: `primary-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + generateMiddleSchoolQuestion() { + // 初中题目:1-5个操作数,必须包含至少一个平方或开根号运算符 + const types = ['square', 'sqrt', 'mixed_square', 'mixed_sqrt', 'power', 'combined']; + const type = types[Math.floor(Math.random() * types.length)]; + + // 1-5个操作数 + const numOperands = Math.floor(Math.random() * 5) + 1; + + let stem, correctAnswer; + + if (type === 'square') { + // 纯平方运算 + let expression = ''; + let result = 0; + const operations = ['+', '-', '×']; + + for (let i = 0; i < numOperands; i++) { + const base = Math.floor(Math.random() * 10) + 1; + if (i === 0) { + expression = `${base}²`; + result = base * base; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${base}²`; + if (op === '+') result += base * base; + else if (op === '-') result -= base * base; + else if (op === '×') result *= base * base; + } + } + stem = `计算:${expression}`; + correctAnswer = result; + } else if (type === 'sqrt') { + // 纯开根号运算 + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]; + let expression = ''; + let result = 0; + const operations = ['+', '-', '×']; + + for (let i = 0; i < numOperands; i++) { + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + if (i === 0) { + expression = `√${num}`; + result = Math.sqrt(num); + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} √${num}`; + const sqrtValue = Math.sqrt(num); + if (op === '+') result += sqrtValue; + else if (op === '-') result -= sqrtValue; + else if (op === '×') result *= sqrtValue; + } + } + stem = `计算:${expression}`; + correctAnswer = result; + } else if (type === 'mixed_square') { + // 混合运算,包含平方 + let expression = ''; + let result = 0; + const operations = ['+', '-', '×', '÷']; + + // 确保至少有一个平方运算 + const squarePosition = Math.floor(Math.random() * numOperands); + + for (let i = 0; i < numOperands; i++) { + let term; + let value; + + if (i === squarePosition) { + // 插入平方项 + const base = Math.floor(Math.random() * 10) + 1; + term = `${base}²`; + value = base * base; + } else { + // 普通数字 + const num = Math.floor(Math.random() * 20) + 1; + term = num.toString(); + value = num; + } + + if (i === 0) { + expression = term; + result = value; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + if (op === '+') result += value; + else if (op === '-') result -= value; + else if (op === '×') result *= value; + else if (op === '÷') result /= value; + } + } + + stem = `计算:${expression}`; + correctAnswer = result; + } else if (type === 'mixed_sqrt') { + // 混合运算,包含开根号 + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144]; + let expression = ''; + let result = 0; + const operations = ['+', '-', '×', '÷']; + + // 确保至少有一个开根号运算 + const sqrtPosition = Math.floor(Math.random() * numOperands); + + for (let i = 0; i < numOperands; i++) { + let term; + let value; + + if (i === sqrtPosition) { + // 插入开根号项 + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + term = `√${num}`; + value = Math.sqrt(num); + } else { + // 普通数字 + const num = Math.floor(Math.random() * 20) + 1; + term = num.toString(); + value = num; + } + + if (i === 0) { + expression = term; + result = value; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + if (op === '+') result += value; + else if (op === '-') result -= value; + else if (op === '×') result *= value; + else if (op === '÷') result /= value; + } + } + + stem = `计算:${expression}`; + correctAnswer = result; + } else if (type === 'power') { + // 幂运算(平方) + const base = Math.floor(Math.random() * 10) + 1; + + if (numOperands === 1) { + stem = `计算:${base}²`; + correctAnswer = base * base; + } else { + let expression = `${base}²`; + let result = base * base; + const operations = ['+', '-', '×', '÷']; + + for (let i = 1; i < numOperands; i++) { + const num = Math.floor(Math.random() * 20) + 1; + const op = operations[Math.floor(Math.random() * operations.length)]; + + expression += ` ${op} ${num}`; + + if (op === '+') result += num; + else if (op === '-') result -= num; + else if (op === '×') result *= num; + else if (op === '÷') result /= num; + } + + stem = `计算:${expression}`; + correctAnswer = result; + } + } else { + // 组合运算(同时包含平方和开根号) + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; + let expression = ''; + let result = 0; + const operations = ['+', '-', '×', '÷']; + + // 确保至少有一个平方和一个开根号 + const squarePosition = Math.floor(Math.random() * (numOperands - 1)); + const sqrtPosition = squarePosition + 1; + + for (let i = 0; i < numOperands; i++) { + let term; + let value; + + if (i === squarePosition) { + // 插入平方项 + const base = Math.floor(Math.random() * 10) + 1; + term = `${base}²`; + value = base * base; + } else if (i === sqrtPosition) { + // 插入开根号项 + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + term = `√${num}`; + value = Math.sqrt(num); + } else { + // 普通数字 + const num = Math.floor(Math.random() * 20) + 1; + term = num.toString(); + value = num; + } + + if (i === 0) { + expression = term; + result = value; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + if (op === '+') result += value; + else if (op === '-') result -= value; + else if (op === '×') result *= value; + else if (op === '÷') result /= value; + } + } + + stem = `计算:${expression}`; + correctAnswer = result; + } + + // 确保答案是数字且合理 + if (typeof correctAnswer === 'number') { + correctAnswer = Math.round(correctAnswer * 100) / 100; // 保留两位小数 + } + + const options = this.generateOptions(correctAnswer, 4, true, false); + + return { + id: `middle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + stem: stem, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + generateHighSchoolQuestion() { + // 高中题目:1-5个操作数,必须包含sin,cos,tan,可以有小数 + const functions = ['sin', 'cos', 'tan']; + const func = functions[Math.floor(Math.random() * functions.length)]; + const angles = [0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 240, 270, 300, 315, 330, 360]; + const angle = angles[Math.floor(Math.random() * angles.length)]; + + let correctAnswer; + switch (func) { + case 'sin': + correctAnswer = Math.round(Math.sin(angle * Math.PI / 180) * 100) / 100; + break; + case 'cos': + correctAnswer = Math.round(Math.cos(angle * Math.PI / 180) * 100) / 100; + break; + case 'tan': + if (angle % 180 === 90) { + correctAnswer = '不存在'; + } else { + correctAnswer = Math.round(Math.tan(angle * Math.PI / 180) * 100) / 100; + } + break; + } + + let stem = `计算:${func}(${angle}°)`; + + // 添加额外操作数(0-3个) + const extraOperands = Math.floor(Math.random() * 4); + if (extraOperands > 0 && correctAnswer !== '不存在') { + const operations = ['+', '-', '×', '÷']; + for (let i = 0; i < extraOperands; i++) { + const op = operations[Math.floor(Math.random() * operations.length)]; + const num = Math.floor(Math.random() * 10) + 1; + stem += ` ${op} ${num}`; + + switch (op) { + case '+': correctAnswer += num; break; + case '-': correctAnswer -= num; break; + case '×': correctAnswer *= num; break; + case '÷': correctAnswer = Math.round((correctAnswer / num) * 100) / 100; break; + } + } + } + + stem += ' = ?'; + const options = this.generateOptions(correctAnswer, 4, true, correctAnswer === '不存在'); + + return { + id: `high-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + stem: stem, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + generateOptions(correctAnswer, count, allowDecimal = false, isTrigSpecial = false) { + const options = []; + const keys = ['A', 'B', 'C', 'D']; + const correctIndex = Math.floor(Math.random() * count); + + for (let i = 0; i < count; i++) { + let value; + if (i === correctIndex) { + value = correctAnswer; + } else { + if (isTrigSpecial && correctAnswer === '不存在') { + const wrongOptions = ['0', '1', '-1', '∞']; + value = wrongOptions[Math.floor(Math.random() * wrongOptions.length)]; + // 确保错误选项不重复 + while (options.some(opt => opt.text === value)) { + value = wrongOptions[Math.floor(Math.random() * wrongOptions.length)]; + } + } else if (typeof correctAnswer === 'number') { + // 生成接近但错误的答案 + let attempts = 0; + do { + const deviation = (Math.random() - 0.5) * 4; + value = allowDecimal ? + Math.round((correctAnswer + deviation) * 100) / 100 : + Math.round(correctAnswer + deviation); + attempts++; + if (attempts > 10) { + // 如果无法生成合适的错误答案,使用简单偏移 + value = correctAnswer + (i + 1); + if (!allowDecimal) value = Math.round(value); + break; + } + } while (value === correctAnswer || + (!allowDecimal && !Number.isInteger(value)) || + options.some(opt => opt.text === value.toString())); + } else { + // 生成随机字符串作为错误选项 + value = Math.random().toString(36).substring(2, 6); + } + } + + options.push({ + key: keys[i], + text: value.toString(), + isCorrect: i === correctIndex + }); + } + + return options; + } + + getDivisors(n) { + const divisors = []; + for (let i = 2; i <= n; i++) { + if (n % i === 0) divisors.push(i); + } + return divisors; + } +} + +module.exports = MathQuestionGenerator; \ No newline at end of file -- 2.34.1 From 66a7778e517585bc90720ca06a4ff36c0f7f7b3e Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Fri, 10 Oct 2025 23:12:43 +0800 Subject: [PATCH 07/13] ADD file via upload --- utils/user-manager.js | 160 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 utils/user-manager.js diff --git a/utils/user-manager.js b/utils/user-manager.js new file mode 100644 index 0000000..2f3e63f --- /dev/null +++ b/utils/user-manager.js @@ -0,0 +1,160 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const USERS_FILE = path.join(__dirname, '../data/users.json'); + +class UserManager { + constructor() { + this.ensureDataFile(); + } + + ensureDataFile() { + const dataDir = path.dirname(USERS_FILE); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + if (!fs.existsSync(USERS_FILE)) { + fs.writeFileSync(USERS_FILE, JSON.stringify([])); + } + } + + readUsers() { + try { + return JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); + } catch (error) { + console.error('读取用户数据失败:', error); + return []; + } + } + + writeUsers(users) { + try { + fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); + return true; + } catch (error) { + console.error('写入用户数据失败:', error); + return false; + } + } + + hashPassword(password) { + return crypto.createHash('sha256').update(password).digest('hex'); + } + + validatePassword(password) { + if (!password || password.length < 6 || password.length > 10) { + return false; + } + const hasUpper = /[A-Z]/.test(password); + const hasLower = /[a-z]/.test(password); + const hasDigit = /\d/.test(password); + return hasUpper && hasLower && hasDigit; + } + + findUserByEmail(email) { + const users = this.readUsers(); + return users.find(user => user.email === email); + } + + findUserByUsername(username) { + const users = this.readUsers(); + return users.find(user => user.username === username); + } + + createUser(email, username) { + const users = this.readUsers(); + + // 严格检查邮箱是否已存在 + if (users.some(user => user.email === email)) { + throw new Error('邮箱已被注册,每个邮箱只能注册一个账户'); + } + + // 检查用户名是否已存在 + if (users.some(user => user.username === username)) { + throw new Error('用户名已存在'); + } + + const newUser = { + email, + username, + registeredAt: new Date().toISOString(), + hasPassword: false + }; + + users.push(newUser); + if (this.writeUsers(users)) { + console.log(`✅ 新用户注册: ${username} (${email})`); + return newUser; + } else { + throw new Error('用户创建失败'); + } + } + + setPassword(email, password) { + const users = this.readUsers(); + const userIndex = users.findIndex(user => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + if (!this.validatePassword(password)) { + throw new Error('密码需6-10位且包含大小写字母和数字'); + } + + users[userIndex].password = this.hashPassword(password); + users[userIndex].hasPassword = true; + users[userIndex].updatedAt = new Date().toISOString(); + + if (this.writeUsers(users)) { + console.log(`✅ 用户设置密码: ${email}`); + return true; + } else { + throw new Error('密码设置失败'); + } + } + + verifyPassword(email, password) { + const user = this.findUserByEmail(email); + if (!user || !user.hasPassword) { + return false; + } + return user.password === this.hashPassword(password); + } + + changePassword(email, oldPassword, newPassword) { + if (!this.verifyPassword(email, oldPassword)) { + throw new Error('原密码不正确'); + } + + return this.setPassword(email, newPassword); + } + + changeUsername(email, newUsername) { + const users = this.readUsers(); + const userIndex = users.findIndex(user => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + // 检查新用户名是否已存在(排除当前用户) + if (users.some(user => user.username === newUsername && user.email !== email)) { + throw new Error('用户名已存在'); + } + + const oldUsername = users[userIndex].username; + users[userIndex].username = newUsername; + users[userIndex].updatedAt = new Date().toISOString(); + + if (this.writeUsers(users)) { + console.log(`✅ 用户修改用户名: ${oldUsername} -> ${newUsername}`); + return true; + } else { + throw new Error('用户名修改失败'); + } + } +} + +module.exports = UserManager; \ No newline at end of file -- 2.34.1 From 817e84413afe99f028c24f32e16c179b6e777983 Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Sun, 12 Oct 2025 14:36:32 +0800 Subject: [PATCH 08/13] =?UTF-8?q?=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/email-config.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/email-config.js diff --git a/src/email-config.js b/src/email-config.js new file mode 100644 index 0000000..d2d4aac --- /dev/null +++ b/src/email-config.js @@ -0,0 +1,31 @@ +// ============================================= +// 数学学习软件 - 邮箱配置文件 +// ============================================= + + +// ==================== 选择1: QQ邮箱服务 ==================== +module.exports = { + service: 'qq', + auth: { + user: '3454934335@qq.com', // 替换为你的QQ邮箱 + pass: 'vxhsswmmqiyvchhh' // 替换为QQ邮箱授权码(16位) + } +}; + +// ==================== 选择2: 163邮箱服务 ==================== +module.exports = { + service: '163', + auth: { + user: '18950579895@163.com',// 替换为你的163邮箱 + pass: 'UCgJrE7yzzd4Uz3g' // 替换为163邮箱授权码 + } +}; + + +// ==================== 选择3: 测试模式 ==================== +// 不发送实际邮件,验证码在控制台显示 +// 适用于开发和测试环境 +/* +module.exports = {}; +*/ + -- 2.34.1 From 06d3f48e98a51606cfc53911e072c4e3577d9705 Mon Sep 17 00:00:00 2001 From: hnu202301120109 <3454934335@qq.com> Date: Sun, 12 Oct 2025 14:39:06 +0800 Subject: [PATCH 09/13] ADD file via upload --- src/package.json | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/package.json diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..b4df712 --- /dev/null +++ b/src/package.json @@ -0,0 +1,35 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "test": "echo \"后端服务测试模式\" && node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "body-parser": "^1.20.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": [ + "education", + "math", + "learning", + "email", + "qq", + "163" + ], + "author": "数学学习软件团队", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/your-repo/math-learning-software" + } +} + -- 2.34.1 From 83f926382f94f1288b1f0ee3ee187ba858c733b2 Mon Sep 17 00:00:00 2001 From: wwb <3454934335@qq.com> Date: Sun, 12 Oct 2025 14:41:05 +0800 Subject: [PATCH 10/13] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=89=80=E6=9C=89?= =?UTF-8?q?=E7=8E=B0=E6=9C=89=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 - email-config.js | 30 - package-lock.json | 1243 ---------------------------------- package.json | 34 - server.js | 279 -------- src/email-config.js | 31 - src/package.json | 35 - utils/multi-email-service.js | 111 --- utils/question-generator.js | 475 ------------- utils/user-manager.js | 160 ----- 10 files changed, 2400 deletions(-) delete mode 100644 README.md delete mode 100644 email-config.js delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 server.js delete mode 100644 src/email-config.js delete mode 100644 src/package.json delete mode 100644 utils/multi-email-service.js delete mode 100644 utils/question-generator.js delete mode 100644 utils/user-manager.js diff --git a/README.md b/README.md deleted file mode 100644 index cfbbb51..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Pair - diff --git a/email-config.js b/email-config.js deleted file mode 100644 index ac8293c..0000000 --- a/email-config.js +++ /dev/null @@ -1,30 +0,0 @@ -// ============================================= -// ѧѧϰ - ļ -// ============================================= - - -// ==================== ѡ1: QQ ==================== -module.exports = { - service: 'qq', - auth: { - user: '3454934335@qq.com', // 滻ΪQQ - pass: 'vxhsswmmqiyvchhh' // 滻ΪQQȨ루16λ - } -}; - -// ==================== ѡ2: 163 ==================== -module.exports = { - service: '163', - auth: { - user: '18950579895@163.com',// 滻Ϊ163 - pass: 'UCgJrE7yzzd4Uz3g' // 滻Ϊ163Ȩ - } -}; - - -// ==================== ѡ3: ģʽ ==================== -// ʵʼ֤ڿ̨ʾ -// ڿͲԻ -/* -module.exports = {}; -*/ diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 01a37dc..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1243 +0,0 @@ -{ - "name": "math-learning-backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "math-learning-backend", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "body-parser": "^1.20.2", - "cors": "^2.8.5", - "express": "^4.18.2", - "nodemailer": "^6.9.7" - }, - "devDependencies": { - "nodemon": "^3.0.1" - } - }, - "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/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/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/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "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/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==", - "dev": true, - "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/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-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/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/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "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/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/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/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/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/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/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/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/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/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/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/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/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/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/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-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": "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/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/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/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/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/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/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/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-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-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/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==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "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/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/nodemailer": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", - "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", - "license": "MIT-0", - "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.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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/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/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-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/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/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/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/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/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/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/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/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "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/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/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "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-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/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/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/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/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/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/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/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/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" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index f7947b2..0000000 --- a/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "math-learning-backend", - "version": "1.0.0", - "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "test": "echo \"后端服务测试模式\" && node server.js" - }, - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", - "body-parser": "^1.20.2", - "nodemailer": "^6.9.7" - }, - "devDependencies": { - "nodemon": "^3.0.1" - }, - "keywords": [ - "education", - "math", - "learning", - "email", - "qq", - "163" - ], - "author": "数学学习软件团队", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/your-repo/math-learning-software" - } -} \ No newline at end of file diff --git a/server.js b/server.js deleted file mode 100644 index 68dd165..0000000 --- a/server.js +++ /dev/null @@ -1,279 +0,0 @@ -const express = require('express'); -const cors = require('cors'); -const bodyParser = require('body-parser'); -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -// 导入工具类 -const UserManager = require('./utils/user-manager'); -const MultiEmailService = require('./utils/multi-email-service'); -const MathQuestionGenerator = require('./utils/question-generator'); - -const app = express(); -const PORT = 8080; - -// 中间件 -app.use(cors()); -app.use(bodyParser.json()); -app.use(express.static(path.join(__dirname, '../frontend'))); - -// 实例化管理器 -const userManager = new UserManager(); -const emailConfig = require('./email-config'); -const emailService = new MultiEmailService(); - -// 存储验证码(内存存储,重启后失效) -const verificationCodes = new Map(); - -// 生成验证码 -function generateVerificationCode() { - return Math.floor(100000 + Math.random() * 900000).toString(); -} - -// 验证邮箱格式 -function validateEmail(email) { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); -} - -// API路由 - -// 健康检查 -app.get('/api/health', (req, res) => { - res.json({ - ok: true, - message: '服务正常运行', - emailService: emailService.transporter ? '已配置' : '测试模式' - }); -}); - -// 发送验证码 -app.post('/api/send-code', async (req, res) => { - try { - const { email, username } = req.body; - - console.log('收到发送验证码请求:', { email, username }); - - if (!email || !username) { - return res.json({ ok: false, message: '邮箱和用户名不能为空' }); - } - - // 验证邮箱格式 - if (!validateEmail(email)) { - return res.json({ ok: false, message: '邮箱格式不正确' }); - } - - // 检查用户名是否已存在 - if (userManager.findUserByUsername(username)) { - return res.json({ ok: false, message: '用户名已存在' }); - } - - // 检查邮箱是否已注册 - if (userManager.findUserByEmail(email)) { - return res.json({ ok: false, message: '邮箱已被注册' }); - } - - const code = generateVerificationCode(); - verificationCodes.set(email, { - code, - username, - timestamp: Date.now() - }); - - console.log(`为邮箱 ${email} 生成验证码: ${code}`); - - // 发送邮件 - const emailResult = await emailService.sendVerificationCode(email, code, username); - - if (emailResult.success) { - res.json({ ok: true, message: emailResult.message }); - } else { - // 如果邮件发送失败,但在开发模式下提供了验证码,仍然算成功 - if (emailResult.debug) { - res.json({ ok: true, message: emailResult.message + ' ' + emailResult.debug }); - } else { - res.json({ ok: false, message: emailResult.message }); - } - } - } catch (error) { - console.error('发送验证码错误:', error); - res.json({ ok: false, message: '服务器内部错误' }); - } -}); - -// 注册 -app.post('/api/register', (req, res) => { - try { - const { email, username, code } = req.body; - - console.log('收到注册请求:', { email, username, code }); - - if (!email || !username || !code) { - return res.json({ ok: false, message: '请填写完整信息' }); - } - - const storedData = verificationCodes.get(email); - - if (!storedData) { - return res.json({ ok: false, message: '请先获取验证码' }); - } - - // 验证码10分钟过期 - if (Date.now() - storedData.timestamp > 10 * 60 * 1000) { - verificationCodes.delete(email); - return res.json({ ok: false, message: '验证码已过期,请重新获取' }); - } - - if (storedData.code !== code) { - return res.json({ ok: false, message: '验证码不正确' }); - } - - if (storedData.username !== username) { - return res.json({ ok: false, message: '用户名与验证时不一致' }); - } - - // 创建用户 - userManager.createUser(email, username); - - // 清除验证码 - verificationCodes.delete(email); - - res.json({ ok: true, message: '注册成功,请设置密码' }); - } catch (error) { - console.error('注册错误:', error); - res.json({ ok: false, message: error.message }); - } -}); - -// 设置密码 -app.post('/api/set-password', (req, res) => { - try { - const { email, password } = req.body; - - if (!email || !password) { - return res.json({ ok: false, message: '请填写完整信息' }); - } - - userManager.setPassword(email, password); - res.json({ ok: true, message: '密码设置成功' }); - } catch (error) { - console.error('设置密码错误:', error); - res.json({ ok: false, message: error.message }); - } -}); - -// 登录 -app.post('/api/login', (req, res) => { - try { - const { account, password } = req.body; - - if (!account || !password) { - return res.json({ ok: false, message: '请填写账号和密码' }); - } - - // 通过邮箱或用户名查找用户 - let user = userManager.findUserByEmail(account); - if (!user) { - user = userManager.findUserByUsername(account); - } - - if (!user || !user.hasPassword) { - return res.json({ ok: false, message: '用户不存在' }); - } - - if (!userManager.verifyPassword(user.email, password)) { - return res.json({ ok: false, message: '密码不正确' }); - } - - res.json({ - ok: true, - message: '登录成功', - data: { - email: user.email, - username: user.username - } - }); - } catch (error) { - console.error('登录错误:', error); - res.json({ ok: false, message: error.message }); - } -}); - -// 修改密码 -app.post('/api/change-password', (req, res) => { - try { - const { email, oldPassword, newPassword } = req.body; - - if (!email || !oldPassword || !newPassword) { - return res.json({ ok: false, message: '请填写完整信息' }); - } - - userManager.changePassword(email, oldPassword, newPassword); - res.json({ ok: true, message: '密码修改成功' }); - } catch (error) { - console.error('修改密码错误:', error); - res.json({ ok: false, message: error.message }); - } -}); - -// 修改用户名 -app.post('/api/change-username', (req, res) => { - try { - const { email, username } = req.body; - - if (!email || !username) { - return res.json({ ok: false, message: '请填写完整信息' }); - } - - userManager.changeUsername(email, username); - res.json({ ok: true, message: '用户名修改成功' }); - } catch (error) { - console.error('修改用户名错误:', error); - res.json({ ok: false, message: error.message }); - } -}); - -// 获取题目 -app.get('/api/questions', (req, res) => { - try { - const { grade, count } = req.query; - - if (!grade || !count) { - return res.json({ ok: false, message: '请选择年级和题目数量' }); - } - - const countNum = parseInt(count); - if (isNaN(countNum) || countNum < 10 || countNum > 30) { - return res.json({ ok: false, message: '题目数量需在10-30之间' }); - } - - const generator = new MathQuestionGenerator(); - const questions = generator.generateQuestions(grade, countNum); - res.json({ - ok: true, - data: questions, - message: '题目生成成功' - }); - } catch (error) { - console.error('生成题目失败:', error); - res.json({ ok: false, message: '生成题目失败' }); - } -}); - -// 提供前端页面 -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, '../frontend/index.html')); -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log(`========================================`); - console.log(`🎓 数学学习软件后端服务已启动`); - console.log(`📍 服务地址: http://localhost:${PORT}`); - console.log(`🌐 前端页面: http://localhost:${PORT}/`); - console.log(`🔧 API文档: http://localhost:${PORT}/api/health`); - console.log(`📧 邮箱服务: ${emailService.transporter ? '已配置' : '测试模式'}`); - console.log(`========================================`); -}); - -module.exports = app; \ No newline at end of file diff --git a/src/email-config.js b/src/email-config.js deleted file mode 100644 index d2d4aac..0000000 --- a/src/email-config.js +++ /dev/null @@ -1,31 +0,0 @@ -// ============================================= -// 数学学习软件 - 邮箱配置文件 -// ============================================= - - -// ==================== 选择1: QQ邮箱服务 ==================== -module.exports = { - service: 'qq', - auth: { - user: '3454934335@qq.com', // 替换为你的QQ邮箱 - pass: 'vxhsswmmqiyvchhh' // 替换为QQ邮箱授权码(16位) - } -}; - -// ==================== 选择2: 163邮箱服务 ==================== -module.exports = { - service: '163', - auth: { - user: '18950579895@163.com',// 替换为你的163邮箱 - pass: 'UCgJrE7yzzd4Uz3g' // 替换为163邮箱授权码 - } -}; - - -// ==================== 选择3: 测试模式 ==================== -// 不发送实际邮件,验证码在控制台显示 -// 适用于开发和测试环境 -/* -module.exports = {}; -*/ - diff --git a/src/package.json b/src/package.json deleted file mode 100644 index b4df712..0000000 --- a/src/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "math-learning-backend", - "version": "1.0.0", - "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "test": "echo \"后端服务测试模式\" && node server.js" - }, - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", - "body-parser": "^1.20.2", - "nodemailer": "^6.9.7" - }, - "devDependencies": { - "nodemon": "^3.0.1" - }, - "keywords": [ - "education", - "math", - "learning", - "email", - "qq", - "163" - ], - "author": "数学学习软件团队", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/your-repo/math-learning-software" - } -} - diff --git a/utils/multi-email-service.js b/utils/multi-email-service.js deleted file mode 100644 index f7fae43..0000000 --- a/utils/multi-email-service.js +++ /dev/null @@ -1,111 +0,0 @@ -const nodemailer = require('nodemailer'); - -class MultiEmailService { - constructor() { - this.transporters = new Map(); - this.initTransporters(); - } - - initTransporters() { - // QQ邮箱配置 - try { - const qqTransporter = nodemailer.createTransport({ - service: 'qq', - auth: { - user: '3454934335@qq.com', - pass: 'vxhsswmmqiyvchhh' - } - }); - this.transporters.set('qq', qqTransporter); - console.log('✅ QQ邮箱服务已初始化'); - } catch (error) { - console.error('❌ QQ邮箱初始化失败:', error); - } - - // 163邮箱配置 - try { - const mail163Transporter = nodemailer.createTransport({ - service: '163', - auth: { - user: '18950579895@163.com', - pass: 'UCgJrE7yzzd4Uz3g' - } - }); - this.transporters.set('163', mail163Transporter); - console.log('✅ 163邮箱服务已初始化'); - } catch (error) { - console.error('❌ 163邮箱初始化失败:', error); - } - } - - async sendVerificationCode(email, code, username) { - // 根据邮箱域名选择发件箱 - let transporter; - if (email.includes('@qq.com')) { - transporter = this.transporters.get('qq'); - } else if (email.includes('@163.com')) { - transporter = this.transporters.get('163'); - } else { - // 默认使用QQ邮箱 - transporter = this.transporters.get('qq'); - } - - if (!transporter) { - console.log(`🔧 测试模式 - 邮箱: ${email}, 验证码: ${code}`); - return { - success: true, - message: '验证码已生成(测试模式)', - debug: `验证码: ${code}` - }; - } - - const mailOptions = { - from: transporter.options.auth.user, - to: email, - subject: '数学学习软件 - 注册验证码', - html: this.generateEmailTemplate(code, username) - }; - - try { - await transporter.sendMail(mailOptions); - console.log(`✅ 验证码邮件已发送到: ${email}`); - return { success: true, message: '验证码已发送到您的邮箱' }; - } catch (error) { - console.error('❌ 邮件发送失败:', error); - return { - success: false, - message: '邮件发送失败,请稍后重试', - debug: `验证码: ${code} (请使用此验证码完成注册)` - }; - } - } - - generateEmailTemplate(code, username) { - return ` - - - - - - - -
-

🎓 数学学习软件

-

亲爱的 ${username},您好!

-

您正在注册数学学习软件,验证码为:

-
${code}
-

验证码有效期为10分钟,请尽快完成注册。

-

如果这不是您本人的操作,请忽略此邮件。

-
- - - `; - } -} - -module.exports = MultiEmailService; \ No newline at end of file diff --git a/utils/question-generator.js b/utils/question-generator.js deleted file mode 100644 index 17c1852..0000000 --- a/utils/question-generator.js +++ /dev/null @@ -1,475 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -class MathQuestionGenerator { - constructor() { - // 不再保存已使用的题目到文件 - // 只在内存中记录当前试卷的题目,试卷完成后自动清除 - this.currentSessionQuestions = new Set(); - } - - generateQuestions(grade, count) { - console.log(`正在生成${grade} ${count}道题目...`); - const questions = []; - this.currentSessionQuestions.clear(); // 清除上一份试卷的记录 - - const maxAttempts = count * 20; // 防止无限循环 - let attempts = 0; - - while (questions.length < count && attempts < maxAttempts) { - attempts++; - const question = this.generateQuestion(grade); - if (!question) continue; - - const questionKey = `${grade}-${question.stem}`; - - // 只检查当前试卷内是否重复 - if (!this.currentSessionQuestions.has(questionKey)) { - questions.push(question); - this.currentSessionQuestions.add(questionKey); - console.log(`✅ 生成第${questions.length}题: ${question.stem}`); - } - } - - if (questions.length < count) { - console.warn(`⚠️ 只生成了${questions.length}道题目,未能达到要求的${count}道`); - } - - return questions; - } - - generateQuestion(grade) { - try { - switch (grade) { - case '小学': - return this.generatePrimaryQuestion(); - case '初中': - return this.generateMiddleSchoolQuestion(); - case '高中': - return this.generateHighSchoolQuestion(); - default: - return this.generatePrimaryQuestion(); - } - } catch (error) { - console.error(`生成${grade}题目时出错:`, error); - return null; - } - } - - generatePrimaryQuestion() { - // 小学题目:2-5个操作数,只能有+,-,*,/,不能有负数,结果整数 - const operations = ['+', '-', '×', '÷']; - const numOperands = Math.floor(Math.random() * 4) + 2; // 2-5个操作数 - - let expression = ''; - let correctAnswer = 0; - let attempts = 0; - - do { - expression = ''; - correctAnswer = 0; - let isValid = true; - - for (let i = 0; i < numOperands; i++) { - const num = Math.floor(Math.random() * 50) + 1; // 1-50 - if (i === 0) { - expression = num.toString(); - correctAnswer = num; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - - if (op === '÷') { - // 确保除法结果是整数 - const divisors = this.getDivisors(correctAnswer); - if (divisors.length > 0) { - const divisor = divisors[Math.floor(Math.random() * divisors.length)]; - expression += ` ÷ ${divisor}`; - correctAnswer = correctAnswer / divisor; - } else { - expression += ` × ${num}`; - correctAnswer = correctAnswer * num; - } - } else if (op === '-') { - // 确保减法结果非负 - if (correctAnswer > num) { - expression += ` - ${num}`; - correctAnswer -= num; - } else { - expression += ` + ${num}`; - correctAnswer += num; - } - } else { - expression += ` ${op} ${num}`; - if (op === '+') correctAnswer += num; - if (op === '×') correctAnswer *= num; - } - } - } - - // 验证结果是否符合要求 - if (correctAnswer < 0 || !Number.isInteger(correctAnswer)) { - isValid = false; - } - - attempts++; - if (isValid) break; - - } while (attempts < 10); - - // 如果仍然无法生成有效题目,使用简单题目 - if (correctAnswer < 0 || !Number.isInteger(correctAnswer)) { - expression = "10 + 5"; - correctAnswer = 15; - } - - const options = this.generateOptions(correctAnswer, 4, false, false); - - return { - id: `primary-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - stem: `计算:${expression} = ?`, - options: options, - answer: options.find(opt => opt.isCorrect).key - }; - } - - generateMiddleSchoolQuestion() { - // 初中题目:1-5个操作数,必须包含至少一个平方或开根号运算符 - const types = ['square', 'sqrt', 'mixed_square', 'mixed_sqrt', 'power', 'combined']; - const type = types[Math.floor(Math.random() * types.length)]; - - // 1-5个操作数 - const numOperands = Math.floor(Math.random() * 5) + 1; - - let stem, correctAnswer; - - if (type === 'square') { - // 纯平方运算 - let expression = ''; - let result = 0; - const operations = ['+', '-', '×']; - - for (let i = 0; i < numOperands; i++) { - const base = Math.floor(Math.random() * 10) + 1; - if (i === 0) { - expression = `${base}²`; - result = base * base; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${base}²`; - if (op === '+') result += base * base; - else if (op === '-') result -= base * base; - else if (op === '×') result *= base * base; - } - } - stem = `计算:${expression}`; - correctAnswer = result; - } else if (type === 'sqrt') { - // 纯开根号运算 - const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225]; - let expression = ''; - let result = 0; - const operations = ['+', '-', '×']; - - for (let i = 0; i < numOperands; i++) { - const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; - if (i === 0) { - expression = `√${num}`; - result = Math.sqrt(num); - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} √${num}`; - const sqrtValue = Math.sqrt(num); - if (op === '+') result += sqrtValue; - else if (op === '-') result -= sqrtValue; - else if (op === '×') result *= sqrtValue; - } - } - stem = `计算:${expression}`; - correctAnswer = result; - } else if (type === 'mixed_square') { - // 混合运算,包含平方 - let expression = ''; - let result = 0; - const operations = ['+', '-', '×', '÷']; - - // 确保至少有一个平方运算 - const squarePosition = Math.floor(Math.random() * numOperands); - - for (let i = 0; i < numOperands; i++) { - let term; - let value; - - if (i === squarePosition) { - // 插入平方项 - const base = Math.floor(Math.random() * 10) + 1; - term = `${base}²`; - value = base * base; - } else { - // 普通数字 - const num = Math.floor(Math.random() * 20) + 1; - term = num.toString(); - value = num; - } - - if (i === 0) { - expression = term; - result = value; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${term}`; - if (op === '+') result += value; - else if (op === '-') result -= value; - else if (op === '×') result *= value; - else if (op === '÷') result /= value; - } - } - - stem = `计算:${expression}`; - correctAnswer = result; - } else if (type === 'mixed_sqrt') { - // 混合运算,包含开根号 - const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144]; - let expression = ''; - let result = 0; - const operations = ['+', '-', '×', '÷']; - - // 确保至少有一个开根号运算 - const sqrtPosition = Math.floor(Math.random() * numOperands); - - for (let i = 0; i < numOperands; i++) { - let term; - let value; - - if (i === sqrtPosition) { - // 插入开根号项 - const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; - term = `√${num}`; - value = Math.sqrt(num); - } else { - // 普通数字 - const num = Math.floor(Math.random() * 20) + 1; - term = num.toString(); - value = num; - } - - if (i === 0) { - expression = term; - result = value; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${term}`; - if (op === '+') result += value; - else if (op === '-') result -= value; - else if (op === '×') result *= value; - else if (op === '÷') result /= value; - } - } - - stem = `计算:${expression}`; - correctAnswer = result; - } else if (type === 'power') { - // 幂运算(平方) - const base = Math.floor(Math.random() * 10) + 1; - - if (numOperands === 1) { - stem = `计算:${base}²`; - correctAnswer = base * base; - } else { - let expression = `${base}²`; - let result = base * base; - const operations = ['+', '-', '×', '÷']; - - for (let i = 1; i < numOperands; i++) { - const num = Math.floor(Math.random() * 20) + 1; - const op = operations[Math.floor(Math.random() * operations.length)]; - - expression += ` ${op} ${num}`; - - if (op === '+') result += num; - else if (op === '-') result -= num; - else if (op === '×') result *= num; - else if (op === '÷') result /= num; - } - - stem = `计算:${expression}`; - correctAnswer = result; - } - } else { - // 组合运算(同时包含平方和开根号) - const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; - let expression = ''; - let result = 0; - const operations = ['+', '-', '×', '÷']; - - // 确保至少有一个平方和一个开根号 - const squarePosition = Math.floor(Math.random() * (numOperands - 1)); - const sqrtPosition = squarePosition + 1; - - for (let i = 0; i < numOperands; i++) { - let term; - let value; - - if (i === squarePosition) { - // 插入平方项 - const base = Math.floor(Math.random() * 10) + 1; - term = `${base}²`; - value = base * base; - } else if (i === sqrtPosition) { - // 插入开根号项 - const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; - term = `√${num}`; - value = Math.sqrt(num); - } else { - // 普通数字 - const num = Math.floor(Math.random() * 20) + 1; - term = num.toString(); - value = num; - } - - if (i === 0) { - expression = term; - result = value; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${term}`; - if (op === '+') result += value; - else if (op === '-') result -= value; - else if (op === '×') result *= value; - else if (op === '÷') result /= value; - } - } - - stem = `计算:${expression}`; - correctAnswer = result; - } - - // 确保答案是数字且合理 - if (typeof correctAnswer === 'number') { - correctAnswer = Math.round(correctAnswer * 100) / 100; // 保留两位小数 - } - - const options = this.generateOptions(correctAnswer, 4, true, false); - - return { - id: `middle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - stem: stem, - options: options, - answer: options.find(opt => opt.isCorrect).key - }; - } - generateHighSchoolQuestion() { - // 高中题目:1-5个操作数,必须包含sin,cos,tan,可以有小数 - const functions = ['sin', 'cos', 'tan']; - const func = functions[Math.floor(Math.random() * functions.length)]; - const angles = [0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 240, 270, 300, 315, 330, 360]; - const angle = angles[Math.floor(Math.random() * angles.length)]; - - let correctAnswer; - switch (func) { - case 'sin': - correctAnswer = Math.round(Math.sin(angle * Math.PI / 180) * 100) / 100; - break; - case 'cos': - correctAnswer = Math.round(Math.cos(angle * Math.PI / 180) * 100) / 100; - break; - case 'tan': - if (angle % 180 === 90) { - correctAnswer = '不存在'; - } else { - correctAnswer = Math.round(Math.tan(angle * Math.PI / 180) * 100) / 100; - } - break; - } - - let stem = `计算:${func}(${angle}°)`; - - // 添加额外操作数(0-3个) - const extraOperands = Math.floor(Math.random() * 4); - if (extraOperands > 0 && correctAnswer !== '不存在') { - const operations = ['+', '-', '×', '÷']; - for (let i = 0; i < extraOperands; i++) { - const op = operations[Math.floor(Math.random() * operations.length)]; - const num = Math.floor(Math.random() * 10) + 1; - stem += ` ${op} ${num}`; - - switch (op) { - case '+': correctAnswer += num; break; - case '-': correctAnswer -= num; break; - case '×': correctAnswer *= num; break; - case '÷': correctAnswer = Math.round((correctAnswer / num) * 100) / 100; break; - } - } - } - - stem += ' = ?'; - const options = this.generateOptions(correctAnswer, 4, true, correctAnswer === '不存在'); - - return { - id: `high-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - stem: stem, - options: options, - answer: options.find(opt => opt.isCorrect).key - }; - } - - generateOptions(correctAnswer, count, allowDecimal = false, isTrigSpecial = false) { - const options = []; - const keys = ['A', 'B', 'C', 'D']; - const correctIndex = Math.floor(Math.random() * count); - - for (let i = 0; i < count; i++) { - let value; - if (i === correctIndex) { - value = correctAnswer; - } else { - if (isTrigSpecial && correctAnswer === '不存在') { - const wrongOptions = ['0', '1', '-1', '∞']; - value = wrongOptions[Math.floor(Math.random() * wrongOptions.length)]; - // 确保错误选项不重复 - while (options.some(opt => opt.text === value)) { - value = wrongOptions[Math.floor(Math.random() * wrongOptions.length)]; - } - } else if (typeof correctAnswer === 'number') { - // 生成接近但错误的答案 - let attempts = 0; - do { - const deviation = (Math.random() - 0.5) * 4; - value = allowDecimal ? - Math.round((correctAnswer + deviation) * 100) / 100 : - Math.round(correctAnswer + deviation); - attempts++; - if (attempts > 10) { - // 如果无法生成合适的错误答案,使用简单偏移 - value = correctAnswer + (i + 1); - if (!allowDecimal) value = Math.round(value); - break; - } - } while (value === correctAnswer || - (!allowDecimal && !Number.isInteger(value)) || - options.some(opt => opt.text === value.toString())); - } else { - // 生成随机字符串作为错误选项 - value = Math.random().toString(36).substring(2, 6); - } - } - - options.push({ - key: keys[i], - text: value.toString(), - isCorrect: i === correctIndex - }); - } - - return options; - } - - getDivisors(n) { - const divisors = []; - for (let i = 2; i <= n; i++) { - if (n % i === 0) divisors.push(i); - } - return divisors; - } -} - -module.exports = MathQuestionGenerator; \ No newline at end of file diff --git a/utils/user-manager.js b/utils/user-manager.js deleted file mode 100644 index 2f3e63f..0000000 --- a/utils/user-manager.js +++ /dev/null @@ -1,160 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -const USERS_FILE = path.join(__dirname, '../data/users.json'); - -class UserManager { - constructor() { - this.ensureDataFile(); - } - - ensureDataFile() { - const dataDir = path.dirname(USERS_FILE); - if (!fs.existsSync(dataDir)) { - fs.mkdirSync(dataDir, { recursive: true }); - } - if (!fs.existsSync(USERS_FILE)) { - fs.writeFileSync(USERS_FILE, JSON.stringify([])); - } - } - - readUsers() { - try { - return JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); - } catch (error) { - console.error('读取用户数据失败:', error); - return []; - } - } - - writeUsers(users) { - try { - fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); - return true; - } catch (error) { - console.error('写入用户数据失败:', error); - return false; - } - } - - hashPassword(password) { - return crypto.createHash('sha256').update(password).digest('hex'); - } - - validatePassword(password) { - if (!password || password.length < 6 || password.length > 10) { - return false; - } - const hasUpper = /[A-Z]/.test(password); - const hasLower = /[a-z]/.test(password); - const hasDigit = /\d/.test(password); - return hasUpper && hasLower && hasDigit; - } - - findUserByEmail(email) { - const users = this.readUsers(); - return users.find(user => user.email === email); - } - - findUserByUsername(username) { - const users = this.readUsers(); - return users.find(user => user.username === username); - } - - createUser(email, username) { - const users = this.readUsers(); - - // 严格检查邮箱是否已存在 - if (users.some(user => user.email === email)) { - throw new Error('邮箱已被注册,每个邮箱只能注册一个账户'); - } - - // 检查用户名是否已存在 - if (users.some(user => user.username === username)) { - throw new Error('用户名已存在'); - } - - const newUser = { - email, - username, - registeredAt: new Date().toISOString(), - hasPassword: false - }; - - users.push(newUser); - if (this.writeUsers(users)) { - console.log(`✅ 新用户注册: ${username} (${email})`); - return newUser; - } else { - throw new Error('用户创建失败'); - } - } - - setPassword(email, password) { - const users = this.readUsers(); - const userIndex = users.findIndex(user => user.email === email); - - if (userIndex === -1) { - throw new Error('用户不存在'); - } - - if (!this.validatePassword(password)) { - throw new Error('密码需6-10位且包含大小写字母和数字'); - } - - users[userIndex].password = this.hashPassword(password); - users[userIndex].hasPassword = true; - users[userIndex].updatedAt = new Date().toISOString(); - - if (this.writeUsers(users)) { - console.log(`✅ 用户设置密码: ${email}`); - return true; - } else { - throw new Error('密码设置失败'); - } - } - - verifyPassword(email, password) { - const user = this.findUserByEmail(email); - if (!user || !user.hasPassword) { - return false; - } - return user.password === this.hashPassword(password); - } - - changePassword(email, oldPassword, newPassword) { - if (!this.verifyPassword(email, oldPassword)) { - throw new Error('原密码不正确'); - } - - return this.setPassword(email, newPassword); - } - - changeUsername(email, newUsername) { - const users = this.readUsers(); - const userIndex = users.findIndex(user => user.email === email); - - if (userIndex === -1) { - throw new Error('用户不存在'); - } - - // 检查新用户名是否已存在(排除当前用户) - if (users.some(user => user.username === newUsername && user.email !== email)) { - throw new Error('用户名已存在'); - } - - const oldUsername = users[userIndex].username; - users[userIndex].username = newUsername; - users[userIndex].updatedAt = new Date().toISOString(); - - if (this.writeUsers(users)) { - console.log(`✅ 用户修改用户名: ${oldUsername} -> ${newUsername}`); - return true; - } else { - throw new Error('用户名修改失败'); - } - } -} - -module.exports = UserManager; \ No newline at end of file -- 2.34.1 From 5dc760d3120c6cee5fef30e6b37607efde5efa29 Mon Sep 17 00:00:00 2001 From: wwb <3454934335@qq.com> Date: Sun, 12 Oct 2025 14:47:45 +0800 Subject: [PATCH 11/13] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=9A=E6=8F=8F=E8=BF=B0=E6=96=87=E4=BB=B6=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/data/users.json | 16 + src/backend/email-config.js | 31 + src/backend/package-lock.json | 1243 ++++++++++++++++++++++ src/backend/package.json | 35 + src/backend/server.js | 310 ++++++ src/backend/utils/multi-email-service.js | 104 ++ src/backend/utils/question-generator.js | 441 ++++++++ src/backend/utils/user-manager.js | 183 ++++ 8 files changed, 2363 insertions(+) create mode 100644 src/backend/data/users.json create mode 100644 src/backend/email-config.js create mode 100644 src/backend/package-lock.json create mode 100644 src/backend/package.json create mode 100644 src/backend/server.js create mode 100644 src/backend/utils/multi-email-service.js create mode 100644 src/backend/utils/question-generator.js create mode 100644 src/backend/utils/user-manager.js diff --git a/src/backend/data/users.json b/src/backend/data/users.json new file mode 100644 index 0000000..ea595de --- /dev/null +++ b/src/backend/data/users.json @@ -0,0 +1,16 @@ +[ + { + "email": "3454934335@qq.com", + "username": "wwb", + "registeredAt": "2025-10-11T05:26:22.038Z", + "password": "cc43f52af644042612d98dea06cf5b64d8e2f8af1de8065fe83b208f4a6d8875", + "updatedAt": "2025-10-11T05:26:22.051Z" + }, + { + "email": "3063485007@qq.com", + "username": "wqs", + "registeredAt": "2025-10-11T19:08:09.303Z", + "password": "be0c664d19564b568c4dd76d00a26a08ee89214877e52225eee5ffefab36c15f", + "updatedAt": "2025-10-11T19:08:26.339Z" + } +] \ No newline at end of file diff --git a/src/backend/email-config.js b/src/backend/email-config.js new file mode 100644 index 0000000..5a4c8e7 --- /dev/null +++ b/src/backend/email-config.js @@ -0,0 +1,31 @@ +// ============================================= +// 数学学习软件 - 邮箱配置文件 +// ============================================= + + +// ==================== 选择1: QQ邮箱服务 ==================== +module.exports = { + service: 'qq', + auth: { + user: '3454934335@qq.com', // 替换为你的QQ邮箱 + pass: 'vxhsswmmqiyvchhh' // 替换为QQ邮箱授权码(16位) + } +}; + +// ==================== 选择2: 163邮箱服务 ==================== +module.exports = { + service: '163', + auth: { + user: '18950579895@163.com',// 替换为你的163邮箱 + pass: 'UCgJrE7yzzd4Uz3g' // 替换为163邮箱授权码 + } +}; + + +// ==================== 选择3: 测试模式 ==================== +// 不发送实际邮件,验证码在控制台显示 +// 适用于开发和测试环境 +/* +module.exports = {}; +*/ + diff --git a/src/backend/package-lock.json b/src/backend/package-lock.json new file mode 100644 index 0000000..52de788 --- /dev/null +++ b/src/backend/package-lock.json @@ -0,0 +1,1243 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "math-learning-backend", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/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/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/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/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/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/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/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/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemailer": { + "version": "6.10.1", + "resolved": "https://registry.npmmirror.com/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/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.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/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/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/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/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/src/backend/package.json b/src/backend/package.json new file mode 100644 index 0000000..2d33a16 --- /dev/null +++ b/src/backend/package.json @@ -0,0 +1,35 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "test": "echo \"后端服务测试模式\" && node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "body-parser": "^1.20.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": [ + "education", + "math", + "learning", + "email", + "qq", + "163" + ], + "author": "数学学习软件团队", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/your-repo/math-learning-software" + } +} + diff --git a/src/backend/server.js b/src/backend/server.js new file mode 100644 index 0000000..5896e8b --- /dev/null +++ b/src/backend/server.js @@ -0,0 +1,310 @@ +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +// 导入工具类 +const UserManager = require('./utils/user-manager'); +const MultiEmailService = require('./utils/multi-email-service'); +const MathQuestionGenerator = require('./utils/question-generator'); + +// 创建Express应用实例 +const app = express(); +const PORT = 8080; + +// 配置中间件 +app.use(cors()); +app.use(bodyParser.json()); +app.use(express.static(path.join(__dirname, '../project4'))); + +// 实例化管理器 +const userManager = new UserManager(); +const emailService = new MultiEmailService(); + +// 存储验证码(内存存储,重启后失效) +const verificationCodes = new Map(); + +// 生成6位数字验证码 +function generateVerificationCode() { + return Math.floor(100000 + Math.random() * 900000).toString(); +} + +// 验证邮箱格式是否正确 +function validateEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); +} + +// API路由 + +// 检查服务器健康状态 +app.get('/api/health', (req, res) => { + res.json({ + ok: true, + message: '服务正常运行', + emailService: emailService.transporter ? '已配置' : '测试模式' + }); +}); + +// 发送邮箱验证码 +app.post('/api/send-code', async (req, res) => { + try { + const {email, username} = req.body; + + console.log('收到发送验证码请求:', {email, username}); + + if (!email || !username) { + return res.json({ok: false, message: '邮箱和用户名不能为空'}); + } + + // 验证邮箱格式 + if (!validateEmail(email)) { + return res.json({ok: false, message: '邮箱格式不正确'}); + } + + // 限制邮箱域名,只允许QQ邮箱和163邮箱 + if (!email.includes('@qq.com') && !email.includes('@163.com')) { + return res.json({ok: false, message: '只支持QQ邮箱和163邮箱'}); + } + + // 检查用户名是否已存在 + if (userManager.findUserByUsername(username)) { + return res.json({ok: false, message: '用户名已存在'}); + } + + // 检查邮箱是否已注册 + if (userManager.findUserByEmail(email)) { + return res.json({ok: false, message: '邮箱已被注册'}); + } + + const code = generateVerificationCode(); + verificationCodes.set(email, { + code, + username, + timestamp: Date.now(), + }); + + console.log(`为邮箱 ${email} 生成验证码: ${code}`); + + // 发送邮件 + const emailResult = await emailService.sendVerificationCode(email, code, username); + + if (emailResult.success) { + res.json({ok: true, message: emailResult.message}); + } else { + // 如果邮件发送失败,但在开发模式下提供了验证码,仍然算成功 + if (emailResult.debug) { + res.json({ok: true, message: emailResult.message + ' ' + emailResult.debug}); + } else { + res.json({ok: false, message: emailResult.message}); + } + } + } catch (error) { + console.error('发送验证码错误:', error); + res.json({ok: false, message: '服务器内部错误'}); + } +}); + +// 验证码验证 +app.post('/api/verify-code', (req, res) => { + try { + const {email, username, code} = req.body; + + console.log('收到验证码验证请求:', {email, username, code}); + + if (!email || !username || !code) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + const storedData = verificationCodes.get(email); + + if (!storedData) { + return res.json({ok: false, message: '请先获取验证码'}); + } + + // 验证码10分钟过期 + if (Date.now() - storedData.timestamp > 10 * 60 * 1000) { + verificationCodes.delete(email); + return res.json({ok: false, message: '验证码已过期,请重新获取'}); + } + + if (storedData.code !== code) { + return res.json({ok: false, message: '验证码不正确'}); + } + + if (storedData.username !== username) { + return res.json({ok: false, message: '用户名与验证时不一致'}); + } + + // 清除验证码 + verificationCodes.delete(email); + + res.json({ok: true, message: '验证成功,请设置密码'}); + } catch (error) { + console.error('验证码验证错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 注册(创建用户 + 设置密码) +app.post('/api/register', (req, res) => { + try { + const {email, username, password} = req.body; + + console.log('收到注册请求:', {email, username}); + + if (!email || !username || !password) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + // 创建用户并设置密码 + userManager.createUser(email, username); + userManager.setPassword(email, password); + + res.json({ok: true, message: '注册成功'}); + } catch (error) { + console.error('注册错误:', error); + res.json({ok: false, message: error.message}); + } +}); + + +// 登录 +app.post('/api/login', (req, res) => { + try { + const {account, password} = req.body; + + if (!account || !password) { + return res.json({ok: false, message: '请填写账号和密码'}); + } + + // 通过邮箱或用户名查找用户 + let user = userManager.findUserByEmail(account); + if (!user) { + user = userManager.findUserByUsername(account); + } + + if (!user || !user.password) { + return res.json({ok: false, message: '用户不存在'}); + } + + if (!userManager.verifyPassword(user.email, password)) { + return res.json({ok: false, message: '密码不正确'}); + } + + res.json({ + ok: true, + message: '登录成功', + data: { + email: user.email, + username: user.username, + }, + }); + } catch (error) { + console.error('登录错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 修改密码 +app.post('/api/change-password', (req, res) => { + try { + const {email, oldPassword, newPassword} = req.body; + + if (!email || !oldPassword || !newPassword) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + userManager.changePassword(email, oldPassword, newPassword); + res.json({ok: true, message: '密码修改成功'}); + } catch (error) { + console.error('修改密码错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 修改用户名 +app.post('/api/change-username', (req, res) => { + try { + const {email, username} = req.body; + + if (!email || !username) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + userManager.changeUsername(email, username); + res.json({ok: true, message: '用户名修改成功'}); + } catch (error) { + console.error('修改用户名错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 删除账号 +app.post('/api/delete-account', (req, res) => { + try { + const {email, password} = req.body; + + if (!email || !password) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + // 验证密码 + if (!userManager.verifyPassword(email, password)) { + return res.json({ok: false, message: '密码不正确'}); + } + + userManager.deleteUser(email); + res.json({ok: true, message: '账号删除成功'}); + } catch (error) { + console.error('删除账号错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 获取题目 +app.get('/api/questions', (req, res) => { + try { + const {grade, count} = req.query; + + if (!grade || !count) { + return res.json({ok: false, message: '请选择年级和题目数量'}); + } + + const countNum = parseInt(count); + if (isNaN(countNum) || countNum < 10 || countNum > 30) { + return res.json({ok: false, message: '题目数量需在10-30之间'}); + } + + const generator = new MathQuestionGenerator(); + const questions = generator.generateQuestions(grade, countNum); + res.json({ + ok: true, + data: questions, + message: '题目生成成功', + }); + } catch (error) { + console.error('生成题目失败:', error); + res.json({ok: false, message: '生成题目失败'}); + } +}); + +// 提供前端页面 +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '../project4/index.html')); +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`========================================`); + console.log(`🎓 数学学习软件后端服务已启动`); + console.log(`📍 服务地址: http://localhost:${PORT}`); + console.log(`🌐 前端页面: http://localhost:${PORT}/`); + console.log(`🔧 API文档: http://localhost:${PORT}/api/health`); + console.log(`📧 邮箱服务: ${emailService.transporter ? '已配置' : '测试模式'}`); + console.log(`========================================`); +}); + +module.exports = app; + diff --git a/src/backend/utils/multi-email-service.js b/src/backend/utils/multi-email-service.js new file mode 100644 index 0000000..bba61fb --- /dev/null +++ b/src/backend/utils/multi-email-service.js @@ -0,0 +1,104 @@ +const nodemailer = require('nodemailer'); + +// 多邮箱服务类,支持多种邮箱服务商 +class MultiEmailService { + constructor() { + this.transporters = new Map(); + this.initTransporters(); + } + + // 初始化各种邮箱服务商的传输器 + initTransporters() { + // QQ邮箱配置 + try { + // 修改 + const qqTransporter = nodemailer.createTransport({ + auth: { + pass: 'vxhsswmmqiyvchhh', + user: '3454934335@qq.com', + }, + service: 'qq', + }); + this.transporters.set('qq', qqTransporter); + console.log('✅ QQ邮箱服务已初始化'); + } catch (error) { + console.error('❌ QQ邮箱初始化失败:', error); + } + + // 163邮箱配置 + try { + // 修改 + const mail163Transporter = nodemailer.createTransport({ + auth: { + pass: 'UCgJrE7yzzd4Uz3g', + user: '18950579895@163.com', + }, + service: '163', + }); + this.transporters.set('163', mail163Transporter); + console.log('✅ 163邮箱服务已初始化'); + } catch (error) { + console.error('❌ 163邮箱初始化失败:', error); + } + } + + // 发送验证码邮件 + async sendVerificationCode(email, code, username) { + // 根据邮箱域名选择发件箱 + let transporter = email.includes('@qq.com') ? this.transporters.get('qq') : + email.includes('@163.com') ? this.transporters.get('163') : + this.transporters.get('qq'); + + if (!transporter) { + console.log(`🔧 测试模式 - 邮箱: ${email}, 验证码: ${code}`); + return {success: true, message: '验证码已生成(测试模式)', debug: `验证码: ${code}`}; + } + + const mailOptions = { + from: transporter.options.auth.user, + to: email, + subject: '数学学习软件 - 注册验证码', + html: this.generateEmailTemplate(code, username) + }; + + try { + await transporter.sendMail(mailOptions); + console.log(`✅ 验证码邮件已发送到: ${email}`); + return {success: true, message: '验证码已发送到您的邮箱'}; + } catch (error) { + console.error('❌ 邮件发送失败:', error); + return {success: false, message: '邮件发送失败,请稍后重试', debug: `验证码: ${code} (请使用此验证码完成注册)`}; + } + } + + // 生成邮件HTML模板 + generateEmailTemplate(code, username) { + return ` + + + + + + + +
+

🎓 数学学习软件

+

亲爱的 ${username},您好!

+

您正在注册数学学习软件,验证码为:

+
${code}
+

验证码有效期为10分钟,请尽快完成注册。

+

如果这不是您本人的操作,请忽略此邮件。

+
+ + + `; + } +} + +module.exports = MultiEmailService; + diff --git a/src/backend/utils/question-generator.js b/src/backend/utils/question-generator.js new file mode 100644 index 0000000..2457bc6 --- /dev/null +++ b/src/backend/utils/question-generator.js @@ -0,0 +1,441 @@ +const fs = require('fs'); +const path = require('path'); + +class MathQuestionGenerator { + constructor() { + this.currentSessionQuestions = new Set(); + } + + // 生成指定年级和数量的题目 + generateQuestions(grade, count) { + console.log(`正在生成${grade} ${count}道题目...`); + const questions = []; + this.currentSessionQuestions.clear(); + + const maxAttempts = count * 20; + let attempts = 0; + + while (questions.length < count && attempts < maxAttempts) { + attempts++; + const question = this.generateQuestion(grade); + if (!question) { + continue; + } + + const questionKey = `${grade}-${question.stem}`; + + if (!this.currentSessionQuestions.has(questionKey)) { + questions.push(question); + this.currentSessionQuestions.add(questionKey); + console.log(`✅ 生成第${questions.length}题: ${question.stem}`); + } + } + + if (questions.length < count) { + console.warn(`⚠️ 只生成了${questions.length}道题目,未能达到要求的${count}道`); + } + + return questions; + } + + // 根据年级生成单个题目 + generateQuestion(grade) { + try { + switch (grade) { + case '小学': + return this.generatePrimaryQuestion(); + case '初中': + return this.generateMiddleSchoolQuestion(); + case '高中': + return this.generateHighSchoolQuestion(); + default: + return this.generatePrimaryQuestion(); + } + } catch (error) { + console.error(`生成${grade}题目时出错:`, error); + return null; + } + } + + // 生成小学题目(2-5个操作数,基础四则运算) + generatePrimaryQuestion() { + const numOperands = Math.floor(Math.random() * 4) + 2; + const operations = ['+', '-', '×', '÷']; + let expression = ''; + let correctAnswer = 0; + + for (let i = 0; i < numOperands; i++) { + const num = Math.floor(Math.random() * 100) + 1; + if (i === 0) { + expression = num.toString(); + correctAnswer = num; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${num}`; + correctAnswer = this.applyOperation(correctAnswer, num, op); + } + } + + correctAnswer = this.calculateWithPriority(expression); + if (numOperands >= 3 && Math.random() < 0.3) { + const result = this.addParentheses(expression, correctAnswer); + if (result) { + expression = result.expression; + correctAnswer = result.answer; + } + } + + const options = this.generateOptions(correctAnswer, 4); + return { + id: `primary-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + // 生成初中题目(1-5个操作数,包含平方和开方) + generateMiddleSchoolQuestion() { + const numOperands = Math.floor(Math.random() * 5) + 1; + const operations = ['+', '-', '×', '÷']; + const specialPosition = Math.floor(Math.random() * numOperands); + const isSquare = Math.random() < 0.5; + let expression = ''; + let correctAnswer = 0; + + for (let i = 0; i < numOperands; i++) { + const {term, value} = i === specialPosition ? + this.generateSpecialTerm(isSquare) : this.generateRandomTerm(); + if (i === 0) { + expression = term; + correctAnswer = value; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + correctAnswer = this.applyOperation(correctAnswer, value, op); + } + } + + correctAnswer = this.calculateWithPriority(expression); + if (numOperands >= 3 && Math.random() < 0.4) { + const result = this.addParentheses(expression, correctAnswer); + if (result) {expression = result.expression; correctAnswer = result.answer;} + } + + const options = this.generateOptions(correctAnswer, 4); + return { + id: `middle-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + // 生成特殊项(平方或开方) + generateSpecialTerm(isSquare) { + if (isSquare) { + const base = Math.floor(Math.random() * 15) + 1; + return { term: `${base}²`, value: base * base }; + } else { + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + return { term: `√${num}`, value: Math.sqrt(num) }; + } + } + + // 生成随机项(数字、平方或开方) + generateRandomTerm() { + const termType = Math.random(); + if (termType < 0.3) { + const base = Math.floor(Math.random() * 15) + 1; + return { term: `${base}²`, value: base * base }; + } else if (termType < 0.6) { + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + return { term: `√${num}`, value: Math.sqrt(num) }; + } else { + const num = Math.floor(Math.random() * 100) + 1; + return { term: num.toString(), value: num }; + } + } + + // 生成高中题目(1-5个操作数,包含三角函数) + generateHighSchoolQuestion() { + const numOperands = Math.floor(Math.random() * 5) + 1; + const operations = ['+', '-', '×', '÷']; + const specialPosition = Math.floor(Math.random() * numOperands); + let expression = '', correctAnswer = 0; + + for (let i = 0; i < numOperands; i++) { + const {term, value} = i === specialPosition ? + this.generateTrigTerm() : (Math.random() < 0.4 ? this.generateTrigTerm() : this.generateNumberTerm()); + + if (i === 0) { expression = term; correctAnswer = value; } + else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + correctAnswer = this.applyOperation(correctAnswer, value, op); + } + } + + correctAnswer = this.calculateWithPriority(expression); + if (numOperands >= 3 && Math.random() < 0.4) { + const result = this.addParentheses(expression, correctAnswer); + if (result) { expression = result.expression; correctAnswer = result.answer; } + } + + const options = this.generateOptions(correctAnswer, 4); + return { + id: `high-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + // 生成三角函数项 + generateTrigTerm() { + const functions = ['sin', 'cos', 'tan']; + const func = functions[Math.floor(Math.random() * functions.length)]; + const angle = Math.floor(Math.random() * 100) + 1; + const value = Math.round(Math[func](angle * Math.PI / 180) * 100) / 100; + return { term: `${func}${angle}`, value }; + } + + // 生成数字项 + generateNumberTerm() { + const num = Math.floor(Math.random() * 100) + 1; + return { term: num.toString(), value: num }; + } + + // 生成选择题选项 + generateOptions(correctAnswer, count) { + const options = []; + const keys = ['A', 'B', 'C', 'D']; + const correctIndex = Math.floor(Math.random() * count); + const isInteger = typeof correctAnswer === 'number' && Number.isInteger(correctAnswer); + + for (let i = 0; i < count; i++) { + const value = i === correctIndex ? correctAnswer : this.generateWrongOption(correctAnswer, isInteger, i); + options.push({key: keys[i], text: value.toString(), isCorrect: i === correctIndex}); + } + return options; + } + + // 生成错误选项 + generateWrongOption(correctAnswer, isInteger, index) { + if (typeof correctAnswer !== 'number') { + return Math.random().toString(36).substring(2, 6); + } + + let attempts = 0; + let value; + do { + const deviation = (Math.random() - 0.5) * 4; + value = isInteger ? + correctAnswer + Math.floor(Math.random() * 10) - 5 : + Math.round((correctAnswer + deviation) * 100) / 100; + + if (++attempts > 10) { + value = correctAnswer + (index + 1); + if (isInteger) { + value = Math.round(value); + } + break; + } + } while (value === correctAnswer); + return value; + } + + // 获取数字的所有因数 + getDivisors(n) { + const divisors = []; + for (let i = 2; i <= Math.min(n, 100); i++) { // 除数也限制在1-100范围内 + if (n % i === 0) divisors.push(i); + } + return divisors; + } + + // 小学题目专用的括号添加函数,确保括号内不会产生负数 + addParenthesesForPrimary(expression, originalAnswer) { + const parts = expression.split(' '); + + // 如果表达式太短,不需要加括号 + if (parts.length < 5) return null; + + // 找到所有可以加括号的位置(运算符位置) + const operatorPositions = []; + for (let i = 1; i < parts.length - 1; i += 2) { + // 只考虑加法和乘法,避免减法导致负数 + if (parts[i] === '+' || parts[i] === '×') { + operatorPositions.push(i); + } + } + + if (operatorPositions.length === 0) return null; + + // 随机选择一个运算符位置 + const operatorIndex = operatorPositions[Math.floor(Math.random() * operatorPositions.length)]; + + // 确定括号的范围(从运算符前一个操作数到运算符后一个操作数) + const startPos = operatorIndex - 1; + const endPos = operatorIndex + 1; + + // 构建带括号的表达式 + let result = ''; + for (let i = 0; i < parts.length; i++) { + if (i === startPos) { + result += '('; + } + + result += parts[i]; + + if (i === endPos) { + result += ')'; + } + + if (i < parts.length - 1) { + result += ' '; + } + } + + // 计算带括号的答案 + let newAnswer = this.calculateWithPriority(result); + + // 确保答案是非负整数 + if (newAnswer < 0 || !Number.isInteger(newAnswer)) { + return null; + } + + return { + answer: newAnswer, + expression: result, + }; + } + + // 通用的括号添加函数 + // 为表达式添加括号 + addParentheses(expression, originalAnswer) { + const parts = expression.split(' '); + + // 如果表达式太短,不需要加括号 + if (parts.length < 5) return null; + + // 找到所有可以加括号的位置(运算符位置) + const operatorPositions = []; + for (let i = 1; i < parts.length - 1; i += 2) { + operatorPositions.push(i); + } + + if (operatorPositions.length === 0) return null; + + // 随机选择一个运算符位置 + const operatorIndex = operatorPositions[Math.floor(Math.random() * operatorPositions.length)]; + + // 确定括号的范围(从运算符前一个操作数到运算符后一个操作数) + const startPos = operatorIndex - 1; + const endPos = operatorIndex + 1; + + // 构建带括号的表达式 + let result = ''; + for (let i = 0; i < parts.length; i++) { + if (i === startPos) { + result += '('; + } + + result += parts[i]; + + if (i === endPos) { + result += ')'; + } + + if (i < parts.length - 1) { + result += ' '; + } + } + + // 计算带括号的答案 + let newAnswer = this.calculateWithPriority(result); + + return { + answer: newAnswer, + expression: result, + }; + } + + // 执行四则运算操作 + applyOperation(current, value, op) { + switch (op) { + case '+': return current + value; + case '-': return current - value; + case '×': return current * value; + case '÷': return value !== 0 ? current / value : current; + default: return current; + } + } + + // 使用正确优先级计算表达式的答案 + calculateWithPriority(expression) { + // 替换运算符为JavaScript可识别的 + let jsExpression = expression + .replace(/×/g, '*') + .replace(/÷/g, '/') + .replace(/²/g, '**2') + .replace(/√(\d+)/g, 'Math.sqrt($1)') + .replace(/sin(\d+)/g, 'Math.sin($1 * Math.PI / 180)') + .replace(/cos(\d+)/g, 'Math.cos($1 * Math.PI / 180)') + .replace(/tan(\d+)/g, 'Math.tan($1 * Math.PI / 180)'); + + try { + // 使用eval计算表达式 + let result = eval(jsExpression); + + // 处理特殊情况 + if (typeof result === 'number') { + // 如果是整数,返回整数 + if (Number.isInteger(result)) { + return result; + } + // 否则保留两位小数 + return Math.round(result * 100) / 100; + } + + return result; + } catch (error) { + console.error('计算表达式时出错:', expression, error); + // 如果计算失败,返回原始表达式的估算值 + return this.estimateExpression(expression); + } + } + + // 估算表达式的值(当eval失败时使用) + estimateExpression(expression) { + // 简单的估算逻辑,按顺序计算 + const parts = expression.split(' '); + let result = parseFloat(parts[0]); + + for (let i = 1; i < parts.length; i += 2) { + const operator = parts[i]; + const num = parseFloat(parts[i + 1]); + + switch (operator) { + case '+': + result += num; + break; + case '-': + result -= num; + break; + case '×': + result *= num; + break; + case '÷': + if (num !== 0) result /= num; + break; + } + } + + return Math.round(result * 100) / 100; + } +} + +module.exports = MathQuestionGenerator; \ No newline at end of file diff --git a/src/backend/utils/user-manager.js b/src/backend/utils/user-manager.js new file mode 100644 index 0000000..3b4929f --- /dev/null +++ b/src/backend/utils/user-manager.js @@ -0,0 +1,183 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const USERS_FILE = path.join(__dirname, '../data/users.json'); + +// 用户管理类,处理用户注册、登录等操作 +class UserManager { + constructor() { + this.ensureDataFile(); + } + + // 确保数据文件存在 + ensureDataFile() { + const dataDir = path.dirname(USERS_FILE); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, {recursive: true}); + } + if (!fs.existsSync(USERS_FILE)) { + fs.writeFileSync(USERS_FILE, JSON.stringify([])); + } + } + + // 读取所有用户数据 + readUsers() { + try { + return JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); + } catch (error) { + console.error('读取用户数据失败:', error); + return []; + } + } + + // 保存用户数据到文件 + writeUsers(users) { + try { + fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); + return true; + } catch (error) { + console.error('写入用户数据失败:', error); + return false; + } + } + + // 对密码进行SHA256哈希加密 + hashPassword(password) { + return crypto.createHash('sha256').update(password).digest('hex'); + } + + // 验证密码格式(6-10位) + validatePassword(password) { + if (!password || password.length < 6 || password.length > 10) { + return false; + } + const hasUpper = /[A-Z]/.test(password); + const hasLower = /[a-z]/.test(password); + const hasDigit = /\d/.test(password); + return hasUpper && hasLower && hasDigit; + } + + findUserByEmail(email) { + const users = this.readUsers(); + return users.find((user) => user.email === email); + } + + findUserByUsername(username) { + const users = this.readUsers(); + return users.find((user) => user.username === username); + } + + createUser(email, username) { + const users = this.readUsers(); + + // 严格检查邮箱是否已存在 + if (users.some((user) => user.email === email)) { + throw new Error('邮箱已被注册,每个邮箱只能注册一个账户'); + } + + // 检查用户名是否已存在 + if (users.some((user) => user.username === username)) { + throw new Error('用户名已存在'); + } + + const newUser = { + email, + registeredAt: new Date().toISOString(), + username, + }; + + users.push(newUser); + if (this.writeUsers(users)) { + console.log(`✅ 新用户注册: ${username} (${email})`); + return newUser; + } else { + throw new Error('用户创建失败'); + } + } + + setPassword(email, password) { + const users = this.readUsers(); + const userIndex = users.findIndex((user) => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + if (!this.validatePassword(password)) { + throw new Error('密码需6-10位且包含大小写字母和数字'); + } + + users[userIndex].password = this.hashPassword(password); + users[userIndex].updatedAt = new Date().toISOString(); + + if (this.writeUsers(users)) { + console.log(`✅ 用户设置密码: ${email}`); + return true; + } else { + throw new Error('密码设置失败'); + } + } + + verifyPassword(email, password) { + const user = this.findUserByEmail(email); + if (!user || !user.password) { + return false; + } + return user.password === this.hashPassword(password); + } + + changePassword(email, oldPassword, newPassword) { + if (!this.verifyPassword(email, oldPassword)) { + throw new Error('原密码不正确'); + } + + return this.setPassword(email, newPassword); + } + + changeUsername(email, newUsername) { + const users = this.readUsers(); + const userIndex = users.findIndex((user) => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + // 检查新用户名是否已存在(排除当前用户) + if (users.some((user) => user.username === newUsername && user.email !== email)) { + throw new Error('用户名已存在'); + } + + const oldUsername = users[userIndex].username; + users[userIndex].username = newUsername; + users[userIndex].updatedAt = new Date().toISOString(); + + if (this.writeUsers(users)) { + console.log(`✅ 用户修改用户名: ${oldUsername} -> ${newUsername}`); + return true; + } else { + throw new Error('用户名修改失败'); + } + } + + deleteUser(email) { + const users = this.readUsers(); + const userIndex = users.findIndex((user) => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + const deletedUser = users[userIndex]; + users.splice(userIndex, 1); + + if (this.writeUsers(users)) { + console.log(`✅ 用户账号已删除: ${deletedUser.username} (${email})`); + return true; + } else { + throw new Error('账号删除失败'); + } + } +} + +module.exports = UserManager; \ No newline at end of file -- 2.34.1 From efbe36a3691ea99df3c23734c4502b4e303f45bc Mon Sep 17 00:00:00 2001 From: wwb <3454934335@qq.com> Date: Sun, 12 Oct 2025 14:55:25 +0800 Subject: [PATCH 12/13] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=89=80=E6=9C=89?= =?UTF-8?q?=E7=8E=B0=E6=9C=89=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/data/users.json | 16 - src/backend/email-config.js | 31 - src/backend/package-lock.json | 1243 ---------------------- src/backend/package.json | 35 - src/backend/server.js | 310 ------ src/backend/utils/multi-email-service.js | 104 -- src/backend/utils/question-generator.js | 441 -------- src/backend/utils/user-manager.js | 183 ---- 8 files changed, 2363 deletions(-) delete mode 100644 src/backend/data/users.json delete mode 100644 src/backend/email-config.js delete mode 100644 src/backend/package-lock.json delete mode 100644 src/backend/package.json delete mode 100644 src/backend/server.js delete mode 100644 src/backend/utils/multi-email-service.js delete mode 100644 src/backend/utils/question-generator.js delete mode 100644 src/backend/utils/user-manager.js diff --git a/src/backend/data/users.json b/src/backend/data/users.json deleted file mode 100644 index ea595de..0000000 --- a/src/backend/data/users.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "email": "3454934335@qq.com", - "username": "wwb", - "registeredAt": "2025-10-11T05:26:22.038Z", - "password": "cc43f52af644042612d98dea06cf5b64d8e2f8af1de8065fe83b208f4a6d8875", - "updatedAt": "2025-10-11T05:26:22.051Z" - }, - { - "email": "3063485007@qq.com", - "username": "wqs", - "registeredAt": "2025-10-11T19:08:09.303Z", - "password": "be0c664d19564b568c4dd76d00a26a08ee89214877e52225eee5ffefab36c15f", - "updatedAt": "2025-10-11T19:08:26.339Z" - } -] \ No newline at end of file diff --git a/src/backend/email-config.js b/src/backend/email-config.js deleted file mode 100644 index 5a4c8e7..0000000 --- a/src/backend/email-config.js +++ /dev/null @@ -1,31 +0,0 @@ -// ============================================= -// 数学学习软件 - 邮箱配置文件 -// ============================================= - - -// ==================== 选择1: QQ邮箱服务 ==================== -module.exports = { - service: 'qq', - auth: { - user: '3454934335@qq.com', // 替换为你的QQ邮箱 - pass: 'vxhsswmmqiyvchhh' // 替换为QQ邮箱授权码(16位) - } -}; - -// ==================== 选择2: 163邮箱服务 ==================== -module.exports = { - service: '163', - auth: { - user: '18950579895@163.com',// 替换为你的163邮箱 - pass: 'UCgJrE7yzzd4Uz3g' // 替换为163邮箱授权码 - } -}; - - -// ==================== 选择3: 测试模式 ==================== -// 不发送实际邮件,验证码在控制台显示 -// 适用于开发和测试环境 -/* -module.exports = {}; -*/ - diff --git a/src/backend/package-lock.json b/src/backend/package-lock.json deleted file mode 100644 index 52de788..0000000 --- a/src/backend/package-lock.json +++ /dev/null @@ -1,1243 +0,0 @@ -{ - "name": "math-learning-backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "math-learning-backend", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "body-parser": "^1.20.2", - "cors": "^2.8.5", - "express": "^4.18.2", - "nodemailer": "^6.9.7" - }, - "devDependencies": { - "nodemon": "^3.0.1" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmmirror.com/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/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/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/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/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/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/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/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/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/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmmirror.com/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/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/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/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmmirror.com/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/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/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/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/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/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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": "3.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/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/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/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/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/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-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nodemailer": { - "version": "6.10.1", - "resolved": "https://registry.npmmirror.com/nodemailer/-/nodemailer-6.10.1.tgz", - "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nodemon": { - "version": "3.1.10", - "resolved": "https://registry.npmmirror.com/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.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmmirror.com/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/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmmirror.com/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/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/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/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/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/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmmirror.com/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/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/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/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/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/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/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/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - } - } -} diff --git a/src/backend/package.json b/src/backend/package.json deleted file mode 100644 index 2d33a16..0000000 --- a/src/backend/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "math-learning-backend", - "version": "1.0.0", - "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "test": "echo \"后端服务测试模式\" && node server.js" - }, - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", - "body-parser": "^1.20.2", - "nodemailer": "^6.9.7" - }, - "devDependencies": { - "nodemon": "^3.0.1" - }, - "keywords": [ - "education", - "math", - "learning", - "email", - "qq", - "163" - ], - "author": "数学学习软件团队", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/your-repo/math-learning-software" - } -} - diff --git a/src/backend/server.js b/src/backend/server.js deleted file mode 100644 index 5896e8b..0000000 --- a/src/backend/server.js +++ /dev/null @@ -1,310 +0,0 @@ -const express = require('express'); -const cors = require('cors'); -const bodyParser = require('body-parser'); -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -// 导入工具类 -const UserManager = require('./utils/user-manager'); -const MultiEmailService = require('./utils/multi-email-service'); -const MathQuestionGenerator = require('./utils/question-generator'); - -// 创建Express应用实例 -const app = express(); -const PORT = 8080; - -// 配置中间件 -app.use(cors()); -app.use(bodyParser.json()); -app.use(express.static(path.join(__dirname, '../project4'))); - -// 实例化管理器 -const userManager = new UserManager(); -const emailService = new MultiEmailService(); - -// 存储验证码(内存存储,重启后失效) -const verificationCodes = new Map(); - -// 生成6位数字验证码 -function generateVerificationCode() { - return Math.floor(100000 + Math.random() * 900000).toString(); -} - -// 验证邮箱格式是否正确 -function validateEmail(email) { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); -} - -// API路由 - -// 检查服务器健康状态 -app.get('/api/health', (req, res) => { - res.json({ - ok: true, - message: '服务正常运行', - emailService: emailService.transporter ? '已配置' : '测试模式' - }); -}); - -// 发送邮箱验证码 -app.post('/api/send-code', async (req, res) => { - try { - const {email, username} = req.body; - - console.log('收到发送验证码请求:', {email, username}); - - if (!email || !username) { - return res.json({ok: false, message: '邮箱和用户名不能为空'}); - } - - // 验证邮箱格式 - if (!validateEmail(email)) { - return res.json({ok: false, message: '邮箱格式不正确'}); - } - - // 限制邮箱域名,只允许QQ邮箱和163邮箱 - if (!email.includes('@qq.com') && !email.includes('@163.com')) { - return res.json({ok: false, message: '只支持QQ邮箱和163邮箱'}); - } - - // 检查用户名是否已存在 - if (userManager.findUserByUsername(username)) { - return res.json({ok: false, message: '用户名已存在'}); - } - - // 检查邮箱是否已注册 - if (userManager.findUserByEmail(email)) { - return res.json({ok: false, message: '邮箱已被注册'}); - } - - const code = generateVerificationCode(); - verificationCodes.set(email, { - code, - username, - timestamp: Date.now(), - }); - - console.log(`为邮箱 ${email} 生成验证码: ${code}`); - - // 发送邮件 - const emailResult = await emailService.sendVerificationCode(email, code, username); - - if (emailResult.success) { - res.json({ok: true, message: emailResult.message}); - } else { - // 如果邮件发送失败,但在开发模式下提供了验证码,仍然算成功 - if (emailResult.debug) { - res.json({ok: true, message: emailResult.message + ' ' + emailResult.debug}); - } else { - res.json({ok: false, message: emailResult.message}); - } - } - } catch (error) { - console.error('发送验证码错误:', error); - res.json({ok: false, message: '服务器内部错误'}); - } -}); - -// 验证码验证 -app.post('/api/verify-code', (req, res) => { - try { - const {email, username, code} = req.body; - - console.log('收到验证码验证请求:', {email, username, code}); - - if (!email || !username || !code) { - return res.json({ok: false, message: '请填写完整信息'}); - } - - const storedData = verificationCodes.get(email); - - if (!storedData) { - return res.json({ok: false, message: '请先获取验证码'}); - } - - // 验证码10分钟过期 - if (Date.now() - storedData.timestamp > 10 * 60 * 1000) { - verificationCodes.delete(email); - return res.json({ok: false, message: '验证码已过期,请重新获取'}); - } - - if (storedData.code !== code) { - return res.json({ok: false, message: '验证码不正确'}); - } - - if (storedData.username !== username) { - return res.json({ok: false, message: '用户名与验证时不一致'}); - } - - // 清除验证码 - verificationCodes.delete(email); - - res.json({ok: true, message: '验证成功,请设置密码'}); - } catch (error) { - console.error('验证码验证错误:', error); - res.json({ok: false, message: error.message}); - } -}); - -// 注册(创建用户 + 设置密码) -app.post('/api/register', (req, res) => { - try { - const {email, username, password} = req.body; - - console.log('收到注册请求:', {email, username}); - - if (!email || !username || !password) { - return res.json({ok: false, message: '请填写完整信息'}); - } - - // 创建用户并设置密码 - userManager.createUser(email, username); - userManager.setPassword(email, password); - - res.json({ok: true, message: '注册成功'}); - } catch (error) { - console.error('注册错误:', error); - res.json({ok: false, message: error.message}); - } -}); - - -// 登录 -app.post('/api/login', (req, res) => { - try { - const {account, password} = req.body; - - if (!account || !password) { - return res.json({ok: false, message: '请填写账号和密码'}); - } - - // 通过邮箱或用户名查找用户 - let user = userManager.findUserByEmail(account); - if (!user) { - user = userManager.findUserByUsername(account); - } - - if (!user || !user.password) { - return res.json({ok: false, message: '用户不存在'}); - } - - if (!userManager.verifyPassword(user.email, password)) { - return res.json({ok: false, message: '密码不正确'}); - } - - res.json({ - ok: true, - message: '登录成功', - data: { - email: user.email, - username: user.username, - }, - }); - } catch (error) { - console.error('登录错误:', error); - res.json({ok: false, message: error.message}); - } -}); - -// 修改密码 -app.post('/api/change-password', (req, res) => { - try { - const {email, oldPassword, newPassword} = req.body; - - if (!email || !oldPassword || !newPassword) { - return res.json({ok: false, message: '请填写完整信息'}); - } - - userManager.changePassword(email, oldPassword, newPassword); - res.json({ok: true, message: '密码修改成功'}); - } catch (error) { - console.error('修改密码错误:', error); - res.json({ok: false, message: error.message}); - } -}); - -// 修改用户名 -app.post('/api/change-username', (req, res) => { - try { - const {email, username} = req.body; - - if (!email || !username) { - return res.json({ok: false, message: '请填写完整信息'}); - } - - userManager.changeUsername(email, username); - res.json({ok: true, message: '用户名修改成功'}); - } catch (error) { - console.error('修改用户名错误:', error); - res.json({ok: false, message: error.message}); - } -}); - -// 删除账号 -app.post('/api/delete-account', (req, res) => { - try { - const {email, password} = req.body; - - if (!email || !password) { - return res.json({ok: false, message: '请填写完整信息'}); - } - - // 验证密码 - if (!userManager.verifyPassword(email, password)) { - return res.json({ok: false, message: '密码不正确'}); - } - - userManager.deleteUser(email); - res.json({ok: true, message: '账号删除成功'}); - } catch (error) { - console.error('删除账号错误:', error); - res.json({ok: false, message: error.message}); - } -}); - -// 获取题目 -app.get('/api/questions', (req, res) => { - try { - const {grade, count} = req.query; - - if (!grade || !count) { - return res.json({ok: false, message: '请选择年级和题目数量'}); - } - - const countNum = parseInt(count); - if (isNaN(countNum) || countNum < 10 || countNum > 30) { - return res.json({ok: false, message: '题目数量需在10-30之间'}); - } - - const generator = new MathQuestionGenerator(); - const questions = generator.generateQuestions(grade, countNum); - res.json({ - ok: true, - data: questions, - message: '题目生成成功', - }); - } catch (error) { - console.error('生成题目失败:', error); - res.json({ok: false, message: '生成题目失败'}); - } -}); - -// 提供前端页面 -app.get('/', (req, res) => { - res.sendFile(path.join(__dirname, '../project4/index.html')); -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log(`========================================`); - console.log(`🎓 数学学习软件后端服务已启动`); - console.log(`📍 服务地址: http://localhost:${PORT}`); - console.log(`🌐 前端页面: http://localhost:${PORT}/`); - console.log(`🔧 API文档: http://localhost:${PORT}/api/health`); - console.log(`📧 邮箱服务: ${emailService.transporter ? '已配置' : '测试模式'}`); - console.log(`========================================`); -}); - -module.exports = app; - diff --git a/src/backend/utils/multi-email-service.js b/src/backend/utils/multi-email-service.js deleted file mode 100644 index bba61fb..0000000 --- a/src/backend/utils/multi-email-service.js +++ /dev/null @@ -1,104 +0,0 @@ -const nodemailer = require('nodemailer'); - -// 多邮箱服务类,支持多种邮箱服务商 -class MultiEmailService { - constructor() { - this.transporters = new Map(); - this.initTransporters(); - } - - // 初始化各种邮箱服务商的传输器 - initTransporters() { - // QQ邮箱配置 - try { - // 修改 - const qqTransporter = nodemailer.createTransport({ - auth: { - pass: 'vxhsswmmqiyvchhh', - user: '3454934335@qq.com', - }, - service: 'qq', - }); - this.transporters.set('qq', qqTransporter); - console.log('✅ QQ邮箱服务已初始化'); - } catch (error) { - console.error('❌ QQ邮箱初始化失败:', error); - } - - // 163邮箱配置 - try { - // 修改 - const mail163Transporter = nodemailer.createTransport({ - auth: { - pass: 'UCgJrE7yzzd4Uz3g', - user: '18950579895@163.com', - }, - service: '163', - }); - this.transporters.set('163', mail163Transporter); - console.log('✅ 163邮箱服务已初始化'); - } catch (error) { - console.error('❌ 163邮箱初始化失败:', error); - } - } - - // 发送验证码邮件 - async sendVerificationCode(email, code, username) { - // 根据邮箱域名选择发件箱 - let transporter = email.includes('@qq.com') ? this.transporters.get('qq') : - email.includes('@163.com') ? this.transporters.get('163') : - this.transporters.get('qq'); - - if (!transporter) { - console.log(`🔧 测试模式 - 邮箱: ${email}, 验证码: ${code}`); - return {success: true, message: '验证码已生成(测试模式)', debug: `验证码: ${code}`}; - } - - const mailOptions = { - from: transporter.options.auth.user, - to: email, - subject: '数学学习软件 - 注册验证码', - html: this.generateEmailTemplate(code, username) - }; - - try { - await transporter.sendMail(mailOptions); - console.log(`✅ 验证码邮件已发送到: ${email}`); - return {success: true, message: '验证码已发送到您的邮箱'}; - } catch (error) { - console.error('❌ 邮件发送失败:', error); - return {success: false, message: '邮件发送失败,请稍后重试', debug: `验证码: ${code} (请使用此验证码完成注册)`}; - } - } - - // 生成邮件HTML模板 - generateEmailTemplate(code, username) { - return ` - - - - - - - -
-

🎓 数学学习软件

-

亲爱的 ${username},您好!

-

您正在注册数学学习软件,验证码为:

-
${code}
-

验证码有效期为10分钟,请尽快完成注册。

-

如果这不是您本人的操作,请忽略此邮件。

-
- - - `; - } -} - -module.exports = MultiEmailService; - diff --git a/src/backend/utils/question-generator.js b/src/backend/utils/question-generator.js deleted file mode 100644 index 2457bc6..0000000 --- a/src/backend/utils/question-generator.js +++ /dev/null @@ -1,441 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -class MathQuestionGenerator { - constructor() { - this.currentSessionQuestions = new Set(); - } - - // 生成指定年级和数量的题目 - generateQuestions(grade, count) { - console.log(`正在生成${grade} ${count}道题目...`); - const questions = []; - this.currentSessionQuestions.clear(); - - const maxAttempts = count * 20; - let attempts = 0; - - while (questions.length < count && attempts < maxAttempts) { - attempts++; - const question = this.generateQuestion(grade); - if (!question) { - continue; - } - - const questionKey = `${grade}-${question.stem}`; - - if (!this.currentSessionQuestions.has(questionKey)) { - questions.push(question); - this.currentSessionQuestions.add(questionKey); - console.log(`✅ 生成第${questions.length}题: ${question.stem}`); - } - } - - if (questions.length < count) { - console.warn(`⚠️ 只生成了${questions.length}道题目,未能达到要求的${count}道`); - } - - return questions; - } - - // 根据年级生成单个题目 - generateQuestion(grade) { - try { - switch (grade) { - case '小学': - return this.generatePrimaryQuestion(); - case '初中': - return this.generateMiddleSchoolQuestion(); - case '高中': - return this.generateHighSchoolQuestion(); - default: - return this.generatePrimaryQuestion(); - } - } catch (error) { - console.error(`生成${grade}题目时出错:`, error); - return null; - } - } - - // 生成小学题目(2-5个操作数,基础四则运算) - generatePrimaryQuestion() { - const numOperands = Math.floor(Math.random() * 4) + 2; - const operations = ['+', '-', '×', '÷']; - let expression = ''; - let correctAnswer = 0; - - for (let i = 0; i < numOperands; i++) { - const num = Math.floor(Math.random() * 100) + 1; - if (i === 0) { - expression = num.toString(); - correctAnswer = num; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${num}`; - correctAnswer = this.applyOperation(correctAnswer, num, op); - } - } - - correctAnswer = this.calculateWithPriority(expression); - if (numOperands >= 3 && Math.random() < 0.3) { - const result = this.addParentheses(expression, correctAnswer); - if (result) { - expression = result.expression; - correctAnswer = result.answer; - } - } - - const options = this.generateOptions(correctAnswer, 4); - return { - id: `primary-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, - stem: `计算:${expression} = ?`, - options: options, - answer: options.find(opt => opt.isCorrect).key - }; - } - - // 生成初中题目(1-5个操作数,包含平方和开方) - generateMiddleSchoolQuestion() { - const numOperands = Math.floor(Math.random() * 5) + 1; - const operations = ['+', '-', '×', '÷']; - const specialPosition = Math.floor(Math.random() * numOperands); - const isSquare = Math.random() < 0.5; - let expression = ''; - let correctAnswer = 0; - - for (let i = 0; i < numOperands; i++) { - const {term, value} = i === specialPosition ? - this.generateSpecialTerm(isSquare) : this.generateRandomTerm(); - if (i === 0) { - expression = term; - correctAnswer = value; - } else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${term}`; - correctAnswer = this.applyOperation(correctAnswer, value, op); - } - } - - correctAnswer = this.calculateWithPriority(expression); - if (numOperands >= 3 && Math.random() < 0.4) { - const result = this.addParentheses(expression, correctAnswer); - if (result) {expression = result.expression; correctAnswer = result.answer;} - } - - const options = this.generateOptions(correctAnswer, 4); - return { - id: `middle-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, - stem: `计算:${expression} = ?`, - options: options, - answer: options.find(opt => opt.isCorrect).key - }; - } - - // 生成特殊项(平方或开方) - generateSpecialTerm(isSquare) { - if (isSquare) { - const base = Math.floor(Math.random() * 15) + 1; - return { term: `${base}²`, value: base * base }; - } else { - const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; - const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; - return { term: `√${num}`, value: Math.sqrt(num) }; - } - } - - // 生成随机项(数字、平方或开方) - generateRandomTerm() { - const termType = Math.random(); - if (termType < 0.3) { - const base = Math.floor(Math.random() * 15) + 1; - return { term: `${base}²`, value: base * base }; - } else if (termType < 0.6) { - const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; - const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; - return { term: `√${num}`, value: Math.sqrt(num) }; - } else { - const num = Math.floor(Math.random() * 100) + 1; - return { term: num.toString(), value: num }; - } - } - - // 生成高中题目(1-5个操作数,包含三角函数) - generateHighSchoolQuestion() { - const numOperands = Math.floor(Math.random() * 5) + 1; - const operations = ['+', '-', '×', '÷']; - const specialPosition = Math.floor(Math.random() * numOperands); - let expression = '', correctAnswer = 0; - - for (let i = 0; i < numOperands; i++) { - const {term, value} = i === specialPosition ? - this.generateTrigTerm() : (Math.random() < 0.4 ? this.generateTrigTerm() : this.generateNumberTerm()); - - if (i === 0) { expression = term; correctAnswer = value; } - else { - const op = operations[Math.floor(Math.random() * operations.length)]; - expression += ` ${op} ${term}`; - correctAnswer = this.applyOperation(correctAnswer, value, op); - } - } - - correctAnswer = this.calculateWithPriority(expression); - if (numOperands >= 3 && Math.random() < 0.4) { - const result = this.addParentheses(expression, correctAnswer); - if (result) { expression = result.expression; correctAnswer = result.answer; } - } - - const options = this.generateOptions(correctAnswer, 4); - return { - id: `high-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, - stem: `计算:${expression} = ?`, - options: options, - answer: options.find(opt => opt.isCorrect).key - }; - } - - // 生成三角函数项 - generateTrigTerm() { - const functions = ['sin', 'cos', 'tan']; - const func = functions[Math.floor(Math.random() * functions.length)]; - const angle = Math.floor(Math.random() * 100) + 1; - const value = Math.round(Math[func](angle * Math.PI / 180) * 100) / 100; - return { term: `${func}${angle}`, value }; - } - - // 生成数字项 - generateNumberTerm() { - const num = Math.floor(Math.random() * 100) + 1; - return { term: num.toString(), value: num }; - } - - // 生成选择题选项 - generateOptions(correctAnswer, count) { - const options = []; - const keys = ['A', 'B', 'C', 'D']; - const correctIndex = Math.floor(Math.random() * count); - const isInteger = typeof correctAnswer === 'number' && Number.isInteger(correctAnswer); - - for (let i = 0; i < count; i++) { - const value = i === correctIndex ? correctAnswer : this.generateWrongOption(correctAnswer, isInteger, i); - options.push({key: keys[i], text: value.toString(), isCorrect: i === correctIndex}); - } - return options; - } - - // 生成错误选项 - generateWrongOption(correctAnswer, isInteger, index) { - if (typeof correctAnswer !== 'number') { - return Math.random().toString(36).substring(2, 6); - } - - let attempts = 0; - let value; - do { - const deviation = (Math.random() - 0.5) * 4; - value = isInteger ? - correctAnswer + Math.floor(Math.random() * 10) - 5 : - Math.round((correctAnswer + deviation) * 100) / 100; - - if (++attempts > 10) { - value = correctAnswer + (index + 1); - if (isInteger) { - value = Math.round(value); - } - break; - } - } while (value === correctAnswer); - return value; - } - - // 获取数字的所有因数 - getDivisors(n) { - const divisors = []; - for (let i = 2; i <= Math.min(n, 100); i++) { // 除数也限制在1-100范围内 - if (n % i === 0) divisors.push(i); - } - return divisors; - } - - // 小学题目专用的括号添加函数,确保括号内不会产生负数 - addParenthesesForPrimary(expression, originalAnswer) { - const parts = expression.split(' '); - - // 如果表达式太短,不需要加括号 - if (parts.length < 5) return null; - - // 找到所有可以加括号的位置(运算符位置) - const operatorPositions = []; - for (let i = 1; i < parts.length - 1; i += 2) { - // 只考虑加法和乘法,避免减法导致负数 - if (parts[i] === '+' || parts[i] === '×') { - operatorPositions.push(i); - } - } - - if (operatorPositions.length === 0) return null; - - // 随机选择一个运算符位置 - const operatorIndex = operatorPositions[Math.floor(Math.random() * operatorPositions.length)]; - - // 确定括号的范围(从运算符前一个操作数到运算符后一个操作数) - const startPos = operatorIndex - 1; - const endPos = operatorIndex + 1; - - // 构建带括号的表达式 - let result = ''; - for (let i = 0; i < parts.length; i++) { - if (i === startPos) { - result += '('; - } - - result += parts[i]; - - if (i === endPos) { - result += ')'; - } - - if (i < parts.length - 1) { - result += ' '; - } - } - - // 计算带括号的答案 - let newAnswer = this.calculateWithPriority(result); - - // 确保答案是非负整数 - if (newAnswer < 0 || !Number.isInteger(newAnswer)) { - return null; - } - - return { - answer: newAnswer, - expression: result, - }; - } - - // 通用的括号添加函数 - // 为表达式添加括号 - addParentheses(expression, originalAnswer) { - const parts = expression.split(' '); - - // 如果表达式太短,不需要加括号 - if (parts.length < 5) return null; - - // 找到所有可以加括号的位置(运算符位置) - const operatorPositions = []; - for (let i = 1; i < parts.length - 1; i += 2) { - operatorPositions.push(i); - } - - if (operatorPositions.length === 0) return null; - - // 随机选择一个运算符位置 - const operatorIndex = operatorPositions[Math.floor(Math.random() * operatorPositions.length)]; - - // 确定括号的范围(从运算符前一个操作数到运算符后一个操作数) - const startPos = operatorIndex - 1; - const endPos = operatorIndex + 1; - - // 构建带括号的表达式 - let result = ''; - for (let i = 0; i < parts.length; i++) { - if (i === startPos) { - result += '('; - } - - result += parts[i]; - - if (i === endPos) { - result += ')'; - } - - if (i < parts.length - 1) { - result += ' '; - } - } - - // 计算带括号的答案 - let newAnswer = this.calculateWithPriority(result); - - return { - answer: newAnswer, - expression: result, - }; - } - - // 执行四则运算操作 - applyOperation(current, value, op) { - switch (op) { - case '+': return current + value; - case '-': return current - value; - case '×': return current * value; - case '÷': return value !== 0 ? current / value : current; - default: return current; - } - } - - // 使用正确优先级计算表达式的答案 - calculateWithPriority(expression) { - // 替换运算符为JavaScript可识别的 - let jsExpression = expression - .replace(/×/g, '*') - .replace(/÷/g, '/') - .replace(/²/g, '**2') - .replace(/√(\d+)/g, 'Math.sqrt($1)') - .replace(/sin(\d+)/g, 'Math.sin($1 * Math.PI / 180)') - .replace(/cos(\d+)/g, 'Math.cos($1 * Math.PI / 180)') - .replace(/tan(\d+)/g, 'Math.tan($1 * Math.PI / 180)'); - - try { - // 使用eval计算表达式 - let result = eval(jsExpression); - - // 处理特殊情况 - if (typeof result === 'number') { - // 如果是整数,返回整数 - if (Number.isInteger(result)) { - return result; - } - // 否则保留两位小数 - return Math.round(result * 100) / 100; - } - - return result; - } catch (error) { - console.error('计算表达式时出错:', expression, error); - // 如果计算失败,返回原始表达式的估算值 - return this.estimateExpression(expression); - } - } - - // 估算表达式的值(当eval失败时使用) - estimateExpression(expression) { - // 简单的估算逻辑,按顺序计算 - const parts = expression.split(' '); - let result = parseFloat(parts[0]); - - for (let i = 1; i < parts.length; i += 2) { - const operator = parts[i]; - const num = parseFloat(parts[i + 1]); - - switch (operator) { - case '+': - result += num; - break; - case '-': - result -= num; - break; - case '×': - result *= num; - break; - case '÷': - if (num !== 0) result /= num; - break; - } - } - - return Math.round(result * 100) / 100; - } -} - -module.exports = MathQuestionGenerator; \ No newline at end of file diff --git a/src/backend/utils/user-manager.js b/src/backend/utils/user-manager.js deleted file mode 100644 index 3b4929f..0000000 --- a/src/backend/utils/user-manager.js +++ /dev/null @@ -1,183 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -const USERS_FILE = path.join(__dirname, '../data/users.json'); - -// 用户管理类,处理用户注册、登录等操作 -class UserManager { - constructor() { - this.ensureDataFile(); - } - - // 确保数据文件存在 - ensureDataFile() { - const dataDir = path.dirname(USERS_FILE); - if (!fs.existsSync(dataDir)) { - fs.mkdirSync(dataDir, {recursive: true}); - } - if (!fs.existsSync(USERS_FILE)) { - fs.writeFileSync(USERS_FILE, JSON.stringify([])); - } - } - - // 读取所有用户数据 - readUsers() { - try { - return JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); - } catch (error) { - console.error('读取用户数据失败:', error); - return []; - } - } - - // 保存用户数据到文件 - writeUsers(users) { - try { - fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); - return true; - } catch (error) { - console.error('写入用户数据失败:', error); - return false; - } - } - - // 对密码进行SHA256哈希加密 - hashPassword(password) { - return crypto.createHash('sha256').update(password).digest('hex'); - } - - // 验证密码格式(6-10位) - validatePassword(password) { - if (!password || password.length < 6 || password.length > 10) { - return false; - } - const hasUpper = /[A-Z]/.test(password); - const hasLower = /[a-z]/.test(password); - const hasDigit = /\d/.test(password); - return hasUpper && hasLower && hasDigit; - } - - findUserByEmail(email) { - const users = this.readUsers(); - return users.find((user) => user.email === email); - } - - findUserByUsername(username) { - const users = this.readUsers(); - return users.find((user) => user.username === username); - } - - createUser(email, username) { - const users = this.readUsers(); - - // 严格检查邮箱是否已存在 - if (users.some((user) => user.email === email)) { - throw new Error('邮箱已被注册,每个邮箱只能注册一个账户'); - } - - // 检查用户名是否已存在 - if (users.some((user) => user.username === username)) { - throw new Error('用户名已存在'); - } - - const newUser = { - email, - registeredAt: new Date().toISOString(), - username, - }; - - users.push(newUser); - if (this.writeUsers(users)) { - console.log(`✅ 新用户注册: ${username} (${email})`); - return newUser; - } else { - throw new Error('用户创建失败'); - } - } - - setPassword(email, password) { - const users = this.readUsers(); - const userIndex = users.findIndex((user) => user.email === email); - - if (userIndex === -1) { - throw new Error('用户不存在'); - } - - if (!this.validatePassword(password)) { - throw new Error('密码需6-10位且包含大小写字母和数字'); - } - - users[userIndex].password = this.hashPassword(password); - users[userIndex].updatedAt = new Date().toISOString(); - - if (this.writeUsers(users)) { - console.log(`✅ 用户设置密码: ${email}`); - return true; - } else { - throw new Error('密码设置失败'); - } - } - - verifyPassword(email, password) { - const user = this.findUserByEmail(email); - if (!user || !user.password) { - return false; - } - return user.password === this.hashPassword(password); - } - - changePassword(email, oldPassword, newPassword) { - if (!this.verifyPassword(email, oldPassword)) { - throw new Error('原密码不正确'); - } - - return this.setPassword(email, newPassword); - } - - changeUsername(email, newUsername) { - const users = this.readUsers(); - const userIndex = users.findIndex((user) => user.email === email); - - if (userIndex === -1) { - throw new Error('用户不存在'); - } - - // 检查新用户名是否已存在(排除当前用户) - if (users.some((user) => user.username === newUsername && user.email !== email)) { - throw new Error('用户名已存在'); - } - - const oldUsername = users[userIndex].username; - users[userIndex].username = newUsername; - users[userIndex].updatedAt = new Date().toISOString(); - - if (this.writeUsers(users)) { - console.log(`✅ 用户修改用户名: ${oldUsername} -> ${newUsername}`); - return true; - } else { - throw new Error('用户名修改失败'); - } - } - - deleteUser(email) { - const users = this.readUsers(); - const userIndex = users.findIndex((user) => user.email === email); - - if (userIndex === -1) { - throw new Error('用户不存在'); - } - - const deletedUser = users[userIndex]; - users.splice(userIndex, 1); - - if (this.writeUsers(users)) { - console.log(`✅ 用户账号已删除: ${deletedUser.username} (${email})`); - return true; - } else { - throw new Error('账号删除失败'); - } - } -} - -module.exports = UserManager; \ No newline at end of file -- 2.34.1 From ad35d6ce799bbfa897c28c405caf1aeedba17cb9 Mon Sep 17 00:00:00 2001 From: wwb <3454934335@qq.com> Date: Sun, 12 Oct 2025 14:56:14 +0800 Subject: [PATCH 13/13] =?UTF-8?q?=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/data/users.json | 16 + src/backend/email-config.js | 31 + src/backend/package-lock.json | 1243 ++++++++++++++++++++++ src/backend/package.json | 35 + src/backend/server.js | 310 ++++++ src/backend/utils/multi-email-service.js | 104 ++ src/backend/utils/question-generator.js | 441 ++++++++ src/backend/utils/user-manager.js | 183 ++++ 8 files changed, 2363 insertions(+) create mode 100644 src/backend/data/users.json create mode 100644 src/backend/email-config.js create mode 100644 src/backend/package-lock.json create mode 100644 src/backend/package.json create mode 100644 src/backend/server.js create mode 100644 src/backend/utils/multi-email-service.js create mode 100644 src/backend/utils/question-generator.js create mode 100644 src/backend/utils/user-manager.js diff --git a/src/backend/data/users.json b/src/backend/data/users.json new file mode 100644 index 0000000..ea595de --- /dev/null +++ b/src/backend/data/users.json @@ -0,0 +1,16 @@ +[ + { + "email": "3454934335@qq.com", + "username": "wwb", + "registeredAt": "2025-10-11T05:26:22.038Z", + "password": "cc43f52af644042612d98dea06cf5b64d8e2f8af1de8065fe83b208f4a6d8875", + "updatedAt": "2025-10-11T05:26:22.051Z" + }, + { + "email": "3063485007@qq.com", + "username": "wqs", + "registeredAt": "2025-10-11T19:08:09.303Z", + "password": "be0c664d19564b568c4dd76d00a26a08ee89214877e52225eee5ffefab36c15f", + "updatedAt": "2025-10-11T19:08:26.339Z" + } +] \ No newline at end of file diff --git a/src/backend/email-config.js b/src/backend/email-config.js new file mode 100644 index 0000000..5a4c8e7 --- /dev/null +++ b/src/backend/email-config.js @@ -0,0 +1,31 @@ +// ============================================= +// 数学学习软件 - 邮箱配置文件 +// ============================================= + + +// ==================== 选择1: QQ邮箱服务 ==================== +module.exports = { + service: 'qq', + auth: { + user: '3454934335@qq.com', // 替换为你的QQ邮箱 + pass: 'vxhsswmmqiyvchhh' // 替换为QQ邮箱授权码(16位) + } +}; + +// ==================== 选择2: 163邮箱服务 ==================== +module.exports = { + service: '163', + auth: { + user: '18950579895@163.com',// 替换为你的163邮箱 + pass: 'UCgJrE7yzzd4Uz3g' // 替换为163邮箱授权码 + } +}; + + +// ==================== 选择3: 测试模式 ==================== +// 不发送实际邮件,验证码在控制台显示 +// 适用于开发和测试环境 +/* +module.exports = {}; +*/ + diff --git a/src/backend/package-lock.json b/src/backend/package-lock.json new file mode 100644 index 0000000..52de788 --- /dev/null +++ b/src/backend/package-lock.json @@ -0,0 +1,1243 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "math-learning-backend", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmmirror.com/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/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/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/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/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/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/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/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/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/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/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/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemailer": { + "version": "6.10.1", + "resolved": "https://registry.npmmirror.com/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmmirror.com/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.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/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/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/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/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/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/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/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/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/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/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/src/backend/package.json b/src/backend/package.json new file mode 100644 index 0000000..2d33a16 --- /dev/null +++ b/src/backend/package.json @@ -0,0 +1,35 @@ +{ + "name": "math-learning-backend", + "version": "1.0.0", + "description": "数学学习软件后端API - 支持QQ邮箱和163邮箱验证", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js", + "test": "echo \"后端服务测试模式\" && node server.js" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "body-parser": "^1.20.2", + "nodemailer": "^6.9.7" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": [ + "education", + "math", + "learning", + "email", + "qq", + "163" + ], + "author": "数学学习软件团队", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/your-repo/math-learning-software" + } +} + diff --git a/src/backend/server.js b/src/backend/server.js new file mode 100644 index 0000000..5896e8b --- /dev/null +++ b/src/backend/server.js @@ -0,0 +1,310 @@ +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +// 导入工具类 +const UserManager = require('./utils/user-manager'); +const MultiEmailService = require('./utils/multi-email-service'); +const MathQuestionGenerator = require('./utils/question-generator'); + +// 创建Express应用实例 +const app = express(); +const PORT = 8080; + +// 配置中间件 +app.use(cors()); +app.use(bodyParser.json()); +app.use(express.static(path.join(__dirname, '../project4'))); + +// 实例化管理器 +const userManager = new UserManager(); +const emailService = new MultiEmailService(); + +// 存储验证码(内存存储,重启后失效) +const verificationCodes = new Map(); + +// 生成6位数字验证码 +function generateVerificationCode() { + return Math.floor(100000 + Math.random() * 900000).toString(); +} + +// 验证邮箱格式是否正确 +function validateEmail(email) { + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); +} + +// API路由 + +// 检查服务器健康状态 +app.get('/api/health', (req, res) => { + res.json({ + ok: true, + message: '服务正常运行', + emailService: emailService.transporter ? '已配置' : '测试模式' + }); +}); + +// 发送邮箱验证码 +app.post('/api/send-code', async (req, res) => { + try { + const {email, username} = req.body; + + console.log('收到发送验证码请求:', {email, username}); + + if (!email || !username) { + return res.json({ok: false, message: '邮箱和用户名不能为空'}); + } + + // 验证邮箱格式 + if (!validateEmail(email)) { + return res.json({ok: false, message: '邮箱格式不正确'}); + } + + // 限制邮箱域名,只允许QQ邮箱和163邮箱 + if (!email.includes('@qq.com') && !email.includes('@163.com')) { + return res.json({ok: false, message: '只支持QQ邮箱和163邮箱'}); + } + + // 检查用户名是否已存在 + if (userManager.findUserByUsername(username)) { + return res.json({ok: false, message: '用户名已存在'}); + } + + // 检查邮箱是否已注册 + if (userManager.findUserByEmail(email)) { + return res.json({ok: false, message: '邮箱已被注册'}); + } + + const code = generateVerificationCode(); + verificationCodes.set(email, { + code, + username, + timestamp: Date.now(), + }); + + console.log(`为邮箱 ${email} 生成验证码: ${code}`); + + // 发送邮件 + const emailResult = await emailService.sendVerificationCode(email, code, username); + + if (emailResult.success) { + res.json({ok: true, message: emailResult.message}); + } else { + // 如果邮件发送失败,但在开发模式下提供了验证码,仍然算成功 + if (emailResult.debug) { + res.json({ok: true, message: emailResult.message + ' ' + emailResult.debug}); + } else { + res.json({ok: false, message: emailResult.message}); + } + } + } catch (error) { + console.error('发送验证码错误:', error); + res.json({ok: false, message: '服务器内部错误'}); + } +}); + +// 验证码验证 +app.post('/api/verify-code', (req, res) => { + try { + const {email, username, code} = req.body; + + console.log('收到验证码验证请求:', {email, username, code}); + + if (!email || !username || !code) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + const storedData = verificationCodes.get(email); + + if (!storedData) { + return res.json({ok: false, message: '请先获取验证码'}); + } + + // 验证码10分钟过期 + if (Date.now() - storedData.timestamp > 10 * 60 * 1000) { + verificationCodes.delete(email); + return res.json({ok: false, message: '验证码已过期,请重新获取'}); + } + + if (storedData.code !== code) { + return res.json({ok: false, message: '验证码不正确'}); + } + + if (storedData.username !== username) { + return res.json({ok: false, message: '用户名与验证时不一致'}); + } + + // 清除验证码 + verificationCodes.delete(email); + + res.json({ok: true, message: '验证成功,请设置密码'}); + } catch (error) { + console.error('验证码验证错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 注册(创建用户 + 设置密码) +app.post('/api/register', (req, res) => { + try { + const {email, username, password} = req.body; + + console.log('收到注册请求:', {email, username}); + + if (!email || !username || !password) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + // 创建用户并设置密码 + userManager.createUser(email, username); + userManager.setPassword(email, password); + + res.json({ok: true, message: '注册成功'}); + } catch (error) { + console.error('注册错误:', error); + res.json({ok: false, message: error.message}); + } +}); + + +// 登录 +app.post('/api/login', (req, res) => { + try { + const {account, password} = req.body; + + if (!account || !password) { + return res.json({ok: false, message: '请填写账号和密码'}); + } + + // 通过邮箱或用户名查找用户 + let user = userManager.findUserByEmail(account); + if (!user) { + user = userManager.findUserByUsername(account); + } + + if (!user || !user.password) { + return res.json({ok: false, message: '用户不存在'}); + } + + if (!userManager.verifyPassword(user.email, password)) { + return res.json({ok: false, message: '密码不正确'}); + } + + res.json({ + ok: true, + message: '登录成功', + data: { + email: user.email, + username: user.username, + }, + }); + } catch (error) { + console.error('登录错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 修改密码 +app.post('/api/change-password', (req, res) => { + try { + const {email, oldPassword, newPassword} = req.body; + + if (!email || !oldPassword || !newPassword) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + userManager.changePassword(email, oldPassword, newPassword); + res.json({ok: true, message: '密码修改成功'}); + } catch (error) { + console.error('修改密码错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 修改用户名 +app.post('/api/change-username', (req, res) => { + try { + const {email, username} = req.body; + + if (!email || !username) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + userManager.changeUsername(email, username); + res.json({ok: true, message: '用户名修改成功'}); + } catch (error) { + console.error('修改用户名错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 删除账号 +app.post('/api/delete-account', (req, res) => { + try { + const {email, password} = req.body; + + if (!email || !password) { + return res.json({ok: false, message: '请填写完整信息'}); + } + + // 验证密码 + if (!userManager.verifyPassword(email, password)) { + return res.json({ok: false, message: '密码不正确'}); + } + + userManager.deleteUser(email); + res.json({ok: true, message: '账号删除成功'}); + } catch (error) { + console.error('删除账号错误:', error); + res.json({ok: false, message: error.message}); + } +}); + +// 获取题目 +app.get('/api/questions', (req, res) => { + try { + const {grade, count} = req.query; + + if (!grade || !count) { + return res.json({ok: false, message: '请选择年级和题目数量'}); + } + + const countNum = parseInt(count); + if (isNaN(countNum) || countNum < 10 || countNum > 30) { + return res.json({ok: false, message: '题目数量需在10-30之间'}); + } + + const generator = new MathQuestionGenerator(); + const questions = generator.generateQuestions(grade, countNum); + res.json({ + ok: true, + data: questions, + message: '题目生成成功', + }); + } catch (error) { + console.error('生成题目失败:', error); + res.json({ok: false, message: '生成题目失败'}); + } +}); + +// 提供前端页面 +app.get('/', (req, res) => { + res.sendFile(path.join(__dirname, '../project4/index.html')); +}); + +// 启动服务器 +app.listen(PORT, () => { + console.log(`========================================`); + console.log(`🎓 数学学习软件后端服务已启动`); + console.log(`📍 服务地址: http://localhost:${PORT}`); + console.log(`🌐 前端页面: http://localhost:${PORT}/`); + console.log(`🔧 API文档: http://localhost:${PORT}/api/health`); + console.log(`📧 邮箱服务: ${emailService.transporter ? '已配置' : '测试模式'}`); + console.log(`========================================`); +}); + +module.exports = app; + diff --git a/src/backend/utils/multi-email-service.js b/src/backend/utils/multi-email-service.js new file mode 100644 index 0000000..bba61fb --- /dev/null +++ b/src/backend/utils/multi-email-service.js @@ -0,0 +1,104 @@ +const nodemailer = require('nodemailer'); + +// 多邮箱服务类,支持多种邮箱服务商 +class MultiEmailService { + constructor() { + this.transporters = new Map(); + this.initTransporters(); + } + + // 初始化各种邮箱服务商的传输器 + initTransporters() { + // QQ邮箱配置 + try { + // 修改 + const qqTransporter = nodemailer.createTransport({ + auth: { + pass: 'vxhsswmmqiyvchhh', + user: '3454934335@qq.com', + }, + service: 'qq', + }); + this.transporters.set('qq', qqTransporter); + console.log('✅ QQ邮箱服务已初始化'); + } catch (error) { + console.error('❌ QQ邮箱初始化失败:', error); + } + + // 163邮箱配置 + try { + // 修改 + const mail163Transporter = nodemailer.createTransport({ + auth: { + pass: 'UCgJrE7yzzd4Uz3g', + user: '18950579895@163.com', + }, + service: '163', + }); + this.transporters.set('163', mail163Transporter); + console.log('✅ 163邮箱服务已初始化'); + } catch (error) { + console.error('❌ 163邮箱初始化失败:', error); + } + } + + // 发送验证码邮件 + async sendVerificationCode(email, code, username) { + // 根据邮箱域名选择发件箱 + let transporter = email.includes('@qq.com') ? this.transporters.get('qq') : + email.includes('@163.com') ? this.transporters.get('163') : + this.transporters.get('qq'); + + if (!transporter) { + console.log(`🔧 测试模式 - 邮箱: ${email}, 验证码: ${code}`); + return {success: true, message: '验证码已生成(测试模式)', debug: `验证码: ${code}`}; + } + + const mailOptions = { + from: transporter.options.auth.user, + to: email, + subject: '数学学习软件 - 注册验证码', + html: this.generateEmailTemplate(code, username) + }; + + try { + await transporter.sendMail(mailOptions); + console.log(`✅ 验证码邮件已发送到: ${email}`); + return {success: true, message: '验证码已发送到您的邮箱'}; + } catch (error) { + console.error('❌ 邮件发送失败:', error); + return {success: false, message: '邮件发送失败,请稍后重试', debug: `验证码: ${code} (请使用此验证码完成注册)`}; + } + } + + // 生成邮件HTML模板 + generateEmailTemplate(code, username) { + return ` + + + + + + + +
+

🎓 数学学习软件

+

亲爱的 ${username},您好!

+

您正在注册数学学习软件,验证码为:

+
${code}
+

验证码有效期为10分钟,请尽快完成注册。

+

如果这不是您本人的操作,请忽略此邮件。

+
+ + + `; + } +} + +module.exports = MultiEmailService; + diff --git a/src/backend/utils/question-generator.js b/src/backend/utils/question-generator.js new file mode 100644 index 0000000..2457bc6 --- /dev/null +++ b/src/backend/utils/question-generator.js @@ -0,0 +1,441 @@ +const fs = require('fs'); +const path = require('path'); + +class MathQuestionGenerator { + constructor() { + this.currentSessionQuestions = new Set(); + } + + // 生成指定年级和数量的题目 + generateQuestions(grade, count) { + console.log(`正在生成${grade} ${count}道题目...`); + const questions = []; + this.currentSessionQuestions.clear(); + + const maxAttempts = count * 20; + let attempts = 0; + + while (questions.length < count && attempts < maxAttempts) { + attempts++; + const question = this.generateQuestion(grade); + if (!question) { + continue; + } + + const questionKey = `${grade}-${question.stem}`; + + if (!this.currentSessionQuestions.has(questionKey)) { + questions.push(question); + this.currentSessionQuestions.add(questionKey); + console.log(`✅ 生成第${questions.length}题: ${question.stem}`); + } + } + + if (questions.length < count) { + console.warn(`⚠️ 只生成了${questions.length}道题目,未能达到要求的${count}道`); + } + + return questions; + } + + // 根据年级生成单个题目 + generateQuestion(grade) { + try { + switch (grade) { + case '小学': + return this.generatePrimaryQuestion(); + case '初中': + return this.generateMiddleSchoolQuestion(); + case '高中': + return this.generateHighSchoolQuestion(); + default: + return this.generatePrimaryQuestion(); + } + } catch (error) { + console.error(`生成${grade}题目时出错:`, error); + return null; + } + } + + // 生成小学题目(2-5个操作数,基础四则运算) + generatePrimaryQuestion() { + const numOperands = Math.floor(Math.random() * 4) + 2; + const operations = ['+', '-', '×', '÷']; + let expression = ''; + let correctAnswer = 0; + + for (let i = 0; i < numOperands; i++) { + const num = Math.floor(Math.random() * 100) + 1; + if (i === 0) { + expression = num.toString(); + correctAnswer = num; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${num}`; + correctAnswer = this.applyOperation(correctAnswer, num, op); + } + } + + correctAnswer = this.calculateWithPriority(expression); + if (numOperands >= 3 && Math.random() < 0.3) { + const result = this.addParentheses(expression, correctAnswer); + if (result) { + expression = result.expression; + correctAnswer = result.answer; + } + } + + const options = this.generateOptions(correctAnswer, 4); + return { + id: `primary-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + // 生成初中题目(1-5个操作数,包含平方和开方) + generateMiddleSchoolQuestion() { + const numOperands = Math.floor(Math.random() * 5) + 1; + const operations = ['+', '-', '×', '÷']; + const specialPosition = Math.floor(Math.random() * numOperands); + const isSquare = Math.random() < 0.5; + let expression = ''; + let correctAnswer = 0; + + for (let i = 0; i < numOperands; i++) { + const {term, value} = i === specialPosition ? + this.generateSpecialTerm(isSquare) : this.generateRandomTerm(); + if (i === 0) { + expression = term; + correctAnswer = value; + } else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + correctAnswer = this.applyOperation(correctAnswer, value, op); + } + } + + correctAnswer = this.calculateWithPriority(expression); + if (numOperands >= 3 && Math.random() < 0.4) { + const result = this.addParentheses(expression, correctAnswer); + if (result) {expression = result.expression; correctAnswer = result.answer;} + } + + const options = this.generateOptions(correctAnswer, 4); + return { + id: `middle-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + // 生成特殊项(平方或开方) + generateSpecialTerm(isSquare) { + if (isSquare) { + const base = Math.floor(Math.random() * 15) + 1; + return { term: `${base}²`, value: base * base }; + } else { + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + return { term: `√${num}`, value: Math.sqrt(num) }; + } + } + + // 生成随机项(数字、平方或开方) + generateRandomTerm() { + const termType = Math.random(); + if (termType < 0.3) { + const base = Math.floor(Math.random() * 15) + 1; + return { term: `${base}²`, value: base * base }; + } else if (termType < 0.6) { + const perfectSquares = [4, 9, 16, 25, 36, 49, 64, 81, 100]; + const num = perfectSquares[Math.floor(Math.random() * perfectSquares.length)]; + return { term: `√${num}`, value: Math.sqrt(num) }; + } else { + const num = Math.floor(Math.random() * 100) + 1; + return { term: num.toString(), value: num }; + } + } + + // 生成高中题目(1-5个操作数,包含三角函数) + generateHighSchoolQuestion() { + const numOperands = Math.floor(Math.random() * 5) + 1; + const operations = ['+', '-', '×', '÷']; + const specialPosition = Math.floor(Math.random() * numOperands); + let expression = '', correctAnswer = 0; + + for (let i = 0; i < numOperands; i++) { + const {term, value} = i === specialPosition ? + this.generateTrigTerm() : (Math.random() < 0.4 ? this.generateTrigTerm() : this.generateNumberTerm()); + + if (i === 0) { expression = term; correctAnswer = value; } + else { + const op = operations[Math.floor(Math.random() * operations.length)]; + expression += ` ${op} ${term}`; + correctAnswer = this.applyOperation(correctAnswer, value, op); + } + } + + correctAnswer = this.calculateWithPriority(expression); + if (numOperands >= 3 && Math.random() < 0.4) { + const result = this.addParentheses(expression, correctAnswer); + if (result) { expression = result.expression; correctAnswer = result.answer; } + } + + const options = this.generateOptions(correctAnswer, 4); + return { + id: `high-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, + stem: `计算:${expression} = ?`, + options: options, + answer: options.find(opt => opt.isCorrect).key + }; + } + + // 生成三角函数项 + generateTrigTerm() { + const functions = ['sin', 'cos', 'tan']; + const func = functions[Math.floor(Math.random() * functions.length)]; + const angle = Math.floor(Math.random() * 100) + 1; + const value = Math.round(Math[func](angle * Math.PI / 180) * 100) / 100; + return { term: `${func}${angle}`, value }; + } + + // 生成数字项 + generateNumberTerm() { + const num = Math.floor(Math.random() * 100) + 1; + return { term: num.toString(), value: num }; + } + + // 生成选择题选项 + generateOptions(correctAnswer, count) { + const options = []; + const keys = ['A', 'B', 'C', 'D']; + const correctIndex = Math.floor(Math.random() * count); + const isInteger = typeof correctAnswer === 'number' && Number.isInteger(correctAnswer); + + for (let i = 0; i < count; i++) { + const value = i === correctIndex ? correctAnswer : this.generateWrongOption(correctAnswer, isInteger, i); + options.push({key: keys[i], text: value.toString(), isCorrect: i === correctIndex}); + } + return options; + } + + // 生成错误选项 + generateWrongOption(correctAnswer, isInteger, index) { + if (typeof correctAnswer !== 'number') { + return Math.random().toString(36).substring(2, 6); + } + + let attempts = 0; + let value; + do { + const deviation = (Math.random() - 0.5) * 4; + value = isInteger ? + correctAnswer + Math.floor(Math.random() * 10) - 5 : + Math.round((correctAnswer + deviation) * 100) / 100; + + if (++attempts > 10) { + value = correctAnswer + (index + 1); + if (isInteger) { + value = Math.round(value); + } + break; + } + } while (value === correctAnswer); + return value; + } + + // 获取数字的所有因数 + getDivisors(n) { + const divisors = []; + for (let i = 2; i <= Math.min(n, 100); i++) { // 除数也限制在1-100范围内 + if (n % i === 0) divisors.push(i); + } + return divisors; + } + + // 小学题目专用的括号添加函数,确保括号内不会产生负数 + addParenthesesForPrimary(expression, originalAnswer) { + const parts = expression.split(' '); + + // 如果表达式太短,不需要加括号 + if (parts.length < 5) return null; + + // 找到所有可以加括号的位置(运算符位置) + const operatorPositions = []; + for (let i = 1; i < parts.length - 1; i += 2) { + // 只考虑加法和乘法,避免减法导致负数 + if (parts[i] === '+' || parts[i] === '×') { + operatorPositions.push(i); + } + } + + if (operatorPositions.length === 0) return null; + + // 随机选择一个运算符位置 + const operatorIndex = operatorPositions[Math.floor(Math.random() * operatorPositions.length)]; + + // 确定括号的范围(从运算符前一个操作数到运算符后一个操作数) + const startPos = operatorIndex - 1; + const endPos = operatorIndex + 1; + + // 构建带括号的表达式 + let result = ''; + for (let i = 0; i < parts.length; i++) { + if (i === startPos) { + result += '('; + } + + result += parts[i]; + + if (i === endPos) { + result += ')'; + } + + if (i < parts.length - 1) { + result += ' '; + } + } + + // 计算带括号的答案 + let newAnswer = this.calculateWithPriority(result); + + // 确保答案是非负整数 + if (newAnswer < 0 || !Number.isInteger(newAnswer)) { + return null; + } + + return { + answer: newAnswer, + expression: result, + }; + } + + // 通用的括号添加函数 + // 为表达式添加括号 + addParentheses(expression, originalAnswer) { + const parts = expression.split(' '); + + // 如果表达式太短,不需要加括号 + if (parts.length < 5) return null; + + // 找到所有可以加括号的位置(运算符位置) + const operatorPositions = []; + for (let i = 1; i < parts.length - 1; i += 2) { + operatorPositions.push(i); + } + + if (operatorPositions.length === 0) return null; + + // 随机选择一个运算符位置 + const operatorIndex = operatorPositions[Math.floor(Math.random() * operatorPositions.length)]; + + // 确定括号的范围(从运算符前一个操作数到运算符后一个操作数) + const startPos = operatorIndex - 1; + const endPos = operatorIndex + 1; + + // 构建带括号的表达式 + let result = ''; + for (let i = 0; i < parts.length; i++) { + if (i === startPos) { + result += '('; + } + + result += parts[i]; + + if (i === endPos) { + result += ')'; + } + + if (i < parts.length - 1) { + result += ' '; + } + } + + // 计算带括号的答案 + let newAnswer = this.calculateWithPriority(result); + + return { + answer: newAnswer, + expression: result, + }; + } + + // 执行四则运算操作 + applyOperation(current, value, op) { + switch (op) { + case '+': return current + value; + case '-': return current - value; + case '×': return current * value; + case '÷': return value !== 0 ? current / value : current; + default: return current; + } + } + + // 使用正确优先级计算表达式的答案 + calculateWithPriority(expression) { + // 替换运算符为JavaScript可识别的 + let jsExpression = expression + .replace(/×/g, '*') + .replace(/÷/g, '/') + .replace(/²/g, '**2') + .replace(/√(\d+)/g, 'Math.sqrt($1)') + .replace(/sin(\d+)/g, 'Math.sin($1 * Math.PI / 180)') + .replace(/cos(\d+)/g, 'Math.cos($1 * Math.PI / 180)') + .replace(/tan(\d+)/g, 'Math.tan($1 * Math.PI / 180)'); + + try { + // 使用eval计算表达式 + let result = eval(jsExpression); + + // 处理特殊情况 + if (typeof result === 'number') { + // 如果是整数,返回整数 + if (Number.isInteger(result)) { + return result; + } + // 否则保留两位小数 + return Math.round(result * 100) / 100; + } + + return result; + } catch (error) { + console.error('计算表达式时出错:', expression, error); + // 如果计算失败,返回原始表达式的估算值 + return this.estimateExpression(expression); + } + } + + // 估算表达式的值(当eval失败时使用) + estimateExpression(expression) { + // 简单的估算逻辑,按顺序计算 + const parts = expression.split(' '); + let result = parseFloat(parts[0]); + + for (let i = 1; i < parts.length; i += 2) { + const operator = parts[i]; + const num = parseFloat(parts[i + 1]); + + switch (operator) { + case '+': + result += num; + break; + case '-': + result -= num; + break; + case '×': + result *= num; + break; + case '÷': + if (num !== 0) result /= num; + break; + } + } + + return Math.round(result * 100) / 100; + } +} + +module.exports = MathQuestionGenerator; \ No newline at end of file diff --git a/src/backend/utils/user-manager.js b/src/backend/utils/user-manager.js new file mode 100644 index 0000000..3b4929f --- /dev/null +++ b/src/backend/utils/user-manager.js @@ -0,0 +1,183 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const USERS_FILE = path.join(__dirname, '../data/users.json'); + +// 用户管理类,处理用户注册、登录等操作 +class UserManager { + constructor() { + this.ensureDataFile(); + } + + // 确保数据文件存在 + ensureDataFile() { + const dataDir = path.dirname(USERS_FILE); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, {recursive: true}); + } + if (!fs.existsSync(USERS_FILE)) { + fs.writeFileSync(USERS_FILE, JSON.stringify([])); + } + } + + // 读取所有用户数据 + readUsers() { + try { + return JSON.parse(fs.readFileSync(USERS_FILE, 'utf8')); + } catch (error) { + console.error('读取用户数据失败:', error); + return []; + } + } + + // 保存用户数据到文件 + writeUsers(users) { + try { + fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); + return true; + } catch (error) { + console.error('写入用户数据失败:', error); + return false; + } + } + + // 对密码进行SHA256哈希加密 + hashPassword(password) { + return crypto.createHash('sha256').update(password).digest('hex'); + } + + // 验证密码格式(6-10位) + validatePassword(password) { + if (!password || password.length < 6 || password.length > 10) { + return false; + } + const hasUpper = /[A-Z]/.test(password); + const hasLower = /[a-z]/.test(password); + const hasDigit = /\d/.test(password); + return hasUpper && hasLower && hasDigit; + } + + findUserByEmail(email) { + const users = this.readUsers(); + return users.find((user) => user.email === email); + } + + findUserByUsername(username) { + const users = this.readUsers(); + return users.find((user) => user.username === username); + } + + createUser(email, username) { + const users = this.readUsers(); + + // 严格检查邮箱是否已存在 + if (users.some((user) => user.email === email)) { + throw new Error('邮箱已被注册,每个邮箱只能注册一个账户'); + } + + // 检查用户名是否已存在 + if (users.some((user) => user.username === username)) { + throw new Error('用户名已存在'); + } + + const newUser = { + email, + registeredAt: new Date().toISOString(), + username, + }; + + users.push(newUser); + if (this.writeUsers(users)) { + console.log(`✅ 新用户注册: ${username} (${email})`); + return newUser; + } else { + throw new Error('用户创建失败'); + } + } + + setPassword(email, password) { + const users = this.readUsers(); + const userIndex = users.findIndex((user) => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + if (!this.validatePassword(password)) { + throw new Error('密码需6-10位且包含大小写字母和数字'); + } + + users[userIndex].password = this.hashPassword(password); + users[userIndex].updatedAt = new Date().toISOString(); + + if (this.writeUsers(users)) { + console.log(`✅ 用户设置密码: ${email}`); + return true; + } else { + throw new Error('密码设置失败'); + } + } + + verifyPassword(email, password) { + const user = this.findUserByEmail(email); + if (!user || !user.password) { + return false; + } + return user.password === this.hashPassword(password); + } + + changePassword(email, oldPassword, newPassword) { + if (!this.verifyPassword(email, oldPassword)) { + throw new Error('原密码不正确'); + } + + return this.setPassword(email, newPassword); + } + + changeUsername(email, newUsername) { + const users = this.readUsers(); + const userIndex = users.findIndex((user) => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + // 检查新用户名是否已存在(排除当前用户) + if (users.some((user) => user.username === newUsername && user.email !== email)) { + throw new Error('用户名已存在'); + } + + const oldUsername = users[userIndex].username; + users[userIndex].username = newUsername; + users[userIndex].updatedAt = new Date().toISOString(); + + if (this.writeUsers(users)) { + console.log(`✅ 用户修改用户名: ${oldUsername} -> ${newUsername}`); + return true; + } else { + throw new Error('用户名修改失败'); + } + } + + deleteUser(email) { + const users = this.readUsers(); + const userIndex = users.findIndex((user) => user.email === email); + + if (userIndex === -1) { + throw new Error('用户不存在'); + } + + const deletedUser = users[userIndex]; + users.splice(userIndex, 1); + + if (this.writeUsers(users)) { + console.log(`✅ 用户账号已删除: ${deletedUser.username} (${email})`); + return true; + } else { + throw new Error('账号删除失败'); + } + } +} + +module.exports = UserManager; \ No newline at end of file -- 2.34.1