From ccff2bdf98c474a673494e349e2cc75a8aef576e Mon Sep 17 00:00:00 2001 From: 18796357645 <674126018@qq.com> Date: Tue, 24 Jun 2025 11:42:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A8=E9=87=8F=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursorignore | 3 + .gitignore | 2 +- PAGINATION_README.md | 183 +++ README.md | 136 ++- app.js | 118 ++ controllers/adminController.js | 235 ++++ controllers/user/auth.js | 80 ++ db/blog.js | 274 +++++ db/init.js | 112 ++ fix_database.js | 103 ++ middleware/auth.js | 16 + models/Category.js | 9 + models/Comment.js | 11 + models/Link.js | 12 + models/Post.js | 49 + models/User.js | 13 + package-lock.json | 1909 +++++++++++++++++++++++++++++++ package.json | 25 + routes/admin/index.js | 356 ++++++ routes/user/auth.js | 144 +++ test_links.js | 48 + test_pagination.js | 55 + test_pagination_component.js | 71 ++ uploads/app.js | 0 uploads/config.js | 0 utils/pagination.js | 81 ++ views/admin/categories/list.ejs | 143 +++ views/admin/comments/list.ejs | 67 ++ views/admin/index.ejs | 163 +++ views/admin/links/list.ejs | 141 +++ views/admin/posts/list.ejs | 380 ++++++ views/admin/users/list.ejs | 102 ++ views/components/pagination.ejs | 139 +++ views/user/index.ejs | 304 +++++ views/user/info.ejs | 348 ++++++ views/user/login.ejs | 138 +++ views/user/register.ejs | 146 +++ views/user/userInfo.ejs | 122 ++ 初始化数据.bat | 4 + 项目启动.bat | 4 + 40 files changed, 6243 insertions(+), 3 deletions(-) create mode 100644 .cursorignore create mode 100644 PAGINATION_README.md create mode 100644 app.js create mode 100644 controllers/adminController.js create mode 100644 controllers/user/auth.js create mode 100644 db/blog.js create mode 100644 db/init.js create mode 100644 fix_database.js create mode 100644 middleware/auth.js create mode 100644 models/Category.js create mode 100644 models/Comment.js create mode 100644 models/Link.js create mode 100644 models/Post.js create mode 100644 models/User.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 routes/admin/index.js create mode 100644 routes/user/auth.js create mode 100644 test_links.js create mode 100644 test_pagination.js create mode 100644 test_pagination_component.js create mode 100644 uploads/app.js create mode 100644 uploads/config.js create mode 100644 utils/pagination.js create mode 100644 views/admin/categories/list.ejs create mode 100644 views/admin/comments/list.ejs create mode 100644 views/admin/index.ejs create mode 100644 views/admin/links/list.ejs create mode 100644 views/admin/posts/list.ejs create mode 100644 views/admin/users/list.ejs create mode 100644 views/components/pagination.ejs create mode 100644 views/user/index.ejs create mode 100644 views/user/info.ejs create mode 100644 views/user/login.ejs create mode 100644 views/user/register.ejs create mode 100644 views/user/userInfo.ejs create mode 100644 初始化数据.bat create mode 100644 项目启动.bat diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..f4a08c3 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,3 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +.idea/ +node_modules/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index ceaea36..a3530df 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* - +.idea # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/PAGINATION_README.md b/PAGINATION_README.md new file mode 100644 index 0000000..cf13d1d --- /dev/null +++ b/PAGINATION_README.md @@ -0,0 +1,183 @@ +# 分页功能说明 + +## 概述 + +博客系统已集成完整的分页功能,支持以下页面的分页显示: + +- **首页博客文章列表** - 每页显示5篇文章 +- **管理员后台文章管理** - 每页显示10篇文章 +- **管理员后台用户管理** - 每页显示10个用户 +- **管理员后台评论管理** - 每页显示10条评论 + +## 功能特性 + +### 1. 通用分页组件 +- **文件位置**: `views/components/pagination.ejs` +- **功能**: 可复用的分页UI组件 +- **特性**: + - 响应式设计 + - 智能页码显示(最多显示5个页码) + - 上一页/下一页导航 + - 首页/末页快速跳转 + - 省略号显示 + - 当前页高亮 + - 分页信息显示 + +### 2. 分页工具函数 +- **文件位置**: `utils/pagination.js` +- **主要函数**: + - `getPaginationInfo()` - 计算分页信息 + - `paginateQuery()` - 执行分页查询 + - `addPaginationToQuery()` - 为查询添加分页 + +### 3. 分页样式 +- **设计风格**: 现代化Bootstrap风格 +- **颜色主题**: 蓝色主题(#3498db) +- **交互效果**: 悬停动画、过渡效果 +- **响应式**: 支持移动端适配 + +## 使用方法 + +### 1. 后端路由中使用分页 + +```javascript +const { paginateQuery } = require('./utils/pagination'); + +// 在路由中使用 +router.get('/posts', async (req, res) => { + const page = parseInt(req.query.page) || 1; + const limit = 10; + + const result = await paginateQuery( + Post, // Mongoose模型 + {}, // 查询条件 + { sort: { createdAt: -1 } }, // 查询选项 + page, // 当前页码 + limit // 每页数量 + ); + + res.render('template', { + posts: result.data, + pagination: result.pagination, + baseUrl: '/posts', + query: {} + }); +}); +``` + +### 2. 前端模板中使用分页组件 + +```ejs + +<%- include('components/pagination', { + currentPage: pagination.currentPage, + totalPages: pagination.totalPages, + baseUrl: baseUrl, + query: query +}) %> +``` + +### 3. 分页参数说明 + +- `currentPage`: 当前页码 +- `totalPages`: 总页数 +- `baseUrl`: 基础URL(用于生成分页链接) +- `query`: 查询参数对象(用于保持其他查询参数) + +## 分页配置 + +### 首页文章列表 +- **每页数量**: 5篇文章 +- **排序**: 按创建时间倒序 +- **URL**: `/` + +### 管理员后台 +- **文章管理**: 每页10篇文章 +- **用户管理**: 每页10个用户 +- **评论管理**: 每页10条评论 + +## 分页组件特性 + +### 1. 智能页码显示 +- 当前页前后各显示2页 +- 最多显示5个页码 +- 自动显示首页和末页 +- 使用省略号表示跳过的页码 + +### 2. 导航功能 +- 上一页/下一页按钮 +- 首页/末页快速跳转 +- 页码直接跳转 +- 禁用状态处理 + +### 3. 查询参数保持 +- 自动保持URL中的查询参数 +- 支持搜索、筛选等功能 +- 分页不影响其他查询条件 + +### 4. 响应式设计 +- 移动端适配 +- 触摸友好的按钮大小 +- 清晰的视觉层次 + +## 样式定制 + +分页组件的样式可以通过修改 `views/components/pagination.ejs` 中的CSS来自定义: + +```css +.pagination-wrapper { + margin-top: 30px; +} + +.page-link { + color: #3498db; + border-color: #dee2e6; + transition: all 0.3s ease; +} + +.page-item.active .page-link { + background-color: #3498db; + border-color: #3498db; + color: white; +} +``` + +## 测试 + +运行分页功能测试: + +```bash +node test_pagination.js +``` + +## 注意事项 + +1. **数据库性能**: 分页查询使用 `skip` 和 `limit`,大数据量时建议使用索引优化 +2. **内存使用**: 分页组件会计算总记录数,确保数据库连接稳定 +3. **URL参数**: 分页参数使用 `page` 参数,避免与其他参数冲突 +4. **错误处理**: 分页组件包含完整的错误处理机制 + +## 扩展功能 + +### 1. 添加搜索功能 +```javascript +// 在查询条件中添加搜索 +const searchQuery = req.query.search ? { title: { $regex: req.query.search, $options: 'i' } } : {}; +const result = await paginateQuery(Post, searchQuery, options, page, limit); +``` + +### 2. 添加筛选功能 +```javascript +// 添加分类筛选 +const categoryFilter = req.query.category ? { category: req.query.category } : {}; +const result = await paginateQuery(Post, categoryFilter, options, page, limit); +``` + +### 3. 自定义每页数量 +```javascript +// 允许用户选择每页显示数量 +const limit = parseInt(req.query.limit) || 10; +const result = await paginateQuery(Post, {}, options, page, limit); +``` + +分页功能已完全集成到博客系统中,提供了良好的用户体验和性能优化。 \ No newline at end of file diff --git a/README.md b/README.md index 4eb9a86..5c3bd44 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,135 @@ -# node-blog +# 陈立龙博客系统 -基于 Node.js + Express + MongoDB + EJS 的博客系统。 \ No newline at end of file +一个基于 Node.js + Express + MongoDB + EJS 的博客系统。 + +## 功能特性 + +- 用户注册和登录 +- 博客文章管理 +- 友情链接管理 +- 响应式设计 +- 管理员后台 + +## 技术栈 + +- **后端**: Node.js + Express +- **数据库**: MongoDB + Mongoose +- **模板引擎**: EJS +- **会话管理**: express-session + connect-mongo +- **样式**: CSS3 + Grid布局 + +## 项目结构 + +``` +ChenLilong_Blog/ +├── app.js # 主应用文件 +├── models/ # 数据模型 +│ ├── Post.js # 博客文章模型 +│ ├── User.js # 用户模型 +│ ├── Link.js # 友情链接模型 +│ └── ... +├── routes/ # 路由文件 +│ ├── user/ # 用户相关路由 +│ └── admin/ # 管理员路由 +├── views/ # 视图模板 +│ ├── user/ # 用户页面 +│ └── admin/ # 管理员页面 +├── middleware/ # 中间件 +├── db/ # 数据库相关 +└── uploads/ # 上传文件 +``` + +## 安装和运行 + +### 1. 安装依赖 + +```bash +npm install +``` + +### 2. 启动MongoDB数据库 + +双击运行 `数据库启动.bat` 或在命令行中运行: + +```bash +mongod --dbpath C:\data\db +``` + +### 3. 初始化测试数据(可选) + +双击运行 `初始化数据.bat` 或在命令行中运行: + +```bash +node db/init.js +``` + +### 4. 启动应用 + +双击运行 `项目启动.bat` 或在命令行中运行: + +```bash +node app.js +``` + +### 5. 访问应用 + +打开浏览器访问:http://localhost:3001 + +## 首页功能 + +### 左侧:博客文章列表 +- 显示最新的10篇博客文章 +- 每篇文章显示标题、作者、分类、发布时间 +- 文章内容预览(限制150字符) +- 阅读全文链接 + +### 右侧:友情链接 +- 显示所有友情链接 +- 点击链接在新标签页中打开 +- 悬停效果和动画 + +## 管理员功能 + +访问 http://localhost:3001/admin 进入管理员后台: + +- 用户管理 +- 文章管理 +- 分类管理 +- 评论管理 +- 友情链接管理 + +## 数据库配置 + +默认数据库配置: +- 数据库名:blog +- 连接地址:mongodb://127.0.0.1:27017/blog + +## 注意事项 + +1. 确保MongoDB已正确安装并运行 +2. 首次运行建议先执行数据初始化脚本 +3. 管理员账号需要在数据库中手动创建或通过注册功能创建 + +![image-20250624113519953](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113519953.png) + +![image-20250624113527404](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113527404.png) + +![image-20250624113549797](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113549797.png) + +![image-20250624113559568](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113559568.png) + +![image-20250624113612329](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113612329.png) + +![image-20250624113623177](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113623177.png) + +![image-20250624113628972](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113628972.png) + +![image-20250624113638050](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113638050.png) + +![image-20250624113644382](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113644382.png) + +![image-20250624113652652](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113652652.png) + +![image-20250624113704419](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113704419.png) + +![image-20250624113714538](https://xy-md-assets.oss-cn-hangzhou.aliyuncs.com/image-20250624113714538.png) diff --git a/app.js b/app.js new file mode 100644 index 0000000..d458fc5 --- /dev/null +++ b/app.js @@ -0,0 +1,118 @@ +const express = require('express'); +const mongoose = require('mongoose'); +const session = require('express-session'); +const MongoStore = require('connect-mongo'); +const bodyParser = require('body-parser'); +const cookieParser = require('cookie-parser'); +const path = require('path'); +const { paginateQuery } = require('./utils/pagination'); +const app = express(); + +// 增强的 MongoDB 连接配置 +mongoose.connect('mongodb://127.0.0.1:27017/blog', { // 使用127.0.0.1而不是localhost + useNewUrlParser: true, + useUnifiedTopology: true, + serverSelectionTimeoutMS: 5000, // 添加服务器选择超时 + socketTimeoutMS: 45000, // 添加socket超时 + family: 4 // 强制使用IPv4 +}) + .then(() => { + console.log('MongoDB 已经连接'); + // 连接成功后启动服务器 + startServer(); + }) + .catch(err => { + console.error('MongoDB 连接错误:', err); + process.exit(1); // 如果数据库连接失败,退出应用 + }); + +// 添加Mongoose连接状态监听 +mongoose.connection.on('connecting', () => console.log('正在连接MongoDB...')); +mongoose.connection.on('disconnected', () => console.log('MongoDB 连接断开')); +mongoose.connection.on('reconnected', () => console.log('MongoDB 重新连接')); + +// 其他中间件配置 +app.set('view engine', 'ejs'); +app.set('views', path.join(__dirname, 'views')); +app.use(express.static(path.join(__dirname, 'public'))); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); +app.use(cookieParser()); + +// 增强的session配置 +app.use(session({ + secret: 'chenlilong_blog_secret', + resave: false, + saveUninitialized: false, + store: MongoStore.create({ + mongoUrl: 'mongodb://127.0.0.1:27017/blog', + ttl: 24 * 60 * 60, // 1天 + autoRemove: 'native' // 自动清理过期session + }), + cookie: { + maxAge: 1000 * 60 * 60 * 24, + httpOnly: true, + secure: process.env.NODE_ENV === 'production' + } +})); + +// 路由配置 +app.get('/', async (req, res) => { + try { + const Post = require('./models/Post'); + const Link = require('./models/Link'); + const { paginateQuery } = require('./utils/pagination'); + + // 获取分页参数 + const page = parseInt(req.query.page) || 1; + const limit = 5; // 每页显示5篇文章 + + // 获取博客文章(带分页,只获取已发布的文章) + const postsResult = await paginateQuery( + Post, + { isPublished: true }, // 只显示已发布的文章 + { sort: { isTop: -1, createdAt: -1 } }, // 置顶文章优先,然后按时间倒序 + page, + limit + ); + + // 获取所有友情链接 + const links = await Link.find().sort({ order: 1, createdAt: -1 }); + + res.render('user/index', { + user: req.session.user || null, + posts: postsResult.data, + links: links, + pagination: postsResult.pagination, + baseUrl: '/', + query: {} + }); + } catch (error) { + console.error('获取首页数据失败:', error); + res.render('user/index', { + user: req.session.user || null, + posts: [], + links: [], + pagination: { currentPage: 1, totalPages: 1, total: 0 }, + baseUrl: '/', + query: {} + }); + } +}); + +const userAuthRouter = require('./routes/user/auth'); +app.use('/', userAuthRouter); +const adminRouter = require('./routes/admin/index'); +app.use('/admin', adminRouter); + +app.use((req, res) => { + res.status(404).send('404 Not Found'); +}); + +// 将服务器启动封装为函数 +function startServer() { + const PORT = process.env.PORT || 3001; + app.listen(PORT, () => { + console.log(`Server running at http://localhost:${PORT}`); + }); +} diff --git a/controllers/adminController.js b/controllers/adminController.js new file mode 100644 index 0000000..68ab4f2 --- /dev/null +++ b/controllers/adminController.js @@ -0,0 +1,235 @@ +const User = require('../models/User'); +const Category = require('../models/Category'); +const Post = require('../models/Post'); +const Comment = require('../models/Comment'); +const Link = require('../models/Link'); + +// 用户管理 +exports.userList = async (req, res) => { + try { + const users = await User.find().sort({ createdAt: -1 }); + res.render('admin/users/list', { users }); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.updateUserStatus = async (req, res) => { + try { + const user = await User.findByIdAndUpdate( + req.params.id, + { isActive: req.body.isActive }, + { new: true } + ); + if (!user) return res.status(404).json({ error: '用户不存在' }); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +exports.resetPassword = async (req, res) => { + try { + const user = await User.findById(req.params.id); + if (!user) return res.status(404).json({ error: '用户不存在' }); + + user.password = req.body.newPassword; + await user.save(); + + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +// 分类管理 +exports.categoryList = async (req, res) => { + try { + const categories = await Category.find().sort({ order: 1 }); + res.render('admin/categories/list', { categories }); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.createCategory = async (req, res) => { + try { + const { name, slug, order, isTop } = req.body; + const category = new Category({ name, slug, order, isTop }); + await category.save(); + res.redirect('/admin/categories'); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.updateCategoryTopStatus = async (req, res) => { + try { + const category = await Category.findByIdAndUpdate( + req.params.id, + { isTop: req.body.isTop }, + { new: true } + ); + if (!category) return res.status(404).json({ error: '分类不存在' }); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +exports.deleteCategory = async (req, res) => { + try { + await Category.findByIdAndDelete(req.params.id); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +// 文章管理 +exports.postList = async (req, res) => { + try { + const posts = await Post.find().sort({ createdAt: -1 }); + res.render('admin/posts/list', { posts }); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.updatePostTopStatus = async (req, res) => { + try { + const post = await Post.findByIdAndUpdate( + req.params.id, + { isTop: req.body.isTop }, + { new: true } + ); + if (!post) return res.status(404).json({ error: '文章不存在' }); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +exports.updatePostPublishStatus = async (req, res) => { + try { + const post = await Post.findByIdAndUpdate( + req.params.id, + { isPublished: req.body.isPublished }, + { new: true } + ); + if (!post) return res.status(404).json({ error: '文章不存在' }); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +exports.deletePost = async (req, res) => { + try { + await Post.findByIdAndDelete(req.params.id); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +exports.createPost = async (req, res) => { + try { + const { title, content, category, isPublished, isTop } = req.body; + const author = req.user._id; + const post = new Post({ + title, + content, + category, + author, + isPublished: isPublished || false, + isTop: isTop || false + }); + await post.save(); + res.json({ success: true, post }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +exports.updatePost = async (req, res) => { + try { + const { title, content, category, isPublished, isTop } = req.body; + const post = await Post.findByIdAndUpdate( + req.params.id, + { title, content, category, isPublished, isTop }, + { new: true } + ); + if (!post) return res.status(404).json({ error: '文章不存在' }); + res.json({ success: true, post }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +// 评论管理 +exports.commentList = async (req, res) => { + try { + const comments = await Comment.find().populate('user post').sort({ createdAt: -1 }); + res.render('admin/comments/list', { comments }); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.deleteComment = async (req, res) => { + try { + await Comment.findByIdAndDelete(req.params.id); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; + +// 友情链接管理 +exports.linkList = async (req, res) => { + try { + const links = await Link.find().sort({ order: 1 }); + res.render('admin/links/list', { links }); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.createLink = async (req, res) => { + try { + const { name, url, description, order } = req.body; + const link = new Link({ name, url, description, order }); + await link.save(); + res.redirect('/admin/links'); + } catch (err) { + console.error(err); + res.status(500).send('服务器错误'); + } +}; + +exports.deleteLink = async (req, res) => { + try { + await Link.findByIdAndDelete(req.params.id); + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ error: '服务器错误' }); + } +}; diff --git a/controllers/user/auth.js b/controllers/user/auth.js new file mode 100644 index 0000000..3fc989a --- /dev/null +++ b/controllers/user/auth.js @@ -0,0 +1,80 @@ +const User = require('../../models/User'); +const bcrypt = require('bcryptjs'); + +// 注册 +exports.register = async (req, res) => { + const { username, password } = req.body; + if (!username || !password) { + return res.render('user/register', { error: '用户名和密码不能为空' }); + } + const exist = await User.findOne({ username }); + if (exist) { + return res.render('user/register', { error: '用户名已存在' }); + } + const hash = await bcrypt.hash(password, 10); + const user = new User({ username, password: hash }); + await user.save(); + res.redirect('/login'); +}; + +// 登录 +exports.login = async (req, res) => { + const { username, password } = req.body; + const user = await User.findOne({ username }); + if (!user) { + return res.render('user/login', { error: '用户不存在' }); + } + if (user.status === 'frozen') { + return res.render('user/login', { error: '账号已被冻结' }); + } + const match = await bcrypt.compare(password, user.password); + if (!match) { + return res.render('user/login', { error: '密码错误' }); + } + req.session.user = { + _id: user._id, + username: user.username, + role: user.role + }; + if (username === 'admin') { + // 跳转到后台管理页面 + return res.redirect('/admin'); + } + + res.redirect('/'); +}; + +// 登出 +exports.logout = (req, res) => { + req.session.destroy(() => { + res.redirect('/login'); + }); +}; + +// 修改密码 +exports.changePassword = async (req, res) => { + if (!req.session.user) { + return res.status(401).json({ success: false, message: '请先登录' }); + } + const { oldPassword, newPassword } = req.body; + if (!oldPassword || !newPassword) { + return res.json({ success: false, message: '参数不完整' }); + } + try { + const user = await User.findById(req.session.user._id); + if (!user) { + return res.json({ success: false, message: '用户不存在' }); + } + const match = await bcrypt.compare(oldPassword, user.password); + if (!match) { + return res.json({ success: false, message: '原密码错误' }); + } + const hash = await bcrypt.hash(newPassword, 10); + user.password = hash; + await user.save(); + res.json({ success: true }); + } catch (err) { + console.error('修改密码失败:', err); + res.status(500).json({ success: false, message: '服务器错误' }); + } +}; diff --git a/db/blog.js b/db/blog.js new file mode 100644 index 0000000..e14f324 --- /dev/null +++ b/db/blog.js @@ -0,0 +1,274 @@ +/* + Navicat Premium Data Transfer + + Source Server : localhost_27017 + Source Server Type : MongoDB + Source Server Version : 80010 (8.0.10) + Source Host : localhost:27017 + Source Schema : blog + + Target Server Type : MongoDB + Target Server Version : 80010 (8.0.10) + File Encoding : 65001 + + Date: 24/06/2025 11:40:35 +*/ + + +// ---------------------------- +// Collection structure for categories +// ---------------------------- +db.getCollection("categories").drop(); +db.createCollection("categories"); +db.getCollection("categories").createIndex({ + name: NumberInt("1") +}, { + name: "name_1", + background: true, + unique: true +}); + +// ---------------------------- +// Documents of categories +// ---------------------------- +db.getCollection("categories").insert([ { + _id: ObjectId("68591bca984303d931013db2") +} ]); + +// ---------------------------- +// Collection structure for comments +// ---------------------------- +db.getCollection("comments").drop(); +db.createCollection("comments"); + +// ---------------------------- +// Documents of comments +// ---------------------------- +db.getCollection("comments").insert([ { + _id: ObjectId("685a19e7109455f150256912"), + content: "不错的讲话,中国加油", + author: ObjectId("68590fed4ef1dd3c6a957339"), + post: ObjectId("685a15b95ce698838bef9269"), + createdAt: ISODate("2025-06-24T03:22:15.371Z"), + __v: NumberInt("0") +} ]); + +// ---------------------------- +// Collection structure for links +// ---------------------------- +db.getCollection("links").drop(); +db.createCollection("links"); + +// ---------------------------- +// Documents of links +// ---------------------------- +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337750"), + name: "百度", + url: "https://www.baidu.com/?tn=68018901_16_pg", + description: "百度搜索", + order: NumberInt("1"), + createdAt: ISODate("2025-06-24T01:49:36.942Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337751"), + name: "谷歌", + url: "https://www.google.com", + description: "全球最大的搜索引擎", + order: NumberInt("2"), + createdAt: ISODate("2025-06-24T01:50:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337752"), + name: "必应", + url: "https://www.bing.com", + description: "微软推出的搜索引擎", + order: NumberInt("3"), + createdAt: ISODate("2025-06-24T01:51:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337753"), + name: "GitHub", + url: "https://github.com", + description: "全球最大的代码托管平台", + order: NumberInt("4"), + createdAt: ISODate("2025-06-24T01:52:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337754"), + name: "Stack Overflow", + url: "https://stackoverflow.com", + description: "程序员问答社区", + order: NumberInt("5"), + createdAt: ISODate("2025-06-24T01:53:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337755"), + name: "知乎", + url: "https://www.zhihu.com", + description: "中文问答社区", + order: NumberInt("6"), + createdAt: ISODate("2025-06-24T01:54:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337756"), + name: "微博", + url: "https://weibo.com", + description: "中文社交媒体平台", + order: NumberInt("7"), + createdAt: ISODate("2025-06-24T01:55:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337757"), + name: "CSDN", + url: "https://www.csdn.net", + description: "中文IT技术社区", + order: NumberInt("8"), + createdAt: ISODate("2025-06-24T01:56:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337758"), + name: "掘金", + url: "https://juejin.cn", + description: "开发者技术社区", + order: NumberInt("9"), + createdAt: ISODate("2025-06-24T01:57:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b12337759"), + name: "哔哩哔哩", + url: "https://www.bilibili.com", + description: "视频分享网站", + order: NumberInt("10"), + createdAt: ISODate("2025-06-24T01:58:00.000Z"), + __v: NumberInt("0") +} ]); +db.getCollection("links").insert([ { + _id: ObjectId("685a0430cf497e2b1233775a"), + name: "豆瓣", + url: "https://www.douban.com", + description: "图书、电影、音乐评论网站", + order: NumberInt("11"), + createdAt: ISODate("2025-06-24T01:59:00.000Z"), + __v: NumberInt("0") +} ]); + +// ---------------------------- +// Collection structure for posts +// ---------------------------- +db.getCollection("posts").drop(); +db.createCollection("posts"); + +// ---------------------------- +// Documents of posts +// ---------------------------- +db.getCollection("posts").insert([ { + _id: ObjectId("685a15b95ce698838bef9269"), + title: "任正非专访", + content: "本周,《人民日报》头版刊登任正非专访。\n\n下面是一些摘录。\n\n(1)芯片问题其实没必要担心。我们单芯片还是落后美国一代,我们用数学补物理、非摩尔补摩尔,用群计算补单芯片,在结果上也能达到实用状况。\n\n(2)软件是卡不住脖子的,那是数学的图形符号、代码,一些尖端的算子、算法垒起来的,没有阻拦索。困难在我们的教育培养、人才梯队的建设。\n\n(3)当我国拥有一定经济实力的时候,要重视理论特别是基础理论的研究。如果不搞基础研究,就没根。即使叶茂,欣欣向荣,风一吹就会倒的。\n\n(4)我们要理解支持搞理论工作的。理论科学家是孤独的,我们要有战略耐心,要理解他们。他们头脑中的符号、公式、思维,世界上能与他们沟通的只有几个人。对理论科学家要尊重,因为我们不懂他的文化,社会要宽容,国家要支持。\n\n(5)买国外的产品很贵,因为价格里面就包含他们在基础研究上的投入。中国搞不搞基础研究,也要付钱的,能不能付给自己搞基础研究的人。\n\n(6)华为一年1800亿投入研发,大概有600亿是做基础理论研究,不考核。1200亿左右投入产品研发,投入是要考核的。没有理论就没有突破,我们就赶不上美国。\n\n(7)人工智能也许是人类社会最后一次技术革命,当然可能还有能源的核聚变。发展人工智能要有电力保障,中国的发电、电网传输都是非常好的,通信网络是世界最发达的,东数西算的理想是可能实现的。\n\n(8)赞声与骂声,都不要在意,而要在乎自己能不能做好。把自己做好,就没有问题。", + author: "人民日报", + category: "技术", + isPublished: true, + isTop: true, + createdAt: ISODate("2025-06-24T03:04:25.725Z"), + updatedAt: ISODate("2025-06-24T03:04:25.729Z"), + __v: NumberInt("0") +} ]); +db.getCollection("posts").insert([ { + _id: ObjectId("685a15ff5ce698838bef9285"), + title: "程序员常用的六大技术博客类", + content: "一:CSDN\n\n网址:http://www.csdn.net/\n\n介绍:CSDN深度IT技术博客,移动开发博客,Web前端博客,企业架构博客,编程语言博客,互联网博客,数据库博客,系统运维博客,云计算博客,研发管理博客,但是csdn最近感觉访问速度比较慢,博客还好些,下载是有时候真慢真卡;以前的CSDN还可以,最近两年商业化似乎越来越严重。\n\n\n\n\n二:博客园\n\n网址:http://www.cnblogs.com\n\n介绍:博客园是一个面向开发者的知识分享社区。自创建以来,博客园一直致力并专注于为开发者打造一个纯净的技术交流社区,推动并帮助开发者通过互联网分享知识,从而让更多的技术者交流,从用博客园以来,发现有一个小小的缺点,就是UI设计的太古板,一直都没有在设计上有所突破,在这个扁平化趋势越来越成为主流的网络上,没有能让人眼前一亮的感觉。\n\n\n三:掘金\n\n网址: https://juejin.im\n\n介绍:掘金是一个帮助开发者成长的社区,是给开发者用的 Hacker News,给设计师用的 Designer News,和给产品经理用的 Medium。掘金的技术文章由稀土上聚集的技术大牛所写 ,文章技术含量很高,但在宣传上似乎有欠缺,在百度搜索关键字,关于掘金的信息也特别的少,或许很多人都不知道吧。很喜欢掘金的页面布局,给人的感觉就是简单大方,相比其他的博客,显得不拥不挤条条有理。\n\n\n\n\n四:github\n\n网址 :https://github.com/\n\n介绍:这个就不多说了,所有的程序员都拥有这样一个只属于自己的代码托管平台吧,gitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名gitHub。gitHub于2008年4月10日正式上线,除了git代码仓库托管及基本的 Web管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑等功能,方便实用。平时没事的时候可以多上传一些自己写的项目,demo,代码等等,在面试的时候还能给自己涨不少的分。\n\n\n\n\n五:segmentfault\n\n网址:https://segmentfault.com/\n\n介绍:SegmentFault是中国领先的开发者技术社区。 为编程爱好者提供一个纯粹、高质的技术交流的平台, 与开发者一起学习、交流与成长,关于前端这一块的技术交流者也是非常多的,每次有问题我都会发出来,就会有很多同行帮忙解答,在这里,也有很多脑洞大开的工程师,每次逛完论坛,就像是走在铺满各种各样贝壳的沙滩上,有太多惊喜和闪闪发光值得收藏的技术点。\n\n\n\n\n六:开源中国\n\n网址 :https://my.oschina.net\n\n介绍:开源中国是目前中国最大的开源技术社区。我们传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。主要有开源软件库、代码分享、资讯、协作翻译、码云、众包、招聘等几大模块,虽然首页信息量很多,但对于个人空间管理,无论是提问还是发表文章,记录笔记等,页面是很整洁简约的。\n\n作者:祈澈菇凉\n链接:https://www.jianshu.com/p/d1614f890282\n来源:简书\n著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。", + author: "CSDN", + category: "技术", + isPublished: true, + isTop: false, + createdAt: ISODate("2025-06-24T03:05:35.067Z"), + updatedAt: ISODate("2025-06-24T03:05:35.068Z"), + __v: NumberInt("0") +} ]); +db.getCollection("posts").insert([ { + _id: ObjectId("685a16255ce698838bef928d"), + title: "精选技术博客推荐", + content: "程序员进阶指南:精选技术博客推荐\n大家好,今天给大家推荐一些我私藏的技术博客和实用网站,希望能帮到正在努力提升自己的程序员们。不过,有些平台实在是不敢在帖子里发链接,怕哪天号被封了,所以大家可以自行上网搜索哦~\n\n🌟 博客:Jack Franklin\nJack是谷歌Chrome团队的开发者之一,他的博客风格非常程序员化,走的是极简路线,体验非常棒。他的技术博客主要关注React、Webpack和各种JavaScript技巧,还有一些实战经验。特别推荐他的VSCode指南系列,真的是非常实用。除此之外,他还会时不时分享一些经验总结和个人体悟,我特别喜欢他关于从错误中学习和对开发者职场发展的思考。\n\n🌟 博客:James Ward\nJames是Google Cloud的倡导者,他的讲座和视频比博客本身更出名。他的技术涵盖面非常广,毕竟是混迹大厂职场的人,包括Adobe和Salesforce。不过他的前端技术相对较浅,主要集中在Java、Salesforce以及云和微服务方面。\n\n🌟 网站:usethekeyboard.com\n这是一个汇总了各种软件快捷键的cheatsheet收藏网站。我是在搜Notion快捷键时无意中发现的。作为程序员,平时使用IDE和Markdown的时候,快捷键真的是救命稻草。特别是最近从Evernote转到Notion,这个网站帮了我大忙。里面常用的快捷键包括VSCode、Notion、Unity、Xcode,还有一些职场常用软件如Slack、Jira、Gitlab等。希望有空的时候能加上IntelliJ的快捷键。\n\n🌟 写码小练习:typescript-exercises.github.io\n这个webapp特别适合想入门TypeScript但又找不到练习上手的朋友们。一共有16个小练习,每一节都在之前的基础上拓展,基本涵盖了TypeScript的重要知识点。写不出来还可以参考答案。作为一个教了三年函数式编程的老师,这个练习居然有柯里化的内容,简直太亲切了。\n\n希望这些推荐对大家有帮助!无论是职场还是求职,程序员的提升之路永远没有终点。加油!", + author: "百度", + category: "随笔", + isPublished: true, + isTop: false, + createdAt: ISODate("2025-06-24T03:06:13.624Z"), + updatedAt: ISODate("2025-06-24T03:06:13.624Z"), + __v: NumberInt("0") +} ]); + +// ---------------------------- +// Collection structure for sessions +// ---------------------------- +db.getCollection("sessions").drop(); +db.createCollection("sessions"); +db.getCollection("sessions").createIndex({ + expires: NumberInt("1") +}, { + name: "expires_1", + background: true +}); + +// ---------------------------- +// Documents of sessions +// ---------------------------- +db.getCollection("sessions").insert([ { + _id: "DQULrlwQkkF9Sm96awZ8jD5dFu0qML08", + expires: ISODate("2025-06-24T15:24:12.519Z"), + session: "{\"cookie\":{\"originalMaxAge\":86400000,\"expires\":\"2025-06-24T09:22:53.530Z\",\"secure\":false,\"httpOnly\":true,\"path\":\"/\"},\"user\":{\"_id\":\"68590fed4ef1dd3c6a957339\",\"username\":\"admin\",\"role\":\"user\"}}" +} ]); +db.getCollection("sessions").insert([ { + _id: "nocdVsYg0XuMYkq9Og5kWuG9nUefstHk", + expires: ISODate("2025-06-25T03:37:10.417Z"), + session: "{\"cookie\":{\"originalMaxAge\":86400000,\"expires\":\"2025-06-25T03:35:44.536Z\",\"secure\":false,\"httpOnly\":true,\"path\":\"/\"},\"user\":{\"_id\":\"68590fed4ef1dd3c6a957339\",\"username\":\"admin\",\"role\":\"user\"}}" +} ]); + +// ---------------------------- +// Collection structure for users +// ---------------------------- +db.getCollection("users").drop(); +db.createCollection("users"); +db.getCollection("users").createIndex({ + username: NumberInt("1") +}, { + name: "username_1", + background: true, + unique: true +}); + +// ---------------------------- +// Documents of users +// ---------------------------- +db.getCollection("users").insert([ { + _id: ObjectId("68590fed4ef1dd3c6a957339"), + username: "admin", + password: "$2a$10$5Egc33FsjLyY7mHYp34RsOXM3ZWehZutDEWq4OYy5tcXHXz9ra4ca", + role: "user", + status: "active", + createdAt: ISODate("2025-06-23T08:27:25.734Z"), + __v: NumberInt("0"), + favorites: [ + ObjectId("685a15b95ce698838bef9269") + ] +} ]); +db.getCollection("users").insert([ { + _id: ObjectId("685a1c4a064b039cc10ee9ea"), + username: "123456", + password: "$2a$10$mK3wHu9wu/r8zJnmuBup7.kWj0OmGsfnivVB/2YugdUxEk3ScL2fO", + role: "user", + status: "active", + favorites: [ ], + createdAt: ISODate("2025-06-24T03:32:26.089Z"), + __v: NumberInt("0") +} ]); diff --git a/db/init.js b/db/init.js new file mode 100644 index 0000000..8622f99 --- /dev/null +++ b/db/init.js @@ -0,0 +1,112 @@ +const mongoose = require('mongoose'); +const Post = require('../models/Post'); +const Link = require('../models/Link'); + +// 连接数据库 +mongoose.connect('mongodb://127.0.0.1:27017/blog', { + useNewUrlParser: true, + useUnifiedTopology: true +}) +.then(() => { + console.log('数据库连接成功'); + initData(); +}) +.catch(err => { + console.error('数据库连接失败:', err); + process.exit(1); +}); + +async function initData() { + try { + // 清空现有数据 + await Post.deleteMany({}); + await Link.deleteMany({}); + + // 添加测试博客文章 + const posts = [ + { + title: '欢迎来到我的博客', + content: '这是我的第一篇博客文章。在这里,我将分享我的技术心得、学习笔记和生活感悟。希望这个博客能够成为我们交流的平台,也希望能够通过写作来提升自己的表达能力。', + author: '陈立龙', + category: '随笔' + }, + { + title: 'JavaScript 异步编程详解', + content: 'JavaScript 的异步编程是前端开发中的重要概念。本文将详细介绍 Promise、async/await 和回调函数的使用方法,以及它们之间的区别和最佳实践。通过实际的代码示例,帮助读者更好地理解异步编程的核心概念。', + author: '陈立龙', + category: '技术' + }, + { + title: 'Node.js 开发环境搭建', + content: 'Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。本文将详细介绍如何在 Windows、Mac 和 Linux 系统上搭建 Node.js 开发环境,包括安装 Node.js、配置 npm 镜像源、安装常用开发工具等步骤。', + author: '陈立龙', + category: '教程' + }, + { + title: 'MongoDB 数据库操作指南', + content: 'MongoDB 是一个基于分布式文件存储的数据库。本文将介绍 MongoDB 的基本概念、安装配置、常用操作命令,以及在 Node.js 中使用 Mongoose 进行数据库操作的方法。', + author: '陈立龙', + category: '数据库' + }, + { + title: '前端开发工具推荐', + content: '工欲善其事,必先利其器。本文将推荐一些前端开发中常用的工具,包括代码编辑器、浏览器开发者工具、包管理器、构建工具等,帮助开发者提高开发效率。', + author: '陈立龙', + category: '工具' + } + ]; + + // 添加测试友情链接 + const links = [ + { + name: 'GitHub', + url: 'https://github.com', + description: '全球最大的代码托管平台', + order: 1 + }, + { + name: 'Stack Overflow', + url: 'https://stackoverflow.com', + description: '程序员问答社区', + order: 2 + }, + { + name: 'MDN Web Docs', + url: 'https://developer.mozilla.org', + description: 'Web开发技术文档', + order: 3 + }, + { + name: 'W3Schools', + url: 'https://www.w3schools.com', + description: 'Web技术学习网站', + order: 4 + }, + { + name: 'CSS-Tricks', + url: 'https://css-tricks.com', + description: 'CSS技巧和教程', + order: 5 + }, + { + name: 'JavaScript.info', + url: 'https://javascript.info', + description: '现代JavaScript教程', + order: 6 + } + ]; + + // 插入数据 + await Post.insertMany(posts); + await Link.insertMany(links); + + console.log('测试数据初始化完成!'); + console.log(`添加了 ${posts.length} 篇博客文章`); + console.log(`添加了 ${links.length} 个友情链接`); + + mongoose.connection.close(); + } catch (error) { + console.error('初始化数据失败:', error); + mongoose.connection.close(); + } +} \ No newline at end of file diff --git a/fix_database.js b/fix_database.js new file mode 100644 index 0000000..b6e18d8 --- /dev/null +++ b/fix_database.js @@ -0,0 +1,103 @@ +const mongoose = require('mongoose'); +const Post = require('./models/Post'); + +// 连接数据库 +mongoose.connect('mongodb://127.0.0.1:27017/blog', { + useNewUrlParser: true, + useUnifiedTopology: true +}) +.then(() => { + console.log('数据库连接成功'); + fixDatabase(); +}) +.catch(err => { + console.error('数据库连接失败:', err); + process.exit(1); +}); + +async function fixDatabase() { + try { + console.log('开始检查和修复数据库...\n'); + + // 查找所有文章 + const posts = await Post.find({}); + console.log(`找到 ${posts.length} 篇文章`); + + let fixedCount = 0; + + for (let post of posts) { + let needsUpdate = false; + let updates = {}; + + // 检查并修复title字段 + if (!post.title || post.title.trim() === '') { + updates.title = '无标题'; + needsUpdate = true; + console.log(`修复文章 ${post._id} 的标题`); + } + + // 检查并修复content字段 + if (!post.content || post.content.trim() === '') { + updates.content = '暂无内容'; + needsUpdate = true; + console.log(`修复文章 ${post._id} 的内容`); + } + + // 检查并修复author字段 + if (!post.author || post.author.trim() === '') { + updates.author = '未知作者'; + needsUpdate = true; + console.log(`修复文章 ${post._id} 的作者`); + } + + // 检查并修复category字段 + if (!post.category || post.category.trim() === '') { + updates.category = '未分类'; + needsUpdate = true; + console.log(`修复文章 ${post._id} 的分类`); + } + + // 添加缺失的字段 + if (post.isPublished === undefined) { + updates.isPublished = true; + needsUpdate = true; + console.log(`为文章 ${post._id} 添加发布状态`); + } + + if (post.isTop === undefined) { + updates.isTop = false; + needsUpdate = true; + console.log(`为文章 ${post._id} 添加置顶状态`); + } + + if (post.updatedAt === undefined) { + updates.updatedAt = post.createdAt || new Date(); + needsUpdate = true; + console.log(`为文章 ${post._id} 添加更新时间`); + } + + // 更新数据库 + if (needsUpdate) { + await Post.findByIdAndUpdate(post._id, updates); + fixedCount++; + } + } + + console.log(`\n修复完成!共修复了 ${fixedCount} 篇文章`); + + // 显示修复后的统计信息 + const totalPosts = await Post.countDocuments(); + const publishedPosts = await Post.countDocuments({ isPublished: true }); + const topPosts = await Post.countDocuments({ isTop: true }); + + console.log('\n数据库统计信息:'); + console.log(`- 总文章数: ${totalPosts}`); + console.log(`- 已发布: ${publishedPosts}`); + console.log(`- 置顶文章: ${topPosts}`); + + mongoose.connection.close(); + } catch (error) { + console.error('修复数据库失败:', error); + mongoose.connection.close(); + } +} \ No newline at end of file diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..4197e28 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,16 @@ +module.exports = { + ensureAuthenticated: (req, res, next) => { + if (req.session && req.session.user) { + return next(); + } + console.log( '请先登录'); + res.redirect('/login'); + }, + ensureAdmin: (req, res, next) => { + if ( req.session.user.username === 'admin') { + return next(); + } + console.log( '您没有管理员权限'); + res.redirect('/'); + } +}; diff --git a/models/Category.js b/models/Category.js new file mode 100644 index 0000000..99f7da1 --- /dev/null +++ b/models/Category.js @@ -0,0 +1,9 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const CategorySchema = new Schema({ + name: { type: String, required: true, unique: true }, + createdAt: { type: Date, default: Date.now } +}); + +module.exports = mongoose.model('Category', CategorySchema); \ No newline at end of file diff --git a/models/Comment.js b/models/Comment.js new file mode 100644 index 0000000..9ec57d6 --- /dev/null +++ b/models/Comment.js @@ -0,0 +1,11 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const CommentSchema = new Schema({ + content: { type: String, required: true }, + author: { type: Schema.Types.ObjectId, ref: 'User', required: true }, + post: { type: Schema.Types.ObjectId, ref: 'Post', required: true }, + createdAt: { type: Date, default: Date.now } +}); + +module.exports = mongoose.model('Comment', CommentSchema); \ No newline at end of file diff --git a/models/Link.js b/models/Link.js new file mode 100644 index 0000000..c9f6eef --- /dev/null +++ b/models/Link.js @@ -0,0 +1,12 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const LinkSchema = new Schema({ + name: { type: String, required: true }, + url: { type: String, required: true }, + description: { type: String, default: '' }, + order: { type: Number, default: 0 }, + createdAt: { type: Date, default: Date.now } +}); + +module.exports = mongoose.model('Link', LinkSchema); \ No newline at end of file diff --git a/models/Post.js b/models/Post.js new file mode 100644 index 0000000..c76b75e --- /dev/null +++ b/models/Post.js @@ -0,0 +1,49 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const PostSchema = new Schema({ + title: { + type: String, + required: true, + default: '无标题' + }, + content: { + type: String, + required: true, + default: '' + }, + author: { + type: String, + required: true, + default: '未知作者' + }, + category: { + type: String, + required: true, + default: '未分类' + }, + isPublished: { + type: Boolean, + default: true + }, + isTop: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}); + +// 更新时自动设置updatedAt +PostSchema.pre('save', function(next) { + this.updatedAt = new Date(); + next(); +}); + +module.exports = mongoose.model('Post', PostSchema); diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..44ca2f1 --- /dev/null +++ b/models/User.js @@ -0,0 +1,13 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const UserSchema = new Schema({ + username: { type: String, required: true, unique: true }, + password: { type: String, required: true }, + role: { type: String, enum: ['admin', 'user'], default: 'user' }, + status: { type: String, enum: ['active', 'frozen'], default: 'active' }, + createdAt: { type: Date, default: Date.now }, + favorites: [{ type: Schema.Types.ObjectId, ref: 'Post' }] +}); + +module.exports = mongoose.model('User', UserSchema); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8feb272 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1909 @@ +{ + "name": "chenlilong_blog", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chenlilong_blog", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.2", + "connect-mongo": "^5.0.0", + "cookie-parser": "^1.4.6", + "ejs": "^3.1.9", + "express": "^4.18.2", + "express-session": "^1.17.3", + "mongoose": "^7.6.1" + }, + "devDependencies": { + "nodemon": "^3.1.10" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/node": { + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=16.20.1" + } + }, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/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/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/connect-mongo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-5.1.0.tgz", + "integrity": "sha512-xT0vxQLqyqoUTxPLzlP9a/u+vir0zNkhiy9uAdHjSCcUUf7TS5b55Icw8lVyYFxfemP3Mf9gdwUOgeF3cxCAhw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "kruptein": "^3.0.0" + }, + "engines": { + "node": ">=12.9.0" + }, + "peerDependencies": { + "express-session": "^1.17.1", + "mongodb": ">= 5.1.0 < 7" + } + }, + "node_modules/connect-mongo/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/connect-mongo/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/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.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/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/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/express-session": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/express/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/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "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/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/kruptein": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.8.tgz", + "integrity": "sha512-0CyalFA0Cjp3jnziMp0u1uLZW2/ouhQ0mEMfYlroBXNe86na1RwAuwBcdRAegeWZNMfQy/G5fN47g/Axjtqrfw==", + "license": "MIT", + "dependencies": { + "asn1.js": "^5.4.1" + }, + "engines": { + "node": ">8" + } + }, + "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/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "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/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.4", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.7.tgz", + "integrity": "sha512-5Bo4CrUxrPITrhMKsqUTOkXXo2CoRC5tXxVQhnddCzqDMwRXfyStrxj1oY865g8gaekSBhxAeNkYyUSJvGm9Hw==", + "license": "MIT", + "dependencies": { + "bson": "^5.5.0", + "kareem": "2.5.1", + "mongodb": "5.9.2", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "license": "Apache-2.0", + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongoose/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/mongoose/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/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/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/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-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/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "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/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==", + "license": "MIT" + }, + "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/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, + "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": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "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/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "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" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5253a37 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "chenlilong_blog", + "version": "1.0.0", + "description": "通用基础内容管理系统(CMS)- Node.js + MongoDB", + "main": "app.js", + "scripts": { + "start": "node app.js", + "dev": "nodemon app.js" + }, + "dependencies": { + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.2", + "connect-mongo": "^5.0.0", + "cookie-parser": "^1.4.6", + "ejs": "^3.1.9", + "express": "^4.18.2", + "express-session": "^1.17.3", + "mongoose": "^7.6.1" + }, + "devDependencies": { + "nodemon": "^3.1.10" + }, + "author": "Chen Lilong", + "license": "MIT" +} diff --git a/routes/admin/index.js b/routes/admin/index.js new file mode 100644 index 0000000..144d352 --- /dev/null +++ b/routes/admin/index.js @@ -0,0 +1,356 @@ +const express = require('express'); +const router = express.Router(); +const { ensureAuthenticated, ensureAdmin } = require('../../middleware/auth'); +const User = require('../../models/User'); +const Category = require('../../models/Category'); +const Post = require('../../models/Post'); +const Comment = require('../../models/Comment'); +const Link = require('../../models/Link'); +const { paginateQuery } = require('../../utils/pagination'); + + +// 后台管理首页 +router.get('/', ensureAuthenticated, ensureAdmin, (req, res) => { + res.render('admin/index', { + user: req.user, + template: 'index' + }); +}); + +// 用户管理路由 +router.get('/users', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + // 获取分页参数 + const page = parseInt(req.query.page) || 1; + const limit = 10; // 每页显示10个用户 + + // 获取用户(带分页) + const usersResult = await paginateQuery( + User, + {}, + { sort: { createdAt: -1 } }, + page, + limit + ); + + res.render('admin/index', { + user: req.user, + template: 'users/list', + users: usersResult.data, + pagination: usersResult.pagination, + baseUrl: '/admin/users', + query: {} + }); + } catch (error) { + console.error('获取用户列表失败:', error); + res.status(500).send('服务器错误'); + } +}); + +// 分类管理路由 +router.get('/categories', ensureAuthenticated, ensureAdmin, async (req, res) => { + const categories = await Category.find().sort({ order: 1 }); + res.render('admin/index', { + user: req.user, + template: 'categories/list', + categories + }); +}); + +// 获取分类列表API - 供前端使用 +router.get('/api/categories', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const categories = await Category.find().sort({ order: 1 }); + res.json(categories); + } catch (err) { + console.error(err); + res.status(500).json({ error: '获取分类失败' }); + } +}); + + + +// 文章管理路由 +router.get('/posts', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + // 获取分页参数 + const page = parseInt(req.query.page) || 1; + const limit = 10; // 每页显示10篇文章 + // 获取文章(带分页) + const postsResult = await paginateQuery( + Post, + {}, + { + populate: 'category author', + sort: { createdAt: -1 } + }, + page, + limit + ); + res.render('admin/index', { + user: req.user, + template: 'posts/list', + posts: postsResult.data, + pagination: postsResult.pagination, + baseUrl: '/admin/posts', + query: {} + }); + } catch (error) { + console.error('获取文章列表失败:', error); + res.status(500).send('服务器错误'); + } +}); + +// 创建文章 - POST +router.post('/posts', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const { title, content, category, author, isPublished, isTop } = req.body; + const newPost = new Post({ + title, + content, + category, + author, + isPublished: isPublished === 'on' || isPublished === true, + isTop: isTop === 'on' || isTop === true + }); + await newPost.save(); + res.json({ success: true, post: newPost }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '创建文章失败' }); + } +}); + +// 更新文章 - PUT +router.put('/posts/:id', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const { title, content, category, author, isPublished, isTop } = req.body; + const updatedPost = await Post.findByIdAndUpdate( + req.params.id, + { + title, + content, + category, + author, + isPublished: isPublished === 'on' || isPublished === true, + isTop: isTop === 'on' || isTop === true, + updatedAt: Date.now() + }, + { new: true } + ); + + if (!updatedPost) { + return res.status(404).json({ success: false, message: '文章未找到' }); + } + + res.json({ success: true, post: updatedPost }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '更新文章失败' }); + } +}); + +// 删除文章 - DELETE +router.delete('/posts/:id', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const deletedPost = await Post.findByIdAndDelete(req.params.id); + + if (!deletedPost) { + return res.status(404).json({ success: false, message: '文章未找到' }); + } + + // 同时删除相关评论 + await Comment.deleteMany({ post: req.params.id }); + + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '删除文章失败' }); + } +}); + +// 设置/取消置顶 - PUT +router.put('/posts/:id/top', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const { isTop } = req.body; + + const updatedPost = await Post.findByIdAndUpdate( + req.params.id, + { isTop }, + { new: true } + ); + + if (!updatedPost) { + return res.status(404).json({ success: false, message: '文章未找到' }); + } + + res.json({ success: true, post: updatedPost }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '操作失败' }); + } +}); +// 发布/取消发布 - PUT +router.put('/posts/:id/publish', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const { isPublished } = req.body; + + const updatedPost = await Post.findByIdAndUpdate( + req.params.id, + { isPublished }, + { new: true } + ); + + if (!updatedPost) { + return res.status(404).json({ success: false, message: '文章未找到' }); + } + + res.json({ success: true, post: updatedPost }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '操作失败' }); + } +}); + +// 获取单篇文章详情 - GET +router.get('/posts/:id', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const post = await Post.findById(req.params.id); + if (!post) { + return res.status(404).json({ success: false, message: '文章未找到' }); + } + res.json(post); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '获取文章信息失败' }); + } +}); + +// 评论管理路由 +router.get('/comments', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + // 获取分页参数 + const page = parseInt(req.query.page) || 1; + const limit = 10; // 每页显示10条评论 + // 获取评论(带分页) + const commentsResult = await paginateQuery( + Comment, + {}, + { + populate: 'author post', + sort: { createdAt: -1 } + }, + page, + limit + ); + + res.render('admin/index', { + user: req.user, + template: 'comments/list', + comments: commentsResult.data, + pagination: commentsResult.pagination, + baseUrl: '/admin/comments', + query: {} + }); + } catch (error) { + console.error('获取评论列表失败:', error); + res.status(500).send('服务器错误'); + } +}); + +// 友情链接路由 +router.get('/links', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + // 获取分页参数 + const page = parseInt(req.query.page) || 1; + const limit = 10; // 每页显示10条链接 + + // 获取友情链接(带分页) + const linksResult = await paginateQuery( + Link, + {}, + { + sort: { order: 1 } + }, + page, + limit + ); + + res.render('admin/index', { + user: req.user, + template: 'links/list', + links: linksResult.data, + pagination: linksResult.pagination, + baseUrl: '/admin/links', + query: {} + }); + } catch (error) { + console.error('获取友情链接列表失败:', error); + res.status(500).send('服务器错误'); + } +}); + +// 添加友情链接 - POST +router.post('/links', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const { name, url, description, order } = req.body; + + const newLink = new Link({ + name, + url, + description: description || '', + order: order || 0 + }); + + await newLink.save(); + res.json({ success: true, link: newLink }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '添加友情链接失败' }); + } +}); + +// 更新友情链接 - PUT +router.put('/links/:id', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const { name, url, description, order } = req.body; + + const updatedLink = await Link.findByIdAndUpdate( + req.params.id, + { + name, + url, + description: description || '', + order: order || 0 + }, + { new: true } + ); + + if (!updatedLink) { + return res.status(404).json({ success: false, message: '友情链接未找到' }); + } + + res.json({ success: true, link: updatedLink }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '更新友情链接失败' }); + } +}); + +// 删除友情链接 - DELETE +router.delete('/links/:id', ensureAuthenticated, ensureAdmin, async (req, res) => { + try { + const deletedLink = await Link.findByIdAndDelete(req.params.id); + + if (!deletedLink) { + return res.status(404).json({ success: false, message: '友情链接未找到' }); + } + + res.json({ success: true }); + } catch (err) { + console.error(err); + res.status(500).json({ success: false, message: '删除友情链接失败' }); + } +}); + +module.exports = router; diff --git a/routes/user/auth.js b/routes/user/auth.js new file mode 100644 index 0000000..2c60b14 --- /dev/null +++ b/routes/user/auth.js @@ -0,0 +1,144 @@ +const express = require('express'); +const router = express.Router(); +const authController = require('../../controllers/user/auth'); +const Post = require('../../models/Post'); +const Comment = require('../../models/Comment'); +const User = require('../../models/User'); + + +// 文章详情页 +// 文章详情页 +router.get('/posts/:id', async (req, res) => { + try { + const post = await Post.findOne({ _id: req.params.id, isPublished: true }); + if (!post) { + return res.status(404).render('user/info', { + post: null, + user: req.session.user || null, + message: '文章不存在或已被删除' + }); + } + res.render('user/info', { + post: post, // 确保传递的是查询到的文章 + user: req.session.user || null + }); + } catch (err) { + console.error('获取文章详情出错:', err); + res.status(500).render('user/info', { + post: null, + user: req.session.user || null, + message: '服务器错误,请稍后再试' + }); + } +}); + +// 注册页面 +router.get('/register', (req, res) => { + res.render('user/register', { error: null }); +}); +// 注册提交 +router.post('/register', authController.register); + +// 登录页面 +router.get('/login', (req, res) => { + res.render('user/login', { error: null }); +}); +// 登录提交 +router.post('/login', authController.login); + +// 登出 +router.get('/logout', authController.logout); + +// 获取用户资料(包括收藏列表) +router.get('/user/profile', async (req, res) => { + if (!req.session.user) { + return res.status(401).json({ success: false, message: '请先登录' }); + } + try { + const user = await User.findById(req.session.user._id).populate('favorites'); + res.json({ + success: true, + user: { + _id: user._id, + username: user.username, + role: user.role + }, + favorites: user.favorites || [] + }); + } catch (err) { + console.error('获取用户资料失败:', err); + res.status(500).json({ success: false, message: '获取用户资料失败' }); + } +}); + +// 收藏文章 +router.post('/posts/:id/favorite', async (req, res) => { + if (!req.session.user) return res.status(401).json({ success: false, message: '请先登录' }); + try { + await User.findByIdAndUpdate(req.session.user._id, { $addToSet: { favorites: req.params.id } }); + res.json({ success: true }); + } catch (err) { + res.status(500).json({ success: false, message: '收藏失败' }); + } +}); +// 取消收藏 +router.post('/posts/:id/unfavorite', async (req, res) => { + if (!req.session.user) return res.status(401).json({ success: false, message: '请先登录' }); + try { + await User.findByIdAndUpdate(req.session.user._id, { $pull: { favorites: req.params.id } }); + res.json({ success: true }); + } catch (err) { + res.status(500).json({ success: false, message: '取消收藏失败' }); + } +}); +// 获取评论列表 +router.get('/posts/:id/comments', async (req, res) => { + try { + const comments = await Comment.find({ post: req.params.id }).populate('author', 'username').sort({ createdAt: -1 }); + res.json({ success: true, comments }); + } catch (err) { + res.status(500).json({ success: false, message: '获取评论失败' }); + } +}); +// 发表评论 +router.post('/posts/:id/comments', async (req, res) => { + if (!req.session.user) return res.status(401).json({ success: false, message: '请先登录' }); + const { content } = req.body; + if (!content || !content.trim()) return res.status(400).json({ success: false, message: '评论内容不能为空' }); + try { + const comment = new Comment({ + content, + author: req.session.user._id, + post: req.params.id + }); + await comment.save(); + res.json({ success: true, comment }); + } catch (err) { + res.status(500).json({ success: false, message: '发表评论失败' }); + } +}); + +// 用户个人中心页面 +router.get('/userInfo', async (req, res) => { + if (!req.session.user) { + return res.redirect('/login'); + } + try { + const user = await User.findById(req.session.user._id).populate('favorites'); + // 获取用户所有评论 + const comments = await Comment.find({ author: req.session.user._id }).populate('post', 'title'); + res.render('user/userInfo', { + user, + favorites: user.favorites || [], + comments: comments || [] + }); + } catch (err) { + console.error('获取个人中心信息失败:', err); + res.status(500).render('user/userInfo', { user: null, favorites: [], comments: [], error: '服务器错误' }); + } +}); + +// 修改密码 +router.post('/user/changePassword', authController.changePassword); + +module.exports = router; diff --git a/test_links.js b/test_links.js new file mode 100644 index 0000000..4c5f7ce --- /dev/null +++ b/test_links.js @@ -0,0 +1,48 @@ +const fetch = require('node-fetch'); + +// 测试友情链接API +async function testLinksAPI() { + const baseURL = 'http://localhost:3001'; + + console.log('开始测试友情链接API...\n'); + + try { + // 测试添加友情链接 + console.log('1. 测试添加友情链接...'); + const addResponse = await fetch(`${baseURL}/admin/links`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: '测试链接', + url: 'https://example.com', + description: '这是一个测试链接', + order: 1 + }) + }); + + if (addResponse.ok) { + const addResult = await addResponse.json(); + console.log('✅ 添加成功:', addResult); + } else { + console.log('❌ 添加失败:', addResponse.status, addResponse.statusText); + } + + // 测试获取友情链接列表 + console.log('\n2. 测试获取友情链接列表...'); + const getResponse = await fetch(`${baseURL}/admin/links`); + + if (getResponse.ok) { + console.log('✅ 获取成功'); + } else { + console.log('❌ 获取失败:', getResponse.status, getResponse.statusText); + } + + } catch (error) { + console.error('❌ 测试失败:', error.message); + } +} + +// 运行测试 +testLinksAPI(); \ No newline at end of file diff --git a/test_pagination.js b/test_pagination.js new file mode 100644 index 0000000..386d73b --- /dev/null +++ b/test_pagination.js @@ -0,0 +1,55 @@ +const { paginateQuery } = require('./utils/pagination'); +const mongoose = require('mongoose'); +const Post = require('./models/Post'); + +// 连接数据库 +mongoose.connect('mongodb://127.0.0.1:27017/blog', { + useNewUrlParser: true, + useUnifiedTopology: true +}) +.then(() => { + console.log('数据库连接成功'); + testPagination(); +}) +.catch(err => { + console.error('数据库连接失败:', err); + process.exit(1); +}); + +async function testPagination() { + try { + console.log('开始测试分页功能...\n'); + + // 测试分页查询 + const page = 1; + const limit = 5; + + console.log(`测试参数: 页码=${page}, 每页数量=${limit}`); + + const result = await paginateQuery( + Post, + {}, + { sort: { createdAt: -1 } }, + page, + limit + ); + + console.log('分页结果:'); + console.log('- 当前页:', result.pagination.currentPage); + console.log('- 总页数:', result.pagination.totalPages); + console.log('- 总记录数:', result.pagination.total); + console.log('- 当前页记录数:', result.data.length); + console.log('- 是否有下一页:', result.pagination.hasNext); + console.log('- 是否有上一页:', result.pagination.hasPrev); + + console.log('\n文章列表:'); + result.data.forEach((post, index) => { + console.log(`${index + 1}. ${post.title} (${post.author})`); + }); + + mongoose.connection.close(); + } catch (error) { + console.error('测试失败:', error); + mongoose.connection.close(); + } +} \ No newline at end of file diff --git a/test_pagination_component.js b/test_pagination_component.js new file mode 100644 index 0000000..d951983 --- /dev/null +++ b/test_pagination_component.js @@ -0,0 +1,71 @@ +const ejs = require('ejs'); +const path = require('path'); +const fs = require('fs'); + +// 测试分页组件是否能正确渲染 +function testPaginationComponent() { + console.log('测试分页组件...\n'); + + try { + // 读取分页组件文件 + const paginationPath = path.join(__dirname, 'views', 'components', 'pagination.ejs'); + + if (fs.existsSync(paginationPath)) { + console.log('✅ 分页组件文件存在'); + + // 读取组件内容 + const componentContent = fs.readFileSync(paginationPath, 'utf8'); + console.log('✅ 分页组件文件读取成功'); + + // 测试数据 + const testData = { + currentPage: 3, + totalPages: 10, + baseUrl: '/admin/posts', + query: { search: 'test' } + }; + + // 尝试渲染组件 + const rendered = ejs.render(componentContent, testData); + console.log('✅ 分页组件渲染成功'); + + // 检查渲染结果 + if (rendered.includes('pagination')) { + console.log('✅ 分页组件包含正确的HTML结构'); + } + + console.log('\n分页组件测试通过!'); + + } else { + console.log('❌ 分页组件文件不存在'); + } + + } catch (error) { + console.error('❌ 测试失败:', error.message); + } +} + +// 测试路径解析 +function testPathResolution() { + console.log('\n测试路径解析...\n'); + + const paths = [ + 'views/components/pagination.ejs', + 'views/admin/users/list.ejs', + 'views/admin/posts/list.ejs', + 'views/admin/comments/list.ejs' + ]; + + paths.forEach(filePath => { + const fullPath = path.join(__dirname, filePath); + if (fs.existsSync(fullPath)) { + console.log(`✅ ${filePath} 存在`); + } else { + console.log(`❌ ${filePath} 不存在`); + } + }); +} + +// 运行测试 +testPaginationComponent(); +testPathResolution(); \ No newline at end of file diff --git a/uploads/app.js b/uploads/app.js new file mode 100644 index 0000000..e69de29 diff --git a/uploads/config.js b/uploads/config.js new file mode 100644 index 0000000..e69de29 diff --git a/utils/pagination.js b/utils/pagination.js new file mode 100644 index 0000000..1a1e9bc --- /dev/null +++ b/utils/pagination.js @@ -0,0 +1,81 @@ +/** + * 分页工具函数 + */ + +/** + * 计算分页信息 + * @param {number} total - 总记录数 + * @param {number} page - 当前页码 + * @param {number} limit - 每页记录数 + * @returns {object} 分页信息对象 + */ +function getPaginationInfo(total, page = 1, limit = 10) { + const totalPages = Math.ceil(total / limit); + const currentPage = Math.max(1, Math.min(page, totalPages)); + const skip = (currentPage - 1) * limit; + + return { + currentPage, + totalPages, + limit, + skip, + total, + hasNext: currentPage < totalPages, + hasPrev: currentPage > 1 + }; +} + +/** + * 为MongoDB查询添加分页 + * @param {object} query - MongoDB查询对象 + * @param {number} page - 当前页码 + * @param {number} limit - 每页记录数 + * @returns {object} 包含分页信息的查询对象 + */ +function addPaginationToQuery(query, page = 1, limit = 10) { + const paginationInfo = getPaginationInfo(query.total || 0, page, limit); + + return { + ...query, + skip: paginationInfo.skip, + limit: paginationInfo.limit, + pagination: paginationInfo + }; +} + +/** + * 执行分页查询 + * @param {object} model - Mongoose模型 + * @param {object} filter - 查询条件 + * @param {object} options - 查询选项 + * @param {number} page - 当前页码 + * @param {number} limit - 每页记录数 + * @returns {Promise} 包含数据和分页信息的对象 + */ +async function paginateQuery(model, filter = {}, options = {}, page = 1, limit = 10) { + const paginationInfo = getPaginationInfo(0, page, limit); + + // 获取总记录数 + const total = await model.countDocuments(filter); + + // 更新分页信息 + const updatedPagination = getPaginationInfo(total, page, limit); + + // 执行查询 + const data = await model.find(filter, null, { + ...options, + skip: updatedPagination.skip, + limit: updatedPagination.limit + }); + + return { + data, + pagination: updatedPagination + }; +} + +module.exports = { + getPaginationInfo, + addPaginationToQuery, + paginateQuery +}; \ No newline at end of file diff --git a/views/admin/categories/list.ejs b/views/admin/categories/list.ejs new file mode 100644 index 0000000..f525804 --- /dev/null +++ b/views/admin/categories/list.ejs @@ -0,0 +1,143 @@ +
+
+
分类管理
+ +
+
+
+ + + + + + + + + + + + + <% categories.forEach(category => { %> + + + + + + + + + <% }); %> + +
排序分类名称别名文章数状态操作
<%= category.order %> + <% if (category.isTop) { %> + 置顶 + <% } %> + <%= category.name %> + <%= category.slug %><%= category.postCount || 0 %> + <% if (category.isActive) { %> + 启用 + <% } else { %> + 禁用 + <% } %> + + + + <% if (category.isTop) { %> + + <% } else { %> + + <% } %> +
+
+
+
+ + + + + diff --git a/views/admin/comments/list.ejs b/views/admin/comments/list.ejs new file mode 100644 index 0000000..4334fd1 --- /dev/null +++ b/views/admin/comments/list.ejs @@ -0,0 +1,67 @@ +
+
+
评论管理
+
+
+
+ + + + + + + + + + + + <% comments.forEach(comment => { %> + + + + + + + + <% }); %> + +
评论内容用户文章评论时间操作
<%= comment.content %><%= comment.author ? comment.author.username : '未知用户' %><%= comment.post ? comment.post.title : '未知文章' %><%= new Date(comment.createdAt).toLocaleString() %> + +
+
+ + + <%- include('../../components/pagination', { + currentPage: pagination.currentPage, + totalPages: pagination.totalPages, + baseUrl: baseUrl, + query: query + }) %> +
+
+ + diff --git a/views/admin/index.ejs b/views/admin/index.ejs new file mode 100644 index 0000000..c269c99 --- /dev/null +++ b/views/admin/index.ejs @@ -0,0 +1,163 @@ + + + + + + 博客系统 - 后台管理 + + + + + + +
+ <% if (template !== 'index') { %> + <%- include(`./${template}`) %> + <% } else { %> +
+
+
+

欢迎来到博客后台管理系统!

+

在这里你可以高效地管理用户、文章、评论、分类和友情链接。

+
+
+ +
+ <% } %> +
+ + + + + diff --git a/views/admin/links/list.ejs b/views/admin/links/list.ejs new file mode 100644 index 0000000..4c2273a --- /dev/null +++ b/views/admin/links/list.ejs @@ -0,0 +1,141 @@ +
+
+
友情链接管理
+ +
+
+
+ + + + + + + + + + + + <% links.forEach(link => { %> + + + + + + + + <% }); %> + +
排序网站名称URL描述操作
<%= link.order || 0 %><%= link.name %><%= link.url %><%= link.description || '' %> + + +
+
+ + + <%- include('../../components/pagination', { + currentPage: pagination.currentPage, + totalPages: pagination.totalPages, + baseUrl: baseUrl, + query: query + }) %> +
+
+ + + + + diff --git a/views/admin/posts/list.ejs b/views/admin/posts/list.ejs new file mode 100644 index 0000000..851722a --- /dev/null +++ b/views/admin/posts/list.ejs @@ -0,0 +1,380 @@ +
+
+
文章管理
+
+ +
+
+
+
+ + + + + + + + + + + + <% posts.forEach(post => { %> + + + + + + + + <% }); %> + +
标题分类作者发布时间操作
+ <% if (post.isTop) { %> + 置顶 + <% } %> + <%= post.title %> + <%= post.category %><%= post.author %><%= new Date(post.createdAt).toLocaleString() %> + + + <% if (post.isTop) { %> + + <% } else { %> + + <% } %> + <% if (post.isPublished) { %> + + <% } else { %> + + <% } %> +
+
+ + <%- include('../../components/pagination', { + currentPage: pagination.currentPage, + totalPages: pagination.totalPages, + baseUrl: baseUrl, + query: query + }) %> +
+
+ + + + + + + + diff --git a/views/admin/users/list.ejs b/views/admin/users/list.ejs new file mode 100644 index 0000000..5789e4d --- /dev/null +++ b/views/admin/users/list.ejs @@ -0,0 +1,102 @@ +
+
+
用户列表
+
+ +
+
+
+
+ + + + + + + + + + + + + <% users.forEach(user => { %> + + + + + + + + <% }); %> + +
ID用户名邮箱注册时间操作
<%= user._id %><%= user.username %><%= user.email %><%= new Date(user.createdAt).toLocaleString() %> + +
+
+ + + <%- include('../../components/pagination', { + currentPage: pagination.currentPage, + totalPages: pagination.totalPages, + baseUrl: baseUrl, + query: query + }) %> +
+
+ + diff --git a/views/components/pagination.ejs b/views/components/pagination.ejs new file mode 100644 index 0000000..3fe4a31 --- /dev/null +++ b/views/components/pagination.ejs @@ -0,0 +1,139 @@ +<% +// 分页组件参数 +// currentPage: 当前页码 +// totalPages: 总页数 +// baseUrl: 基础URL +// query: 查询参数对象 +%> + + +
+ + + +
+ 第 <%= currentPage %> 页,共 <%= totalPages %> 页 +
+
+ + diff --git a/views/user/index.ejs b/views/user/index.ejs new file mode 100644 index 0000000..10adf8c --- /dev/null +++ b/views/user/index.ejs @@ -0,0 +1,304 @@ + + + + + + 我的博客 - 首页 + + + +<% +// 安全地获取变量,提供默认值 +var posts = locals.posts || []; +var links = locals.links || []; +var user = locals.user || null; +%> + +
+

欢迎来到博客系统

+ <% if (user) { %> +
+ 欢迎,<%= user.username %>! + 个人中心 + 退出登录 +
+ <% } else { %> + + <% } %> +
+
+ +
+

最新文章

+ <% if (posts && posts.length > 0) { %> + <% posts.forEach(function(post) { %> +
+ <%= post.title || '无标题' %> + +
+
+ <% + var content = post.content || ''; + var excerpt = content.length > 150 ? content.substring(0, 150) + '...' : content; + %> + <%= excerpt %> +
+
+ 阅读全文 → +
+ <% }); %> + <% } else { %> +
+

暂无文章

+
+ <% } %> +
+ + + +
+ + diff --git a/views/user/info.ejs b/views/user/info.ejs new file mode 100644 index 0000000..1420176 --- /dev/null +++ b/views/user/info.ejs @@ -0,0 +1,348 @@ + + + + + + 博客详情 + + + + + +
+
+

<%= post.title %>

+ +
+
+ <%= post.content %> +
+ +
+ <% if (user) { %> + + <% } %> + ← 返回首页 +
+ +
+

评论区

+
加载中...
+ + <% if (user) { %> +
+ + +
+ <% } else { %> + + <% } %> +
+ + +
+ + diff --git a/views/user/login.ejs b/views/user/login.ejs new file mode 100644 index 0000000..e2e15e3 --- /dev/null +++ b/views/user/login.ejs @@ -0,0 +1,138 @@ + + + + + + 内容管理系统 - 用户登录 + + + +
+

内容管理系统登录

+ <% if (error) { %> +
<%= error %>
+ <% } %> +
+
+ + +
+
+ + +
+ +
+ +
+ + diff --git a/views/user/register.ejs b/views/user/register.ejs new file mode 100644 index 0000000..89748dc --- /dev/null +++ b/views/user/register.ejs @@ -0,0 +1,146 @@ + + + + + + 内容管理系统 - 用户注册 + + + +
+

创建新账户

+ <% if (error) { %> +
<%= error %>
+ <% } %> +
+
+ + +
+
+ + +
建议使用8位以上包含字母和数字的组合
+
+ +
+ +
+ + diff --git a/views/user/userInfo.ejs b/views/user/userInfo.ejs new file mode 100644 index 0000000..024ff1d --- /dev/null +++ b/views/user/userInfo.ejs @@ -0,0 +1,122 @@ + + + + + 个人中心 + + + +
+

个人中心

+ +
+
修改密码
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ +
+
我的收藏
+ <% if (favorites && favorites.length > 0) { %> +
    + <% favorites.forEach(function(post) { %> +
  • + <%= post.title %> +
    + +
    +
  • + <% }); %> +
+ <% } else { %> +
暂无收藏的博文
+ <% } %> +
+ +
+
我的评论
+ <% if (comments && comments.length > 0) { %> +
    + <% comments.forEach(function(comment) { %> +
  • + <%= comment.content %> + <%= comment.post.title %> • <%= new Date(comment.createdAt).toLocaleString('zh-CN') %> +
  • + <% }); %> +
+ <% } else { %> +
暂无评论
+ <% } %> +
+
+ + + diff --git a/初始化数据.bat b/初始化数据.bat new file mode 100644 index 0000000..3203483 --- /dev/null +++ b/初始化数据.bat @@ -0,0 +1,4 @@ +@echo off +echo 正在初始化数据库... +node db/init.js +pause \ No newline at end of file diff --git a/项目启动.bat b/项目启动.bat new file mode 100644 index 0000000..5cc2258 --- /dev/null +++ b/项目启动.bat @@ -0,0 +1,4 @@ +@echo off +echo 正在启动博客系统... +node app.js +pause