Spaces:
Sleeping
Sleeping
Upload 19 files
Browse files- Dockerfile +23 -0
- config/auth.js +12 -0
- config/db.js +71 -0
- controllers/authController.js +83 -0
- controllers/categoryController.js +113 -0
- controllers/promptGroupController.js +246 -0
- middleware/auth.js +49 -0
- middleware/errorHandler.js +16 -0
- models/Category.js +23 -0
- models/PromptGroup.js +81 -0
- models/User.js +44 -0
- package.json +29 -0
- routes/authRoutes.js +15 -0
- routes/categoryRoutes.js +21 -0
- routes/index.js +12 -0
- routes/promptGroupRoutes.js +42 -0
- server.js +56 -0
- utils/logger.js +35 -0
- utils/validators.js +42 -0
Dockerfile
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:18-alpine
|
2 |
+
|
3 |
+
# 创建工作目录
|
4 |
+
WORKDIR /app
|
5 |
+
|
6 |
+
# 复制 package.json 和 package-lock.json
|
7 |
+
COPY package*.json ./
|
8 |
+
|
9 |
+
# 安装依赖
|
10 |
+
RUN npm install --production
|
11 |
+
|
12 |
+
# 复制所有项目文件
|
13 |
+
COPY . .
|
14 |
+
|
15 |
+
# 暴露端口
|
16 |
+
EXPOSE 8080
|
17 |
+
|
18 |
+
# 设置环境变量
|
19 |
+
ENV PORT=8080
|
20 |
+
ENV NODE_ENV=production
|
21 |
+
|
22 |
+
# 启动应用
|
23 |
+
CMD ["npm", "start"]
|
config/auth.js
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const jwt = require('jsonwebtoken');
|
2 |
+
|
3 |
+
// 生成 JWT Token
|
4 |
+
const generateToken = (userId) => {
|
5 |
+
return jwt.sign(
|
6 |
+
{ id: userId },
|
7 |
+
process.env.JWT_SECRET,
|
8 |
+
{ expiresIn: process.env.JWT_EXPIRE || '24h' }
|
9 |
+
);
|
10 |
+
};
|
11 |
+
|
12 |
+
module.exports = { generateToken };
|
config/db.js
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const mongoose = require('mongoose');
|
2 |
+
const logger = require('../utils/logger');
|
3 |
+
|
4 |
+
const connectDB = async () => {
|
5 |
+
try {
|
6 |
+
const conn = await mongoose.connect(process.env.MONGODB_URI, {
|
7 |
+
useNewUrlParser: true,
|
8 |
+
useUnifiedTopology: true,
|
9 |
+
});
|
10 |
+
|
11 |
+
logger.info(`MongoDB Connected: ${conn.connection.host}`);
|
12 |
+
|
13 |
+
// 创建默认管理员用户和默认分类
|
14 |
+
await createDefaultAdmin();
|
15 |
+
await createDefaultCategories();
|
16 |
+
} catch (error) {
|
17 |
+
logger.error(`Error connecting to MongoDB: ${error.message}`);
|
18 |
+
process.exit(1);
|
19 |
+
}
|
20 |
+
};
|
21 |
+
|
22 |
+
const createDefaultAdmin = async () => {
|
23 |
+
try {
|
24 |
+
const User = require('../models/User');
|
25 |
+
const bcrypt = require('bcryptjs');
|
26 |
+
|
27 |
+
// 检查是否已存在管理员用户
|
28 |
+
const adminExists = await User.findOne({ username: process.env.ADMIN_USERNAME });
|
29 |
+
|
30 |
+
if (!adminExists && process.env.ADMIN_USERNAME && process.env.ADMIN_PASSWORD) {
|
31 |
+
// 创建管理员用户
|
32 |
+
const hashedPassword = await bcrypt.hash(process.env.ADMIN_PASSWORD, 10);
|
33 |
+
|
34 |
+
await User.create({
|
35 |
+
username: process.env.ADMIN_USERNAME,
|
36 |
+
password: hashedPassword,
|
37 |
+
isAdmin: true
|
38 |
+
});
|
39 |
+
|
40 |
+
logger.info('Default admin user created');
|
41 |
+
}
|
42 |
+
} catch (error) {
|
43 |
+
logger.error(`Error creating default admin: ${error.message}`);
|
44 |
+
}
|
45 |
+
};
|
46 |
+
|
47 |
+
const createDefaultCategories = async () => {
|
48 |
+
try {
|
49 |
+
const Category = require('../models/Category');
|
50 |
+
|
51 |
+
// 检查是否已有分类
|
52 |
+
const categoriesCount = await Category.countDocuments();
|
53 |
+
|
54 |
+
if (categoriesCount === 0) {
|
55 |
+
// 创建默认分类
|
56 |
+
const defaultCategories = [
|
57 |
+
{ name: '通用', color: '#007AFF' },
|
58 |
+
{ name: '工作', color: '#FF9500' },
|
59 |
+
{ name: '学习', color: '#4CD964' },
|
60 |
+
{ name: '创意', color: '#FF2D55' }
|
61 |
+
];
|
62 |
+
|
63 |
+
await Category.insertMany(defaultCategories);
|
64 |
+
logger.info('Default categories created');
|
65 |
+
}
|
66 |
+
} catch (error) {
|
67 |
+
logger.error(`Error creating default categories: ${error.message}`);
|
68 |
+
}
|
69 |
+
};
|
70 |
+
|
71 |
+
module.exports = { connectDB };
|
controllers/authController.js
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const asyncHandler = require('express-async-handler');
|
2 |
+
const User = require('../models/User');
|
3 |
+
const { generateToken } = require('../config/auth');
|
4 |
+
|
5 |
+
// @desc 用户注册
|
6 |
+
// @route POST /api/auth/register
|
7 |
+
// @access Public
|
8 |
+
const registerUser = asyncHandler(async (req, res) => {
|
9 |
+
const { username, password } = req.body;
|
10 |
+
|
11 |
+
// 检查用户是否已存在
|
12 |
+
const userExists = await User.findOne({ username });
|
13 |
+
|
14 |
+
if (userExists) {
|
15 |
+
res.status(400);
|
16 |
+
throw new Error('用户已存在');
|
17 |
+
}
|
18 |
+
|
19 |
+
// 创建用户
|
20 |
+
const user = await User.create({
|
21 |
+
username,
|
22 |
+
password,
|
23 |
+
});
|
24 |
+
|
25 |
+
if (user) {
|
26 |
+
res.status(201).json({
|
27 |
+
_id: user._id,
|
28 |
+
username: user.username,
|
29 |
+
isAdmin: user.isAdmin,
|
30 |
+
token: generateToken(user._id),
|
31 |
+
});
|
32 |
+
} else {
|
33 |
+
res.status(400);
|
34 |
+
throw new Error('无效的用户数据');
|
35 |
+
}
|
36 |
+
});
|
37 |
+
|
38 |
+
// @desc 用户登录
|
39 |
+
// @route POST /api/auth/login
|
40 |
+
// @access Public
|
41 |
+
const loginUser = asyncHandler(async (req, res) => {
|
42 |
+
const { username, password } = req.body;
|
43 |
+
|
44 |
+
// 查找用户
|
45 |
+
const user = await User.findOne({ username });
|
46 |
+
|
47 |
+
// 检查用户和密码
|
48 |
+
if (user && (await user.matchPassword(password))) {
|
49 |
+
res.json({
|
50 |
+
_id: user._id,
|
51 |
+
username: user.username,
|
52 |
+
isAdmin: user.isAdmin,
|
53 |
+
token: generateToken(user._id),
|
54 |
+
});
|
55 |
+
} else {
|
56 |
+
res.status(401);
|
57 |
+
throw new Error('用户名或密码错误');
|
58 |
+
}
|
59 |
+
});
|
60 |
+
|
61 |
+
// @desc 获取当前用户资料
|
62 |
+
// @route GET /api/auth/profile
|
63 |
+
// @access Private
|
64 |
+
const getUserProfile = asyncHandler(async (req, res) => {
|
65 |
+
const user = await User.findById(req.user._id).select('-password');
|
66 |
+
|
67 |
+
if (user) {
|
68 |
+
res.json({
|
69 |
+
_id: user._id,
|
70 |
+
username: user.username,
|
71 |
+
isAdmin: user.isAdmin,
|
72 |
+
});
|
73 |
+
} else {
|
74 |
+
res.status(404);
|
75 |
+
throw new Error('用户未找到');
|
76 |
+
}
|
77 |
+
});
|
78 |
+
|
79 |
+
module.exports = {
|
80 |
+
registerUser,
|
81 |
+
loginUser,
|
82 |
+
getUserProfile,
|
83 |
+
};
|
controllers/categoryController.js
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const asyncHandler = require('express-async-handler');
|
2 |
+
const Category = require('../models/Category');
|
3 |
+
const PromptGroup = require('../models/PromptGroup');
|
4 |
+
|
5 |
+
// @desc 获取所有分类
|
6 |
+
// @route GET /api/categories
|
7 |
+
// @access Private
|
8 |
+
const getCategories = asyncHandler(async (req, res) => {
|
9 |
+
const categories = await Category.find({}).sort({ name: 1 });
|
10 |
+
res.json(categories);
|
11 |
+
});
|
12 |
+
|
13 |
+
// @desc 通过ID获取分类
|
14 |
+
// @route GET /api/categories/:id
|
15 |
+
// @access Private
|
16 |
+
const getCategoryById = asyncHandler(async (req, res) => {
|
17 |
+
const category = await Category.findById(req.params.id);
|
18 |
+
|
19 |
+
if (category) {
|
20 |
+
res.json(category);
|
21 |
+
} else {
|
22 |
+
res.status(404);
|
23 |
+
throw new Error('分类未找到');
|
24 |
+
}
|
25 |
+
});
|
26 |
+
|
27 |
+
// @desc 创建分类
|
28 |
+
// @route POST /api/categories
|
29 |
+
// @access Private
|
30 |
+
const createCategory = asyncHandler(async (req, res) => {
|
31 |
+
const { name, color } = req.body;
|
32 |
+
|
33 |
+
// 检查是否分类名称已存在
|
34 |
+
const categoryExists = await Category.findOne({ name });
|
35 |
+
|
36 |
+
if (categoryExists) {
|
37 |
+
res.status(400);
|
38 |
+
throw new Error('分类名称已存在');
|
39 |
+
}
|
40 |
+
|
41 |
+
const category = await Category.create({
|
42 |
+
name,
|
43 |
+
color: color || '#007AFF',
|
44 |
+
});
|
45 |
+
|
46 |
+
if (category) {
|
47 |
+
res.status(201).json(category);
|
48 |
+
} else {
|
49 |
+
res.status(400);
|
50 |
+
throw new Error('无效的分类数据');
|
51 |
+
}
|
52 |
+
});
|
53 |
+
|
54 |
+
// @desc 更新分类
|
55 |
+
// @route PUT /api/categories/:id
|
56 |
+
// @access Private
|
57 |
+
const updateCategory = asyncHandler(async (req, res) => {
|
58 |
+
const { name, color } = req.body;
|
59 |
+
|
60 |
+
const category = await Category.findById(req.params.id);
|
61 |
+
|
62 |
+
if (category) {
|
63 |
+
// 如果更改了名称,检查新名称是否已存在
|
64 |
+
if (name && name !== category.name) {
|
65 |
+
const categoryExists = await Category.findOne({ name });
|
66 |
+
|
67 |
+
if (categoryExists) {
|
68 |
+
res.status(400);
|
69 |
+
throw new Error('分类名称已存在');
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
category.name = name || category.name;
|
74 |
+
category.color = color || category.color;
|
75 |
+
|
76 |
+
const updatedCategory = await category.save();
|
77 |
+
res.json(updatedCategory);
|
78 |
+
} else {
|
79 |
+
res.status(404);
|
80 |
+
throw new Error('分类未找到');
|
81 |
+
}
|
82 |
+
});
|
83 |
+
|
84 |
+
// @desc 删除分类
|
85 |
+
// @route DELETE /api/categories/:id
|
86 |
+
// @access Private
|
87 |
+
const deleteCategory = asyncHandler(async (req, res) => {
|
88 |
+
const category = await Category.findById(req.params.id);
|
89 |
+
|
90 |
+
if (!category) {
|
91 |
+
res.status(404);
|
92 |
+
throw new Error('分类未找到');
|
93 |
+
}
|
94 |
+
|
95 |
+
// 检查是否有提示词组使用该分类
|
96 |
+
const groupsUsingCategory = await PromptGroup.countDocuments({ category: req.params.id });
|
97 |
+
|
98 |
+
if (groupsUsingCategory > 0) {
|
99 |
+
res.status(400);
|
100 |
+
throw new Error(`无法删除分类,有 ${groupsUsingCategory} 个提示词组正在使用它`);
|
101 |
+
}
|
102 |
+
|
103 |
+
await category.deleteOne();
|
104 |
+
res.json({ message: '分类已删除' });
|
105 |
+
});
|
106 |
+
|
107 |
+
module.exports = {
|
108 |
+
getCategories,
|
109 |
+
getCategoryById,
|
110 |
+
createCategory,
|
111 |
+
updateCategory,
|
112 |
+
deleteCategory,
|
113 |
+
};
|
controllers/promptGroupController.js
ADDED
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const asyncHandler = require('express-async-handler');
|
2 |
+
const PromptGroup = require('../models/PromptGroup');
|
3 |
+
|
4 |
+
// @desc 获取所有提示词组
|
5 |
+
// @route GET /api/prompt-groups
|
6 |
+
// @access Private
|
7 |
+
const getPromptGroups = asyncHandler(async (req, res) => {
|
8 |
+
const promptGroups = await PromptGroup.find({})
|
9 |
+
.populate('category', 'name color')
|
10 |
+
.sort({ updatedAt: -1 });
|
11 |
+
|
12 |
+
res.json(promptGroups);
|
13 |
+
});
|
14 |
+
|
15 |
+
// @desc 获取单个提示词组
|
16 |
+
// @route GET /api/prompt-groups/:id
|
17 |
+
// @access Private
|
18 |
+
const getPromptGroupById = asyncHandler(async (req, res) => {
|
19 |
+
const promptGroup = await PromptGroup.findById(req.params.id)
|
20 |
+
.populate('category', 'name color');
|
21 |
+
|
22 |
+
if (promptGroup) {
|
23 |
+
res.json(promptGroup);
|
24 |
+
} else {
|
25 |
+
res.status(404);
|
26 |
+
throw new Error('提示词组未找到');
|
27 |
+
}
|
28 |
+
});
|
29 |
+
|
30 |
+
// @desc 创建提示词组
|
31 |
+
// @route POST /api/prompt-groups
|
32 |
+
// @access Private
|
33 |
+
const createPromptGroup = asyncHandler(async (req, res) => {
|
34 |
+
const { name, description, category } = req.body;
|
35 |
+
|
36 |
+
const promptGroup = await PromptGroup.create({
|
37 |
+
name,
|
38 |
+
description,
|
39 |
+
category,
|
40 |
+
prompts: [],
|
41 |
+
workflows: [],
|
42 |
+
dslFiles: [],
|
43 |
+
createdBy: req.user._id,
|
44 |
+
});
|
45 |
+
|
46 |
+
if (promptGroup) {
|
47 |
+
res.status(201).json(promptGroup);
|
48 |
+
} else {
|
49 |
+
res.status(400);
|
50 |
+
throw new Error('无效的提示词组数据');
|
51 |
+
}
|
52 |
+
});
|
53 |
+
|
54 |
+
// @desc 更新提示词组
|
55 |
+
// @route PUT /api/prompt-groups/:id
|
56 |
+
// @access Private
|
57 |
+
const updatePromptGroup = asyncHandler(async (req, res) => {
|
58 |
+
const { name, description, category } = req.body;
|
59 |
+
|
60 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
61 |
+
|
62 |
+
if (promptGroup) {
|
63 |
+
promptGroup.name = name || promptGroup.name;
|
64 |
+
promptGroup.description = description || promptGroup.description;
|
65 |
+
promptGroup.category = category || promptGroup.category;
|
66 |
+
|
67 |
+
const updatedPromptGroup = await promptGroup.save();
|
68 |
+
res.json(updatedPromptGroup);
|
69 |
+
} else {
|
70 |
+
res.status(404);
|
71 |
+
throw new Error('提示词组未找到');
|
72 |
+
}
|
73 |
+
});
|
74 |
+
|
75 |
+
// @desc 删除提示词组
|
76 |
+
// @route DELETE /api/prompt-groups/:id
|
77 |
+
// @access Private
|
78 |
+
const deletePromptGroup = asyncHandler(async (req, res) => {
|
79 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
80 |
+
|
81 |
+
if (promptGroup) {
|
82 |
+
await promptGroup.deleteOne();
|
83 |
+
res.json({ message: '提示词组已删除' });
|
84 |
+
} else {
|
85 |
+
res.status(404);
|
86 |
+
throw new Error('提示词组未找到');
|
87 |
+
}
|
88 |
+
});
|
89 |
+
|
90 |
+
// @desc 添加提示词到提示词组
|
91 |
+
// @route POST /api/prompt-groups/:id/prompts
|
92 |
+
// @access Private
|
93 |
+
const addPromptToGroup = asyncHandler(async (req, res) => {
|
94 |
+
const { title, content, tags } = req.body;
|
95 |
+
|
96 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
97 |
+
|
98 |
+
if (promptGroup) {
|
99 |
+
const newPrompt = {
|
100 |
+
title,
|
101 |
+
content,
|
102 |
+
tags: tags || [],
|
103 |
+
createdAt: new Date(),
|
104 |
+
updatedAt: new Date(),
|
105 |
+
};
|
106 |
+
|
107 |
+
promptGroup.prompts.push(newPrompt);
|
108 |
+
promptGroup.updatedAt = new Date();
|
109 |
+
|
110 |
+
const updatedPromptGroup = await promptGroup.save();
|
111 |
+
res.status(201).json(updatedPromptGroup.prompts[updatedPromptGroup.prompts.length - 1]);
|
112 |
+
} else {
|
113 |
+
res.status(404);
|
114 |
+
throw new Error('提示词组未找到');
|
115 |
+
}
|
116 |
+
});
|
117 |
+
|
118 |
+
// @desc 更新提示词
|
119 |
+
// @route PUT /api/prompt-groups/:id/prompts/:promptId
|
120 |
+
// @access Private
|
121 |
+
const updatePrompt = asyncHandler(async (req, res) => {
|
122 |
+
const { title, content, tags } = req.body;
|
123 |
+
|
124 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
125 |
+
|
126 |
+
if (promptGroup) {
|
127 |
+
const promptIndex = promptGroup.prompts.findIndex(
|
128 |
+
(p) => p._id.toString() === req.params.promptId
|
129 |
+
);
|
130 |
+
|
131 |
+
if (promptIndex !== -1) {
|
132 |
+
promptGroup.prompts[promptIndex].title = title || promptGroup.prompts[promptIndex].title;
|
133 |
+
promptGroup.prompts[promptIndex].content = content || promptGroup.prompts[promptIndex].content;
|
134 |
+
promptGroup.prompts[promptIndex].tags = tags || promptGroup.prompts[promptIndex].tags;
|
135 |
+
promptGroup.prompts[promptIndex].updatedAt = new Date();
|
136 |
+
promptGroup.updatedAt = new Date();
|
137 |
+
|
138 |
+
const updatedPromptGroup = await promptGroup.save();
|
139 |
+
res.json(updatedPromptGroup.prompts[promptIndex]);
|
140 |
+
} else {
|
141 |
+
res.status(404);
|
142 |
+
throw new Error('提示词未找到');
|
143 |
+
}
|
144 |
+
} else {
|
145 |
+
res.status(404);
|
146 |
+
throw new Error('提示词组未找到');
|
147 |
+
}
|
148 |
+
});
|
149 |
+
|
150 |
+
// @desc 删除提示词
|
151 |
+
// @route DELETE /api/prompt-groups/:id/prompts/:promptId
|
152 |
+
// @access Private
|
153 |
+
const deletePrompt = asyncHandler(async (req, res) => {
|
154 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
155 |
+
|
156 |
+
if (promptGroup) {
|
157 |
+
const promptExists = promptGroup.prompts.some(
|
158 |
+
(p) => p._id.toString() === req.params.promptId
|
159 |
+
);
|
160 |
+
|
161 |
+
if (promptExists) {
|
162 |
+
promptGroup.prompts = promptGroup.prompts.filter(
|
163 |
+
(p) => p._id.toString() !== req.params.promptId
|
164 |
+
);
|
165 |
+
promptGroup.updatedAt = new Date();
|
166 |
+
|
167 |
+
await promptGroup.save();
|
168 |
+
res.json({ message: '提示词已删除' });
|
169 |
+
} else {
|
170 |
+
res.status(404);
|
171 |
+
throw new Error('提示词未找到');
|
172 |
+
}
|
173 |
+
} else {
|
174 |
+
res.status(404);
|
175 |
+
throw new Error('提示词组未找到');
|
176 |
+
}
|
177 |
+
});
|
178 |
+
|
179 |
+
// @desc 添加DSL文件到提示词组
|
180 |
+
// @route POST /api/prompt-groups/:id/dsl-files
|
181 |
+
// @access Private
|
182 |
+
const addDslFileToGroup = asyncHandler(async (req, res) => {
|
183 |
+
const { name, fileData, mimeType } = req.body;
|
184 |
+
|
185 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
186 |
+
|
187 |
+
if (promptGroup) {
|
188 |
+
const newDslFile = {
|
189 |
+
name,
|
190 |
+
fileData,
|
191 |
+
mimeType: mimeType || 'application/octet-stream',
|
192 |
+
uploadedAt: new Date(),
|
193 |
+
};
|
194 |
+
|
195 |
+
promptGroup.dslFiles.push(newDslFile);
|
196 |
+
promptGroup.updatedAt = new Date();
|
197 |
+
|
198 |
+
const updatedPromptGroup = await promptGroup.save();
|
199 |
+
res.status(201).json(updatedPromptGroup.dslFiles[updatedPromptGroup.dslFiles.length - 1]);
|
200 |
+
} else {
|
201 |
+
res.status(404);
|
202 |
+
throw new Error('提示词组未找到');
|
203 |
+
}
|
204 |
+
});
|
205 |
+
|
206 |
+
// @desc 删除DSL文件
|
207 |
+
// @route DELETE /api/prompt-groups/:id/dsl-files/:fileId
|
208 |
+
// @access Private
|
209 |
+
const deleteDslFile = asyncHandler(async (req, res) => {
|
210 |
+
const promptGroup = await PromptGroup.findById(req.params.id);
|
211 |
+
|
212 |
+
if (promptGroup) {
|
213 |
+
const fileExists = promptGroup.dslFiles.some(
|
214 |
+
(f) => f._id.toString() === req.params.fileId
|
215 |
+
);
|
216 |
+
|
217 |
+
if (fileExists) {
|
218 |
+
promptGroup.dslFiles = promptGroup.dslFiles.filter(
|
219 |
+
(f) => f._id.toString() !== req.params.fileId
|
220 |
+
);
|
221 |
+
promptGroup.updatedAt = new Date();
|
222 |
+
|
223 |
+
await promptGroup.save();
|
224 |
+
res.json({ message: 'DSL文件已删除' });
|
225 |
+
} else {
|
226 |
+
res.status(404);
|
227 |
+
throw new Error('DSL文件未找到');
|
228 |
+
}
|
229 |
+
} else {
|
230 |
+
res.status(404);
|
231 |
+
throw new Error('提示词组未找到');
|
232 |
+
}
|
233 |
+
});
|
234 |
+
|
235 |
+
module.exports = {
|
236 |
+
getPromptGroups,
|
237 |
+
getPromptGroupById,
|
238 |
+
createPromptGroup,
|
239 |
+
updatePromptGroup,
|
240 |
+
deletePromptGroup,
|
241 |
+
addPromptToGroup,
|
242 |
+
updatePrompt,
|
243 |
+
deletePrompt,
|
244 |
+
addDslFileToGroup,
|
245 |
+
deleteDslFile,
|
246 |
+
};
|
middleware/auth.js
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const jwt = require('jsonwebtoken');
|
2 |
+
const asyncHandler = require('express-async-handler');
|
3 |
+
const User = require('../models/User');
|
4 |
+
|
5 |
+
// 保护路由 - 验证 JWT Token
|
6 |
+
const protect = asyncHandler(async (req, res, next) => {
|
7 |
+
let token;
|
8 |
+
|
9 |
+
// 从 Authorization 头获取 token
|
10 |
+
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
|
11 |
+
try {
|
12 |
+
// 获取 token
|
13 |
+
token = req.headers.authorization.split(' ')[1];
|
14 |
+
|
15 |
+
// 验证 token
|
16 |
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
17 |
+
|
18 |
+
// 获取用户并添加到请求对象中,不包含密码
|
19 |
+
req.user = await User.findById(decoded.id).select('-password');
|
20 |
+
|
21 |
+
if (!req.user) {
|
22 |
+
res.status(401);
|
23 |
+
throw new Error('未授权,用户不存在');
|
24 |
+
}
|
25 |
+
|
26 |
+
next();
|
27 |
+
} catch (error) {
|
28 |
+
res.status(401);
|
29 |
+
throw new Error('未授权,token 无效');
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
if (!token) {
|
34 |
+
res.status(401);
|
35 |
+
throw new Error('未授权,未提供 token');
|
36 |
+
}
|
37 |
+
});
|
38 |
+
|
39 |
+
// 限制仅管理员访问
|
40 |
+
const admin = (req, res, next) => {
|
41 |
+
if (req.user && req.user.isAdmin) {
|
42 |
+
next();
|
43 |
+
} else {
|
44 |
+
res.status(403);
|
45 |
+
throw new Error('未授权,仅管理员可访问');
|
46 |
+
}
|
47 |
+
};
|
48 |
+
|
49 |
+
module.exports = { protect, admin };
|
middleware/errorHandler.js
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const logger = require('../utils/logger');
|
2 |
+
|
3 |
+
const errorHandler = (err, req, res, next) => {
|
4 |
+
// 日志记录错误
|
5 |
+
logger.error(`${err.message} - ${req.originalUrl} - ${req.method}`);
|
6 |
+
|
7 |
+
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
|
8 |
+
|
9 |
+
res.status(statusCode);
|
10 |
+
res.json({
|
11 |
+
message: err.message,
|
12 |
+
stack: process.env.NODE_ENV === 'production' ? null : err.stack,
|
13 |
+
});
|
14 |
+
};
|
15 |
+
|
16 |
+
module.exports = { errorHandler };
|
models/Category.js
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const mongoose = require('mongoose');
|
2 |
+
|
3 |
+
const categorySchema = new mongoose.Schema(
|
4 |
+
{
|
5 |
+
name: {
|
6 |
+
type: String,
|
7 |
+
required: [true, '请输入分类名称'],
|
8 |
+
trim: true,
|
9 |
+
unique: true,
|
10 |
+
},
|
11 |
+
color: {
|
12 |
+
type: String,
|
13 |
+
default: '#007AFF',
|
14 |
+
},
|
15 |
+
},
|
16 |
+
{
|
17 |
+
timestamps: true,
|
18 |
+
}
|
19 |
+
);
|
20 |
+
|
21 |
+
const Category = mongoose.model('Category', categorySchema);
|
22 |
+
|
23 |
+
module.exports = Category;
|
models/PromptGroup.js
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const mongoose = require('mongoose');
|
2 |
+
|
3 |
+
// 提示词 Schema
|
4 |
+
const promptSchema = new mongoose.Schema(
|
5 |
+
{
|
6 |
+
title: {
|
7 |
+
type: String,
|
8 |
+
required: [true, '请输入提示词标题'],
|
9 |
+
trim: true,
|
10 |
+
},
|
11 |
+
content: {
|
12 |
+
type: String,
|
13 |
+
required: [true, '请输入提示词内容'],
|
14 |
+
},
|
15 |
+
tags: [String],
|
16 |
+
createdAt: {
|
17 |
+
type: Date,
|
18 |
+
default: Date.now,
|
19 |
+
},
|
20 |
+
updatedAt: {
|
21 |
+
type: Date,
|
22 |
+
default: Date.now,
|
23 |
+
},
|
24 |
+
}
|
25 |
+
);
|
26 |
+
|
27 |
+
// DSL 文件 Schema
|
28 |
+
const dslFileSchema = new mongoose.Schema(
|
29 |
+
{
|
30 |
+
name: {
|
31 |
+
type: String,
|
32 |
+
required: [true, '请输入文件名称'],
|
33 |
+
trim: true,
|
34 |
+
},
|
35 |
+
fileData: {
|
36 |
+
type: String,
|
37 |
+
required: [true, '请提供文件数据'],
|
38 |
+
},
|
39 |
+
mimeType: {
|
40 |
+
type: String,
|
41 |
+
default: 'application/octet-stream',
|
42 |
+
},
|
43 |
+
uploadedAt: {
|
44 |
+
type: Date,
|
45 |
+
default: Date.now,
|
46 |
+
},
|
47 |
+
}
|
48 |
+
);
|
49 |
+
|
50 |
+
// 提示词组 Schema
|
51 |
+
const promptGroupSchema = new mongoose.Schema(
|
52 |
+
{
|
53 |
+
name: {
|
54 |
+
type: String,
|
55 |
+
required: [true, '请输入提示词组名称'],
|
56 |
+
trim: true,
|
57 |
+
},
|
58 |
+
description: {
|
59 |
+
type: String,
|
60 |
+
trim: true,
|
61 |
+
},
|
62 |
+
category: {
|
63 |
+
type: mongoose.Schema.Types.ObjectId,
|
64 |
+
ref: 'Category',
|
65 |
+
},
|
66 |
+
prompts: [promptSchema],
|
67 |
+
workflows: [String],
|
68 |
+
dslFiles: [dslFileSchema],
|
69 |
+
createdBy: {
|
70 |
+
type: mongoose.Schema.Types.ObjectId,
|
71 |
+
ref: 'User',
|
72 |
+
},
|
73 |
+
},
|
74 |
+
{
|
75 |
+
timestamps: true,
|
76 |
+
}
|
77 |
+
);
|
78 |
+
|
79 |
+
const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema);
|
80 |
+
|
81 |
+
module.exports = PromptGroup;
|
models/User.js
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const mongoose = require('mongoose');
|
2 |
+
const bcrypt = require('bcryptjs');
|
3 |
+
|
4 |
+
const userSchema = new mongoose.Schema(
|
5 |
+
{
|
6 |
+
username: {
|
7 |
+
type: String,
|
8 |
+
required: [true, '请输入用户名'],
|
9 |
+
unique: true,
|
10 |
+
trim: true,
|
11 |
+
},
|
12 |
+
password: {
|
13 |
+
type: String,
|
14 |
+
required: [true, '请输入密码'],
|
15 |
+
minlength: [6, '密码长度不能小于6个字符'],
|
16 |
+
},
|
17 |
+
isAdmin: {
|
18 |
+
type: Boolean,
|
19 |
+
default: false,
|
20 |
+
},
|
21 |
+
},
|
22 |
+
{
|
23 |
+
timestamps: true,
|
24 |
+
}
|
25 |
+
);
|
26 |
+
|
27 |
+
// 密码匹配方法
|
28 |
+
userSchema.methods.matchPassword = async function (enteredPassword) {
|
29 |
+
return await bcrypt.compare(enteredPassword, this.password);
|
30 |
+
};
|
31 |
+
|
32 |
+
// 保存前加密密码
|
33 |
+
userSchema.pre('save', async function (next) {
|
34 |
+
if (!this.isModified('password')) {
|
35 |
+
next();
|
36 |
+
}
|
37 |
+
|
38 |
+
const salt = await bcrypt.genSalt(10);
|
39 |
+
this.password = await bcrypt.hash(this.password, salt);
|
40 |
+
});
|
41 |
+
|
42 |
+
const User = mongoose.model('User', userSchema);
|
43 |
+
|
44 |
+
module.exports = User;
|
package.json
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "prompt-manager-backend",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"description": "Backend for Prompt Manager application",
|
5 |
+
"main": "server.js",
|
6 |
+
"scripts": {
|
7 |
+
"start": "node server.js",
|
8 |
+
"dev": "nodemon server.js",
|
9 |
+
"test": "echo \"Error: no test specified\" && exit 1"
|
10 |
+
},
|
11 |
+
"dependencies": {
|
12 |
+
"bcryptjs": "^2.4.3",
|
13 |
+
"cors": "^2.8.5",
|
14 |
+
"dotenv": "^16.0.3",
|
15 |
+
"express": "^4.18.2",
|
16 |
+
"express-async-handler": "^1.2.0",
|
17 |
+
"joi": "^17.9.2",
|
18 |
+
"jsonwebtoken": "^9.0.0",
|
19 |
+
"mongoose": "^7.2.0",
|
20 |
+
"morgan": "^1.10.0",
|
21 |
+
"winston": "^3.8.2"
|
22 |
+
},
|
23 |
+
"devDependencies": {
|
24 |
+
"nodemon": "^2.0.22"
|
25 |
+
},
|
26 |
+
"engines": {
|
27 |
+
"node": ">=14.0.0"
|
28 |
+
}
|
29 |
+
}
|
routes/authRoutes.js
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const express = require('express');
|
2 |
+
const router = express.Router();
|
3 |
+
const { registerUser, loginUser, getUserProfile } = require('../controllers/authController');
|
4 |
+
const { protect, admin } = require('../middleware/auth');
|
5 |
+
|
6 |
+
// 用户注册
|
7 |
+
router.post('/register', protect, admin, registerUser);
|
8 |
+
|
9 |
+
// 用户登录
|
10 |
+
router.post('/login', loginUser);
|
11 |
+
|
12 |
+
// 获取用户资料
|
13 |
+
router.get('/profile', protect, getUserProfile);
|
14 |
+
|
15 |
+
module.exports = router;
|
routes/categoryRoutes.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const express = require('express');
|
2 |
+
const router = express.Router();
|
3 |
+
const {
|
4 |
+
getCategories,
|
5 |
+
getCategoryById,
|
6 |
+
createCategory,
|
7 |
+
updateCategory,
|
8 |
+
deleteCategory,
|
9 |
+
} = require('../controllers/categoryController');
|
10 |
+
const { protect } = require('../middleware/auth');
|
11 |
+
|
12 |
+
router.route('/')
|
13 |
+
.get(protect, getCategories)
|
14 |
+
.post(protect, createCategory);
|
15 |
+
|
16 |
+
router.route('/:id')
|
17 |
+
.get(protect, getCategoryById)
|
18 |
+
.put(protect, updateCategory)
|
19 |
+
.delete(protect, deleteCategory);
|
20 |
+
|
21 |
+
module.exports = router;
|
routes/index.js
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const express = require('express');
|
2 |
+
const router = express.Router();
|
3 |
+
const authRoutes = require('./authRoutes');
|
4 |
+
const promptGroupRoutes = require('./promptGroupRoutes');
|
5 |
+
const categoryRoutes = require('./categoryRoutes');
|
6 |
+
|
7 |
+
// 注册路由
|
8 |
+
router.use('/auth', authRoutes);
|
9 |
+
router.use('/prompt-groups', promptGroupRoutes);
|
10 |
+
router.use('/categories', categoryRoutes);
|
11 |
+
|
12 |
+
module.exports = router;
|
routes/promptGroupRoutes.js
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const express = require('express');
|
2 |
+
const router = express.Router();
|
3 |
+
const {
|
4 |
+
getPromptGroups,
|
5 |
+
getPromptGroupById,
|
6 |
+
createPromptGroup,
|
7 |
+
updatePromptGroup,
|
8 |
+
deletePromptGroup,
|
9 |
+
addPromptToGroup,
|
10 |
+
updatePrompt,
|
11 |
+
deletePrompt,
|
12 |
+
addDslFileToGroup,
|
13 |
+
deleteDslFile,
|
14 |
+
} = require('../controllers/promptGroupController');
|
15 |
+
const { protect } = require('../middleware/auth');
|
16 |
+
|
17 |
+
// 提示词组路由
|
18 |
+
router.route('/')
|
19 |
+
.get(protect, getPromptGroups)
|
20 |
+
.post(protect, createPromptGroup);
|
21 |
+
|
22 |
+
router.route('/:id')
|
23 |
+
.get(protect, getPromptGroupById)
|
24 |
+
.put(protect, updatePromptGroup)
|
25 |
+
.delete(protect, deletePromptGroup);
|
26 |
+
|
27 |
+
// 提示词路由
|
28 |
+
router.route('/:id/prompts')
|
29 |
+
.post(protect, addPromptToGroup);
|
30 |
+
|
31 |
+
router.route('/:id/prompts/:promptId')
|
32 |
+
.put(protect, updatePrompt)
|
33 |
+
.delete(protect, deletePrompt);
|
34 |
+
|
35 |
+
// DSL文件路由
|
36 |
+
router.route('/:id/dsl-files')
|
37 |
+
.post(protect, addDslFileToGroup);
|
38 |
+
|
39 |
+
router.route('/:id/dsl-files/:fileId')
|
40 |
+
.delete(protect, deleteDslFile);
|
41 |
+
|
42 |
+
module.exports = router;
|
server.js
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const express = require('express');
|
2 |
+
const dotenv = require('dotenv');
|
3 |
+
const cors = require('cors');
|
4 |
+
const morgan = require('morgan');
|
5 |
+
const { connectDB } = require('./config/db');
|
6 |
+
const { errorHandler } = require('./middleware/errorHandler');
|
7 |
+
const routes = require('./routes');
|
8 |
+
const logger = require('./utils/logger');
|
9 |
+
|
10 |
+
// 加载环境变量
|
11 |
+
dotenv.config();
|
12 |
+
|
13 |
+
// 初始化 Express 应用
|
14 |
+
const app = express();
|
15 |
+
|
16 |
+
// 中间件
|
17 |
+
app.use(cors());
|
18 |
+
app.use(express.json());
|
19 |
+
app.use(express.urlencoded({ extended: true }));
|
20 |
+
|
21 |
+
// 日志中间件
|
22 |
+
if (process.env.NODE_ENV === 'development') {
|
23 |
+
app.use(morgan('dev'));
|
24 |
+
}
|
25 |
+
|
26 |
+
// 连接数据库
|
27 |
+
connectDB();
|
28 |
+
|
29 |
+
// 路由
|
30 |
+
app.use('/api', routes);
|
31 |
+
|
32 |
+
// 健康检查端点
|
33 |
+
app.get('/health', (req, res) => {
|
34 |
+
res.status(200).json({ status: 'ok', message: 'Server is running' });
|
35 |
+
});
|
36 |
+
|
37 |
+
// 错误处理中间件
|
38 |
+
app.use(errorHandler);
|
39 |
+
|
40 |
+
// 启动服务器
|
41 |
+
const PORT = process.env.PORT || 8080;
|
42 |
+
app.listen(PORT, () => {
|
43 |
+
logger.info(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
|
44 |
+
});
|
45 |
+
|
46 |
+
// 处理未捕获的异常
|
47 |
+
process.on('uncaughtException', (err) => {
|
48 |
+
logger.error('Uncaught Exception:', err);
|
49 |
+
process.exit(1);
|
50 |
+
});
|
51 |
+
|
52 |
+
// 处理未处理的 Promise 拒绝
|
53 |
+
process.on('unhandledRejection', (err) => {
|
54 |
+
logger.error('Unhandled Rejection:', err);
|
55 |
+
process.exit(1);
|
56 |
+
});
|
utils/logger.js
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const winston = require('winston');
|
2 |
+
|
3 |
+
// 定义日志格式
|
4 |
+
const logger = winston.createLogger({
|
5 |
+
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
|
6 |
+
format: winston.format.combine(
|
7 |
+
winston.format.timestamp({
|
8 |
+
format: 'YYYY-MM-DD HH:mm:ss',
|
9 |
+
}),
|
10 |
+
winston.format.errors({ stack: true }),
|
11 |
+
winston.format.splat(),
|
12 |
+
winston.format.json()
|
13 |
+
),
|
14 |
+
defaultMeta: { service: 'prompt-manager-api' },
|
15 |
+
transports: [
|
16 |
+
new winston.transports.Console({
|
17 |
+
format: winston.format.combine(
|
18 |
+
winston.format.colorize(),
|
19 |
+
winston.format.printf(
|
20 |
+
(info) => `${info.timestamp} ${info.level}: ${info.message}`
|
21 |
+
)
|
22 |
+
),
|
23 |
+
}),
|
24 |
+
],
|
25 |
+
});
|
26 |
+
|
27 |
+
// 如果是生产环境,记录到文件
|
28 |
+
if (process.env.NODE_ENV === 'production') {
|
29 |
+
logger.add(
|
30 |
+
new winston.transports.File({ filename: 'logs/error.log', level: 'error' })
|
31 |
+
);
|
32 |
+
logger.add(new winston.transports.File({ filename: 'logs/combined.log' }));
|
33 |
+
}
|
34 |
+
|
35 |
+
module.exports = logger;
|
utils/validators.js
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const Joi = require('joi');
|
2 |
+
|
3 |
+
// 用户验证模式
|
4 |
+
const userSchema = Joi.object({
|
5 |
+
username: Joi.string().required().min(3).max(30),
|
6 |
+
password: Joi.string().required().min(6),
|
7 |
+
});
|
8 |
+
|
9 |
+
// 分类验证模式
|
10 |
+
const categorySchema = Joi.object({
|
11 |
+
name: Joi.string().required().min(1).max(30),
|
12 |
+
color: Joi.string().pattern(new RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')),
|
13 |
+
});
|
14 |
+
|
15 |
+
// 提示词验证模式
|
16 |
+
const promptSchema = Joi.object({
|
17 |
+
title: Joi.string().required().min(1).max(100),
|
18 |
+
content: Joi.string().required(),
|
19 |
+
tags: Joi.array().items(Joi.string()),
|
20 |
+
});
|
21 |
+
|
22 |
+
// 提示词组验证模式
|
23 |
+
const promptGroupSchema = Joi.object({
|
24 |
+
name: Joi.string().required().min(1).max(100),
|
25 |
+
description: Joi.string().allow('', null),
|
26 |
+
category: Joi.string().pattern(new RegExp('^[0-9a-fA-F]{24}$')),
|
27 |
+
});
|
28 |
+
|
29 |
+
// DSL文件验证模式
|
30 |
+
const dslFileSchema = Joi.object({
|
31 |
+
name: Joi.string().required(),
|
32 |
+
fileData: Joi.string().required(),
|
33 |
+
mimeType: Joi.string(),
|
34 |
+
});
|
35 |
+
|
36 |
+
module.exports = {
|
37 |
+
userSchema,
|
38 |
+
categorySchema,
|
39 |
+
promptSchema,
|
40 |
+
promptGroupSchema,
|
41 |
+
dslFileSchema,
|
42 |
+
};
|