2025-06-13 06:04:40 +00:00
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const mysql = require('mysql2/promise');
|
|
|
|
|
|
const cors = require('cors');
|
|
|
|
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
|
|
const jwt = require('jsonwebtoken');
|
|
|
|
|
|
require('dotenv').config();
|
|
|
|
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
|
const PORT = process.env.PORT || 5000;
|
|
|
|
|
|
const JWT_SECRET = 'weiMonkey2024_secret_key';
|
|
|
|
|
|
|
|
|
|
|
|
// 中间件
|
|
|
|
|
|
app.use(cors());
|
|
|
|
|
|
app.use(express.json());
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库连接配置
|
|
|
|
|
|
const dbConfig = {
|
|
|
|
|
|
host: 'localhost',
|
|
|
|
|
|
port: 3306,
|
|
|
|
|
|
user: 'root',
|
|
|
|
|
|
password: '123456',
|
|
|
|
|
|
database: 'myuser'
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 创建数据库连接池
|
|
|
|
|
|
const pool = mysql.createPool(dbConfig);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化数据库表
|
|
|
|
|
|
async function initDatabase() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 创建用户表
|
|
|
|
|
|
await connection.execute(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
|
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
|
username VARCHAR(64) NOT NULL UNIQUE,
|
|
|
|
|
|
password VARCHAR(255) NOT NULL,
|
|
|
|
|
|
is_admin BOOLEAN DEFAULT FALSE,
|
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
|
)
|
|
|
|
|
|
`);
|
|
|
|
|
|
|
2025-06-13 06:04:40 +00:00
|
|
|
|
// 创建待办事项表
|
|
|
|
|
|
await connection.execute(`
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS todos (
|
|
|
|
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
|
title VARCHAR(255) NOT NULL,
|
2025-06-13 07:31:12 +00:00
|
|
|
|
description TEXT,
|
2025-06-13 06:04:40 +00:00
|
|
|
|
priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
|
|
|
|
|
|
completed BOOLEAN DEFAULT FALSE,
|
|
|
|
|
|
suspended BOOLEAN DEFAULT FALSE,
|
|
|
|
|
|
date DATE NOT NULL,
|
|
|
|
|
|
suspended_date DATE NULL,
|
2025-06-13 07:31:12 +00:00
|
|
|
|
user_id INT NOT NULL,
|
2025-06-13 06:04:40 +00:00
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
2025-06-13 07:31:12 +00:00
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
2025-06-13 06:04:40 +00:00
|
|
|
|
)
|
|
|
|
|
|
`);
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 添加user_id字段(如果表已存在但没有该字段)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.execute(`
|
|
|
|
|
|
ALTER TABLE todos
|
|
|
|
|
|
ADD COLUMN user_id INT NOT NULL,
|
|
|
|
|
|
ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
|
|
|
|
`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 字段可能已存在,忽略错误
|
|
|
|
|
|
console.log('User_id column may already exist');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-13 06:04:40 +00:00
|
|
|
|
// 添加suspended字段(如果表已存在但没有该字段)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.execute(`
|
|
|
|
|
|
ALTER TABLE todos
|
|
|
|
|
|
ADD COLUMN suspended BOOLEAN DEFAULT FALSE,
|
|
|
|
|
|
ADD COLUMN suspended_date DATE NULL
|
|
|
|
|
|
`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 字段可能已存在,忽略错误
|
|
|
|
|
|
console.log('Suspended columns may already exist');
|
|
|
|
|
|
}
|
2025-06-13 07:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
// 添加description字段(如果表已存在但没有该字段)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.execute(`
|
|
|
|
|
|
ALTER TABLE todos
|
|
|
|
|
|
ADD COLUMN description TEXT
|
|
|
|
|
|
`);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 字段可能已存在,忽略错误
|
|
|
|
|
|
console.log('Description column may already exist');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建默认管理员用户
|
|
|
|
|
|
const adminPassword = await bcrypt.hash('weiMonkey2024', 10);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await connection.execute(`
|
|
|
|
|
|
INSERT INTO users (username, password, is_admin)
|
|
|
|
|
|
VALUES ('admin', ?, TRUE)
|
|
|
|
|
|
`, [adminPassword]);
|
|
|
|
|
|
console.log('默认管理员用户创建成功');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 用户可能已存在
|
|
|
|
|
|
console.log('默认管理员用户可能已存在');
|
|
|
|
|
|
}
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
console.log('数据库表初始化成功');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('数据库初始化失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证密码的中间件
|
|
|
|
|
|
const authenticatePassword = (req, res, next) => {
|
|
|
|
|
|
const { password } = req.body;
|
|
|
|
|
|
const correctPassword = 'weiMonkey2024';
|
|
|
|
|
|
|
|
|
|
|
|
if (password !== correctPassword) {
|
|
|
|
|
|
return res.status(401).json({ error: '密码错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 验证token的中间件
|
|
|
|
|
|
const authenticateToken = (req, res, next) => {
|
|
|
|
|
|
const authHeader = req.headers['authorization'];
|
|
|
|
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
return res.status(401).json({ error: '需要认证' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
jwt.verify(token, JWT_SECRET, (err, user) => {
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
return res.status(403).json({ error: '无效的token' });
|
|
|
|
|
|
}
|
|
|
|
|
|
req.user = user;
|
|
|
|
|
|
next();
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 验证管理员权限的中间件
|
|
|
|
|
|
const authenticateAdmin = (req, res, next) => {
|
|
|
|
|
|
if (!req.user.is_admin) {
|
|
|
|
|
|
return res.status(403).json({ error: '需要管理员权限' });
|
|
|
|
|
|
}
|
|
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 路由
|
|
|
|
|
|
|
|
|
|
|
|
// 用户登录
|
|
|
|
|
|
app.post('/api/auth/login', async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { username, password } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!username || !password) {
|
|
|
|
|
|
return res.status(400).json({ error: '用户名和密码是必需的' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
// 查找用户
|
|
|
|
|
|
const [users] = await connection.execute(
|
|
|
|
|
|
'SELECT * FROM users WHERE username = ?',
|
|
|
|
|
|
[username]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
|
|
|
|
|
|
if (users.length === 0) {
|
|
|
|
|
|
return res.status(401).json({ error: '用户名或密码错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const user = users[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 验证密码
|
|
|
|
|
|
const isValidPassword = await bcrypt.compare(password, user.password);
|
|
|
|
|
|
if (!isValidPassword) {
|
|
|
|
|
|
return res.status(401).json({ error: '用户名或密码错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成token
|
|
|
|
|
|
const token = jwt.sign(
|
|
|
|
|
|
{
|
|
|
|
|
|
userId: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
is_admin: user.is_admin
|
|
|
|
|
|
},
|
|
|
|
|
|
JWT_SECRET,
|
|
|
|
|
|
{ expiresIn: '24h' }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
token,
|
|
|
|
|
|
user: {
|
|
|
|
|
|
id: user.id,
|
|
|
|
|
|
username: user.username,
|
|
|
|
|
|
is_admin: user.is_admin
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('登录失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 管理员添加用户
|
|
|
|
|
|
app.post('/api/auth/register', authenticateToken, authenticateAdmin, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { username, password } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!username || !password) {
|
|
|
|
|
|
return res.status(400).json({ error: '用户名和密码是必需的' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (password.length < 6) {
|
|
|
|
|
|
return res.status(400).json({ error: '密码长度至少6位' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户名是否已存在
|
|
|
|
|
|
const [existingUsers] = await connection.execute(
|
|
|
|
|
|
'SELECT id FROM users WHERE username = ?',
|
|
|
|
|
|
[username]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingUsers.length > 0) {
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
return res.status(400).json({ error: '用户名已存在' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新用户
|
|
|
|
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
|
|
|
|
const [result] = await connection.execute(
|
|
|
|
|
|
'INSERT INTO users (username, password, is_admin) VALUES (?, ?, FALSE)',
|
|
|
|
|
|
[username, hashedPassword]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '用户创建成功',
|
|
|
|
|
|
userId: result.insertId
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('注册用户失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有用户(仅管理员)
|
|
|
|
|
|
app.get('/api/users', authenticateToken, authenticateAdmin, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
const [users] = await connection.execute(
|
|
|
|
|
|
'SELECT id, username, is_admin, created_at FROM users ORDER BY created_at DESC'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(users);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户列表失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-06-13 06:04:40 +00:00
|
|
|
|
// 获取历史待办事项(一周之前的)
|
|
|
|
|
|
app.get('/api/todos/history', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取一周之前的历史数据
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 管理员可以查看所有用户的数据,普通用户只能查看自己的
|
|
|
|
|
|
let query = 'SELECT * FROM todos WHERE date < ? AND user_id = ? ORDER BY date DESC, priority DESC, created_at DESC';
|
|
|
|
|
|
let params = [oneWeekAgo.toISOString().split('T')[0]];
|
|
|
|
|
|
params.push(req.user.userId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [rows] = await connection.execute(query, params);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(rows);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取历史待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取最近一周的待办事项(排除挂起的)
|
|
|
|
|
|
app.get('/api/todos', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取最近一周的日期范围
|
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 管理员可以查看所有用户的数据,普通用户只能查看自己的
|
|
|
|
|
|
let query = 'SELECT * FROM todos WHERE date >= ? AND date <= ? AND suspended = FALSE AND user_id = ? ORDER BY date DESC, priority DESC, created_at DESC';
|
|
|
|
|
|
|
|
|
|
|
|
let params = [oneWeekAgo.toISOString().split('T')[0], today.toISOString().split('T')[0]];
|
|
|
|
|
|
params.push(req.user.userId);
|
|
|
|
|
|
const [rows] = await connection.execute(query, params);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(rows);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的待办事项
|
|
|
|
|
|
app.post('/api/todos', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const { title, priority, date, description } = req.body;
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
if (!title || !date) {
|
|
|
|
|
|
return res.status(400).json({ error: '标题和日期是必需的' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
const [result] = await connection.execute(
|
2025-06-13 07:31:12 +00:00
|
|
|
|
'INSERT INTO todos (title, description, priority, date, user_id) VALUES (?, ?, ?, ?, ?)',
|
|
|
|
|
|
[title, description || null, priority || 'medium', date, req.user.userId]
|
2025-06-13 06:04:40 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const [newTodo] = await connection.execute(
|
|
|
|
|
|
'SELECT * FROM todos WHERE id = ?',
|
|
|
|
|
|
[result.insertId]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.status(201).json(newTodo[0]);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('创建待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新待办事项状态
|
|
|
|
|
|
app.put('/api/todos/:id', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id } = req.params;
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const { completed, title, priority, date, description } = req.body;
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 检查待办事项是否存在且属于当前用户(管理员可以修改所有)
|
|
|
|
|
|
let checkQuery = 'SELECT * FROM todos WHERE id = ?';
|
|
|
|
|
|
let checkParams = [id];
|
|
|
|
|
|
|
|
|
|
|
|
if (!req.user.is_admin) {
|
|
|
|
|
|
checkQuery = 'SELECT * FROM todos WHERE id = ? AND user_id = ?';
|
|
|
|
|
|
checkParams.push(req.user.userId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const [existingTodos] = await connection.execute(checkQuery, checkParams);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingTodos.length === 0) {
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
return res.status(404).json({ error: '待办事项不存在或无权限访问' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建更新字段和值
|
|
|
|
|
|
const updates = [];
|
|
|
|
|
|
const values = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (completed !== undefined) {
|
|
|
|
|
|
updates.push('completed = ?');
|
|
|
|
|
|
values.push(completed);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (title !== undefined) {
|
|
|
|
|
|
updates.push('title = ?');
|
|
|
|
|
|
values.push(title);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (priority !== undefined) {
|
|
|
|
|
|
updates.push('priority = ?');
|
|
|
|
|
|
values.push(priority);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (date !== undefined) {
|
|
|
|
|
|
updates.push('date = ?');
|
|
|
|
|
|
values.push(date);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (description !== undefined) {
|
|
|
|
|
|
updates.push('description = ?');
|
|
|
|
|
|
values.push(description || null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (updates.length === 0) {
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
return res.status(400).json({ error: '没有提供要更新的字段' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加 id 到值数组
|
|
|
|
|
|
values.push(id);
|
|
|
|
|
|
|
|
|
|
|
|
// 执行更新
|
2025-06-13 06:04:40 +00:00
|
|
|
|
await connection.execute(
|
2025-06-13 07:31:12 +00:00
|
|
|
|
`UPDATE todos SET ${updates.join(', ')} WHERE id = ?`,
|
|
|
|
|
|
values
|
2025-06-13 06:04:40 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 获取更新后的待办事项
|
2025-06-13 06:04:40 +00:00
|
|
|
|
const [updatedTodo] = await connection.execute(
|
|
|
|
|
|
'SELECT * FROM todos WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(updatedTodo[0]);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 删除待办事项
|
|
|
|
|
|
app.delete('/api/todos/:id', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 检查待办事项是否存在且属于当前用户(管理员可以删除所有)
|
|
|
|
|
|
let checkQuery = 'SELECT * FROM todos WHERE id = ?';
|
|
|
|
|
|
let checkParams = [id];
|
|
|
|
|
|
|
|
|
|
|
|
if (!req.user.is_admin) {
|
|
|
|
|
|
checkQuery = 'SELECT * FROM todos WHERE id = ? AND user_id = ?';
|
|
|
|
|
|
checkParams.push(req.user.userId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const [existingTodos] = await connection.execute(checkQuery, checkParams);
|
|
|
|
|
|
|
|
|
|
|
|
if (existingTodos.length === 0) {
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
return res.status(404).json({ error: '待办事项不存在或无权限访问' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-13 06:04:40 +00:00
|
|
|
|
await connection.execute('DELETE FROM todos WHERE id = ?', [id]);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 挂起今天的待办事项
|
|
|
|
|
|
app.put('/api/todos/:id/suspend', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 检查是否是今天的待办事项且属于当前用户
|
|
|
|
|
|
let checkQuery = 'SELECT * FROM todos WHERE id = ? AND date = ? AND user_id = ?';
|
|
|
|
|
|
let checkParams = [id, today];
|
|
|
|
|
|
checkParams.push(req.user.userId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [todoCheck] = await connection.execute(checkQuery, checkParams);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
if (todoCheck.length === 0) {
|
|
|
|
|
|
connection.release();
|
2025-06-13 07:31:12 +00:00
|
|
|
|
return res.status(400).json({ error: '只能挂起今天的待办事项或无权限访问' });
|
2025-06-13 06:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新挂起状态
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE todos SET suspended = TRUE, suspended_date = ? WHERE id = ?',
|
|
|
|
|
|
[today, id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const [updatedTodo] = await connection.execute(
|
|
|
|
|
|
'SELECT * FROM todos WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(updatedTodo[0]);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('挂起待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有挂起的待办事项
|
|
|
|
|
|
app.get('/api/todos/suspended', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 管理员可以查看所有用户的挂起待办,普通用户只能查看自己的
|
|
|
|
|
|
let query = 'SELECT * FROM todos WHERE suspended = TRUE AND user_id = ? ORDER BY suspended_date DESC, priority DESC, created_at DESC';
|
|
|
|
|
|
let params = [];
|
|
|
|
|
|
params.push(req.user.userId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [rows] = await connection.execute(query, params);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(rows);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取挂起待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复挂起的待办事项到今天
|
|
|
|
|
|
app.put('/api/todos/:id/resume', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
// 检查是否是挂起的待办事项且属于当前用户
|
|
|
|
|
|
let checkQuery = 'SELECT * FROM todos WHERE id = ? AND suspended = TRUE AND user_id = ?';
|
|
|
|
|
|
let checkParams = [id];
|
|
|
|
|
|
checkParams.push(req.user.userId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [todoCheck] = await connection.execute(checkQuery, checkParams);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
if (todoCheck.length === 0) {
|
|
|
|
|
|
connection.release();
|
2025-06-13 07:31:12 +00:00
|
|
|
|
return res.status(400).json({ error: '待办事项未处于挂起状态或无权限访问' });
|
2025-06-13 06:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复待办事项到今天
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE todos SET suspended = FALSE, suspended_date = NULL, date = ?, completed = FALSE WHERE id = ?',
|
|
|
|
|
|
[today, id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const [updatedTodo] = await connection.execute(
|
|
|
|
|
|
'SELECT * FROM todos WHERE id = ?',
|
|
|
|
|
|
[id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json(updatedTodo[0]);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('恢复待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 将历史未完成待办迁移到今天
|
|
|
|
|
|
app.post('/api/todos/migrate-pending', authenticateToken, async (req, res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取今天的日期
|
|
|
|
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
|
|
// 查找今天之前所有未完成且未挂起的待办事项
|
2025-06-13 07:31:12 +00:00
|
|
|
|
let query = 'SELECT * FROM todos WHERE date < ? AND completed = FALSE AND suspended = FALSE AND user_id = ? ORDER BY date DESC, priority DESC';
|
|
|
|
|
|
let params = [today];
|
|
|
|
|
|
params.push(req.user.userId);
|
|
|
|
|
|
|
|
|
|
|
|
const [pendingTodos] = await connection.execute(query, params);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
let migratedCount = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// 将每个未完成的待办事项迁移到今天
|
|
|
|
|
|
for (const todo of pendingTodos) {
|
|
|
|
|
|
await connection.execute(
|
|
|
|
|
|
'UPDATE todos SET date = ? WHERE id = ?',
|
|
|
|
|
|
[today, todo.id]
|
|
|
|
|
|
);
|
|
|
|
|
|
migratedCount++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
connection.release();
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
migratedCount,
|
|
|
|
|
|
message: `已迁移 ${migratedCount} 个未完成的待办事项到今天`
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('迁移待办事项失败:', error);
|
|
|
|
|
|
res.status(500).json({ error: '服务器错误' });
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 启动服务器
|
|
|
|
|
|
app.listen(PORT, async () => {
|
|
|
|
|
|
console.log(`服务器运行在端口 ${PORT}`);
|
|
|
|
|
|
await initDatabase();
|
|
|
|
|
|
});
|