Migrate project from typing_practiceweb
This commit is contained in:
324
server/routes/admin.ts
Normal file
324
server/routes/admin.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
// server/routes/admin.ts
|
||||
import express from 'express';
|
||||
import { adminAuth } from '../middleware/auth';
|
||||
import { User } from '../models/User';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { OAuth2Client } from '../models/oauth2';
|
||||
import { AdminController } from './admin.controller';
|
||||
import multer from 'multer';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import csv from 'csv-parser';
|
||||
import { Word, WordSet } from '../models/Vocabulary';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const router = express.Router();
|
||||
const adminController = new AdminController();
|
||||
|
||||
// 配置 multer 用于文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
const uploadDir = path.join(__dirname, '../uploads');
|
||||
// 确保上传目录存在
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, `${Date.now()}-${file.originalname}`);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024 // 限制5MB
|
||||
},
|
||||
fileFilter: function (req, file, cb) {
|
||||
// 只允许上传CSV文件
|
||||
if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只支持CSV文件'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/users', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const users = await User.find()
|
||||
.select('-password')
|
||||
.sort({ createdAt: -1 });
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: '获取用户列表失败' });
|
||||
}
|
||||
});
|
||||
// 添加 PUT 路由用于更新用户
|
||||
router.put('/users/:id', adminAuth, async (req, res) => {
|
||||
try {
|
||||
// 检查用户名是否已存在(排除当前用户)
|
||||
const existingUser = await User.findOne({
|
||||
username: req.body.username,
|
||||
_id: { $ne: req.params.id }
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ message: '用户名已被使用' });
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在(排除当前用户)
|
||||
if (req.body.email) {
|
||||
const existingEmail = await User.findOne({
|
||||
email: req.body.email,
|
||||
_id: { $ne: req.params.id }
|
||||
});
|
||||
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({ message: '邮箱已被使用' });
|
||||
}
|
||||
}
|
||||
|
||||
const user = await User.findByIdAndUpdate(
|
||||
req.params.id,
|
||||
req.body,
|
||||
{ new: true, select: '-password' }
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: '用户不存在' });
|
||||
}
|
||||
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
console.error('Error updating user:', error);
|
||||
res.status(500).json({ message: '更新用户失败' });
|
||||
}
|
||||
});
|
||||
router.post('/users', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const { username, email, password, fullname, isAdmin } = req.body;
|
||||
// 验证必填字段
|
||||
if (!username || !email || !password || !fullname) {
|
||||
return res.status(400).json({ message: '请填写所有必填字段' });
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const existingUser = await User.findOne({ username });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ message: '用户名已被使用' });
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
const existingEmail = await User.findOne({ email });
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({ message: '邮箱已被使用' });
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
|
||||
const newUser = new User({
|
||||
username,
|
||||
email,
|
||||
password: password,
|
||||
fullname,
|
||||
isAdmin: isAdmin || false
|
||||
});
|
||||
|
||||
await newUser.save();
|
||||
|
||||
// 返回用户信息(不包含密码)
|
||||
const userResponse = await User.findById(newUser._id).select('-password');
|
||||
res.status(201).json(userResponse);
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
res.status(500).json({ message: '创建用户失败' });
|
||||
}
|
||||
});
|
||||
// 添加删除用户路由
|
||||
router.delete('/users/:id', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const user = await User.findById(req.params.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: '用户不存在' });
|
||||
}
|
||||
|
||||
// 防止删除最后一个管理员
|
||||
if (user.isAdmin) {
|
||||
const adminCount = await User.countDocuments({ isAdmin: true });
|
||||
if (adminCount <= 1) {
|
||||
return res.status(400).json({ message: '不能删除最后一个管理员' });
|
||||
}
|
||||
}
|
||||
|
||||
await User.findByIdAndDelete(req.params.id);
|
||||
res.json({ message: '用户已删除' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
res.status(500).json({ message: '删除用户失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// OAuth2 客户端管理路由
|
||||
router.get('/oauth2/clients', adminAuth, (req, res) => adminController.listOAuth2Clients(req, res));
|
||||
|
||||
router.post('/oauth2/clients', adminAuth, (req, res) => adminController.createOAuth2Client(req, res));
|
||||
|
||||
router.put('/oauth2/clients/:id', adminAuth, (req, res) => adminController.updateOAuth2Client(req, res));
|
||||
|
||||
router.delete('/oauth2/clients/:id', adminAuth, (req, res) => adminController.deleteOAuth2Client(req, res));
|
||||
|
||||
// ===== 单词库管理路由 =====
|
||||
|
||||
// 获取所有单词集
|
||||
router.get('/vocabulary/word-sets', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const wordSets = await WordSet.find()
|
||||
.select('name description totalWords createdAt owner')
|
||||
.populate('owner', 'username fullname')
|
||||
.sort({ createdAt: -1 });
|
||||
|
||||
res.json(wordSets);
|
||||
} catch (error) {
|
||||
console.error('获取单词集失败:', error);
|
||||
res.status(500).json({ message: '获取单词集失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 上传单词文件并创建单词集(管理员版本)
|
||||
router.post('/vocabulary/upload', adminAuth, upload.single('file'), async (req, res) => {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ message: '请上传文件' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { name, description, ownerId } = req.body;
|
||||
const fileName = req.file.filename;
|
||||
const filePath = req.file.path;
|
||||
|
||||
// 如果没有指定所有者,默认为当前管理员
|
||||
const owner = ownerId || req.user._id;
|
||||
|
||||
// 检查是否已存在同名单词集
|
||||
const existingSet = await WordSet.findOne({
|
||||
name: name || path.basename(fileName, '.csv'),
|
||||
owner: owner
|
||||
});
|
||||
|
||||
if (existingSet) {
|
||||
// 删除上传的文件
|
||||
fs.unlinkSync(filePath);
|
||||
return res.status(400).json({ message: '已存在同名单词集' });
|
||||
}
|
||||
|
||||
// 创建单词集
|
||||
const wordSet = await WordSet.create({
|
||||
name: name || path.basename(fileName, '.csv'),
|
||||
description: description || '',
|
||||
owner: owner,
|
||||
totalWords: 0
|
||||
});
|
||||
|
||||
const results: any[] = [];
|
||||
let wordCount = 0;
|
||||
|
||||
// 处理CSV文件
|
||||
fs.createReadStream(filePath)
|
||||
.pipe(csv())
|
||||
.on('data', (data) => {
|
||||
// 检查必要的字段是否存在
|
||||
if (data.word && data.translation) {
|
||||
results.push({
|
||||
word: data.word.trim(),
|
||||
translation: data.translation.trim(),
|
||||
pronunciation: data.pronunciation?.trim(),
|
||||
example: data.example?.trim(),
|
||||
wordSet: wordSet._id
|
||||
});
|
||||
wordCount++;
|
||||
}
|
||||
})
|
||||
.on('end', async () => {
|
||||
try {
|
||||
// 批量插入单词
|
||||
if (results.length > 0) {
|
||||
await Word.insertMany(results);
|
||||
|
||||
// 更新单词集的单词数量
|
||||
await WordSet.findByIdAndUpdate(wordSet._id, { totalWords: wordCount });
|
||||
}
|
||||
|
||||
// 删除上传的文件
|
||||
fs.unlinkSync(filePath);
|
||||
|
||||
res.status(201).json({
|
||||
message: `成功创建单词集并导入${wordCount}个单词`,
|
||||
wordSet
|
||||
});
|
||||
} catch (error) {
|
||||
// 如果插入单词失败,删除创建的单词集
|
||||
await WordSet.findByIdAndDelete(wordSet._id);
|
||||
|
||||
console.error('导入单词失败:', error);
|
||||
res.status(500).json({ message: '导入单词失败' });
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
console.error('处理CSV文件失败:', error);
|
||||
res.status(500).json({ message: '处理CSV文件失败' });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('上传文件失败:', error);
|
||||
res.status(500).json({ message: '上传文件失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除单词集
|
||||
router.delete('/vocabulary/word-sets/:id', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 检查单词集是否存在
|
||||
const wordSet = await WordSet.findById(id);
|
||||
if (!wordSet) {
|
||||
return res.status(404).json({ message: '未找到单词集' });
|
||||
}
|
||||
|
||||
// 删除单词集及其关联的所有单词
|
||||
await Word.deleteMany({ wordSet: id });
|
||||
await WordSet.findByIdAndDelete(id);
|
||||
|
||||
res.json({ message: '单词集已删除' });
|
||||
} catch (error) {
|
||||
console.error('删除单词集失败:', error);
|
||||
res.status(500).json({ message: '删除单词集失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 单词集详情(包括所有单词)
|
||||
router.get('/vocabulary/word-sets/:id/words', adminAuth, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// 检查单词集是否存在
|
||||
const wordSet = await WordSet.findById(id);
|
||||
if (!wordSet) {
|
||||
return res.status(404).json({ message: '未找到单词集' });
|
||||
}
|
||||
|
||||
// 获取单词集中的所有单词
|
||||
const words = await Word.find({ wordSet: id });
|
||||
|
||||
res.json({
|
||||
wordSet,
|
||||
words
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取单词集详情失败:', error);
|
||||
res.status(500).json({ message: '获取单词集详情失败' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user