samlax12 commited on
Commit
4c025e9
·
verified ·
1 Parent(s): 33ceeb8

Upload 19 files

Browse files
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
+ };