collabdocchat 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,317 @@
1
+ import express from 'express';
2
+ import Group from '../models/Group.js';
3
+ import User from '../models/User.js';
4
+ import Message from '../models/Message.js';
5
+ import { authenticate, isAdmin } from '../middleware/auth.js';
6
+
7
+ const router = express.Router();
8
+
9
+ // 创建群组(仅管理员)
10
+ router.post('/', authenticate, isAdmin, async (req, res) => {
11
+ try {
12
+ const { name, description, members } = req.body;
13
+
14
+ const group = new Group({
15
+ name,
16
+ description,
17
+ admin: req.user.userId,
18
+ members: [req.user.userId, ...(members || [])] // 管理员 + 选中的成员
19
+ });
20
+
21
+ await group.save();
22
+
23
+ // 更新用户的群组列表
24
+ await User.updateMany(
25
+ { _id: { $in: group.members } },
26
+ { $push: { groups: group._id } }
27
+ );
28
+
29
+ res.status(201).json({ message: '群组创建成功', group });
30
+ } catch (error) {
31
+ res.status(500).json({ message: '创建群组失败', error: error.message });
32
+ }
33
+ });
34
+
35
+ // 获取所有群组(包括未加入的)
36
+ router.get('/all', authenticate, async (req, res) => {
37
+ try {
38
+ const groups = await Group.find({})
39
+ .populate('admin', 'username avatar')
40
+ .populate('members', 'username avatar isOnline');
41
+
42
+ res.json({ groups });
43
+ } catch (error) {
44
+ res.status(500).json({ message: '获取群组失败', error: error.message });
45
+ }
46
+ });
47
+
48
+ // 获取用户的所有群组
49
+ router.get('/', authenticate, async (req, res) => {
50
+ try {
51
+ const groups = await Group.find({ members: req.user.userId })
52
+ .populate('admin', 'username avatar')
53
+ .populate('members', 'username avatar isOnline');
54
+
55
+ res.json({ groups });
56
+ } catch (error) {
57
+ res.status(500).json({ message: '获取群组失败', error: error.message });
58
+ }
59
+ });
60
+
61
+ // 获取群组详情
62
+ router.get('/:id', authenticate, async (req, res) => {
63
+ try {
64
+ const group = await Group.findById(req.params.id)
65
+ .populate('admin', 'username avatar')
66
+ .populate('members', 'username avatar isOnline')
67
+ .populate('documents')
68
+ .populate('tasks');
69
+
70
+ if (!group) {
71
+ return res.status(404).json({ message: '群组不存在' });
72
+ }
73
+
74
+ res.json({ group });
75
+ } catch (error) {
76
+ res.status(500).json({ message: '获取群组详情失败', error: error.message });
77
+ }
78
+ });
79
+
80
+ // 用户加入群组
81
+ router.post('/:id/join', authenticate, async (req, res) => {
82
+ try {
83
+ const group = await Group.findById(req.params.id);
84
+
85
+ if (!group) {
86
+ return res.status(404).json({ message: '群组不存在' });
87
+ }
88
+
89
+ if (group.members.includes(req.user.userId)) {
90
+ return res.status(400).json({ message: '您已在该群组中' });
91
+ }
92
+
93
+ group.members.push(req.user.userId);
94
+ await group.save();
95
+
96
+ await User.findByIdAndUpdate(req.user.userId, { $push: { groups: group._id } });
97
+
98
+ res.json({ message: '加入群组成功', group });
99
+ } catch (error) {
100
+ res.status(500).json({ message: '加入群组失败', error: error.message });
101
+ }
102
+ });
103
+
104
+ // 用户退出群组
105
+ router.post('/:id/leave', authenticate, async (req, res) => {
106
+ try {
107
+ const group = await Group.findById(req.params.id);
108
+
109
+ if (!group) {
110
+ return res.status(404).json({ message: '群组不存在' });
111
+ }
112
+
113
+ if (group.admin.toString() === req.user.userId) {
114
+ return res.status(400).json({ message: '管理员不能退出群组' });
115
+ }
116
+
117
+ if (!group.members.includes(req.user.userId)) {
118
+ return res.status(400).json({ message: '您不在该群组中' });
119
+ }
120
+
121
+ group.members = group.members.filter(m => m.toString() !== req.user.userId);
122
+ await group.save();
123
+
124
+ await User.findByIdAndUpdate(req.user.userId, { $pull: { groups: group._id } });
125
+
126
+ res.json({ message: '退出群组成功' });
127
+ } catch (error) {
128
+ res.status(500).json({ message: '退出群组失败', error: error.message });
129
+ }
130
+ });
131
+
132
+ // 添加成员(仅管理员)
133
+ router.post('/:id/members', authenticate, isAdmin, async (req, res) => {
134
+ try {
135
+ const { userId } = req.body;
136
+ const group = await Group.findById(req.params.id);
137
+
138
+ if (!group) {
139
+ return res.status(404).json({ message: '群组不存在' });
140
+ }
141
+
142
+ if (group.members.includes(userId)) {
143
+ return res.status(400).json({ message: '用户已在群组中' });
144
+ }
145
+
146
+ group.members.push(userId);
147
+ await group.save();
148
+
149
+ await User.findByIdAndUpdate(userId, { $push: { groups: group._id } });
150
+
151
+ res.json({ message: '成员添加成功', group });
152
+ } catch (error) {
153
+ res.status(500).json({ message: '添加成员失败', error: error.message });
154
+ }
155
+ });
156
+
157
+ // 移除成员(仅管理员)
158
+ router.delete('/:id/members/:userId', authenticate, isAdmin, async (req, res) => {
159
+ try {
160
+ const group = await Group.findById(req.params.id);
161
+
162
+ if (!group) {
163
+ return res.status(404).json({ message: '群组不存在' });
164
+ }
165
+
166
+ if (group.admin.toString() === req.params.userId) {
167
+ return res.status(400).json({ message: '不能移除管理员' });
168
+ }
169
+
170
+ group.members = group.members.filter(m => m.toString() !== req.params.userId);
171
+ await group.save();
172
+
173
+ await User.findByIdAndUpdate(req.params.userId, { $pull: { groups: group._id } });
174
+
175
+ res.json({ message: '成员移除成功' });
176
+ } catch (error) {
177
+ res.status(500).json({ message: '移除成员失败', error: error.message });
178
+ }
179
+ });
180
+
181
+ // 全体禁言(仅管理员) enabled=true -> 除管理员外都不能发言
182
+ router.post('/:id/mute/all', authenticate, isAdmin, async (req, res) => {
183
+ try {
184
+ const { enabled } = req.body || {};
185
+ const group = await Group.findById(req.params.id);
186
+
187
+ if (!group) {
188
+ return res.status(404).json({ message: '群组不存在' });
189
+ }
190
+
191
+ group.mutedAll = Boolean(enabled);
192
+ await group.save();
193
+
194
+ res.json({
195
+ message: group.mutedAll ? '已开启全体禁言' : '已取消全体禁言',
196
+ mutedAll: group.mutedAll
197
+ });
198
+ } catch (error) {
199
+ res.status(500).json({ message: '设置全体禁言失败', error: error.message });
200
+ }
201
+ });
202
+
203
+ // 个人禁言(仅管理员) muted=true/false
204
+ router.post('/:id/mute/users/:userId', authenticate, isAdmin, async (req, res) => {
205
+ try {
206
+ const { muted } = req.body || {};
207
+ const group = await Group.findById(req.params.id);
208
+
209
+ if (!group) {
210
+ return res.status(404).json({ message: '群组不存在' });
211
+ }
212
+
213
+ if (group.admin.toString() === req.params.userId) {
214
+ return res.status(400).json({ message: '不能禁言管理员' });
215
+ }
216
+
217
+ const targetUser = await User.findById(req.params.userId).select('_id');
218
+ if (!targetUser) {
219
+ return res.status(404).json({ message: '用户不存在' });
220
+ }
221
+
222
+ const isInGroup = group.members.some(m => m.toString() === req.params.userId);
223
+ if (!isInGroup) {
224
+ return res.status(400).json({ message: '该用户不在群组中' });
225
+ }
226
+
227
+ const alreadyMuted = group.mutedUsers.some(m => m.toString() === req.params.userId);
228
+
229
+ if (Boolean(muted)) {
230
+ if (!alreadyMuted) group.mutedUsers.push(targetUser._id);
231
+ } else {
232
+ group.mutedUsers = group.mutedUsers.filter(m => m.toString() !== req.params.userId);
233
+ }
234
+
235
+ await group.save();
236
+
237
+ res.json({
238
+ message: Boolean(muted) ? '已禁言该成员' : '已取消禁言',
239
+ mutedUsers: group.mutedUsers.map(m => m.toString())
240
+ });
241
+ } catch (error) {
242
+ res.status(500).json({ message: '设置个人禁言失败', error: error.message });
243
+ }
244
+ });
245
+
246
+ // 随机点名(仅管理员)
247
+ router.post('/:id/call', authenticate, isAdmin, async (req, res) => {
248
+ try {
249
+ const { count = 1 } = req.body;
250
+ const group = await Group.findById(req.params.id).populate('members', 'username avatar');
251
+
252
+ if (!group) {
253
+ return res.status(404).json({ message: '群组不存在' });
254
+ }
255
+
256
+ // 排除管理员,随机选择成员
257
+ const eligibleMembers = group.members.filter(
258
+ member => member._id.toString() !== group.admin.toString()
259
+ );
260
+
261
+ if (eligibleMembers.length === 0) {
262
+ return res.status(400).json({ message: '没有可点名的成员' });
263
+ }
264
+
265
+ const calledCount = Math.min(count, eligibleMembers.length);
266
+ const shuffled = eligibleMembers.sort(() => 0.5 - Math.random());
267
+ const calledMembers = shuffled.slice(0, calledCount);
268
+
269
+ // 记录点名历史
270
+ calledMembers.forEach(member => {
271
+ group.callHistory.push({
272
+ calledUser: member._id,
273
+ timestamp: new Date(),
274
+ responded: false
275
+ });
276
+ });
277
+
278
+ await group.save();
279
+
280
+ res.json({
281
+ message: '点名成功',
282
+ calledMembers: calledMembers.map(m => ({
283
+ id: m._id,
284
+ username: m.username,
285
+ avatar: m.avatar
286
+ }))
287
+ });
288
+ } catch (error) {
289
+ res.status(500).json({ message: '点名失败', error: error.message });
290
+ }
291
+ });
292
+
293
+ // 获取群组历史消息
294
+ router.get('/:id/messages', authenticate, async (req, res) => {
295
+ try {
296
+ const group = await Group.findById(req.params.id);
297
+ if (!group) {
298
+ return res.status(404).json({ message: '群组不存在' });
299
+ }
300
+
301
+ // 检查用户是否在群组中
302
+ if (!group.members.includes(req.user.userId)) {
303
+ return res.status(403).json({ message: '您不在该群组中' });
304
+ }
305
+
306
+ const messages = await Message.find({ group: req.params.id })
307
+ .sort({ createdAt: 1 }) // 按时间正序排列
308
+ .limit(100); // 限制最近 100 条
309
+
310
+ res.json({ messages });
311
+ } catch (error) {
312
+ res.status(500).json({ message: '获取消息失败', error: error.message });
313
+ }
314
+ });
315
+
316
+ export default router;
317
+
@@ -0,0 +1,110 @@
1
+ import express from 'express';
2
+ import Task from '../models/Task.js';
3
+ import Group from '../models/Group.js';
4
+ import { authenticate, isAdmin } from '../middleware/auth.js';
5
+
6
+ const router = express.Router();
7
+
8
+ // 创建任务(仅管理员)
9
+ router.post('/', authenticate, isAdmin, async (req, res) => {
10
+ try {
11
+ const { title, description, groupId, assignedTo, relatedDocument, deadline } = req.body;
12
+
13
+ const task = new Task({
14
+ title,
15
+ description,
16
+ group: groupId,
17
+ creator: req.user.userId,
18
+ assignedTo,
19
+ relatedDocument,
20
+ deadline
21
+ });
22
+
23
+ await task.save();
24
+
25
+ // 更新群组的任务列表
26
+ await Group.findByIdAndUpdate(groupId, { $push: { tasks: task._id } });
27
+
28
+ res.status(201).json({ message: '任务创建成功', task });
29
+ } catch (error) {
30
+ res.status(500).json({ message: '创建任务失败', error: error.message });
31
+ }
32
+ });
33
+
34
+ // 获取群组的所有任务
35
+ router.get('/group/:groupId', authenticate, async (req, res) => {
36
+ try {
37
+ const tasks = await Task.find({ group: req.params.groupId })
38
+ .populate('creator', 'username avatar')
39
+ .populate('assignedTo', 'username avatar')
40
+ .populate('relatedDocument', 'title')
41
+ .sort({ createdAt: -1 });
42
+
43
+ res.json({ tasks });
44
+ } catch (error) {
45
+ res.status(500).json({ message: '获取任务失败', error: error.message });
46
+ }
47
+ });
48
+
49
+ // 获取用户的任务
50
+ router.get('/my', authenticate, async (req, res) => {
51
+ try {
52
+ const tasks = await Task.find({ assignedTo: req.user.userId })
53
+ .populate('creator', 'username avatar')
54
+ .populate('group', 'name')
55
+ .populate('relatedDocument', 'title')
56
+ .sort({ createdAt: -1 });
57
+
58
+ res.json({ tasks });
59
+ } catch (error) {
60
+ res.status(500).json({ message: '获取任务失败', error: error.message });
61
+ }
62
+ });
63
+
64
+ // 更新任务状态
65
+ router.patch('/:id/status', authenticate, async (req, res) => {
66
+ try {
67
+ const { status } = req.body;
68
+ const task = await Task.findById(req.params.id);
69
+
70
+ if (!task) {
71
+ return res.status(404).json({ message: '任务不存在' });
72
+ }
73
+
74
+ task.status = status;
75
+
76
+ if (status === 'completed') {
77
+ task.completedBy.push({
78
+ user: req.user.userId,
79
+ completedAt: new Date()
80
+ });
81
+ }
82
+
83
+ await task.save();
84
+
85
+ res.json({ message: '任务状态更新成功', task });
86
+ } catch (error) {
87
+ res.status(500).json({ message: '更新任务状态失败', error: error.message });
88
+ }
89
+ });
90
+
91
+ // 删除任务(仅管理员)
92
+ router.delete('/:id', authenticate, isAdmin, async (req, res) => {
93
+ try {
94
+ const task = await Task.findByIdAndDelete(req.params.id);
95
+
96
+ if (!task) {
97
+ return res.status(404).json({ message: '任务不存在' });
98
+ }
99
+
100
+ await Group.findByIdAndUpdate(task.group, { $pull: { tasks: task._id } });
101
+
102
+ res.json({ message: '任务删除成功' });
103
+ } catch (error) {
104
+ res.status(500).json({ message: '删除任务失败', error: error.message });
105
+ }
106
+ });
107
+
108
+ export default router;
109
+
110
+
@@ -0,0 +1,238 @@
1
+ import AuditLog from '../models/AuditLog.js';
2
+
3
+ /**
4
+ * 计算文本差异
5
+ * @param {string} oldText - 原始文本
6
+ * @param {string} newText - 新文本
7
+ * @returns {Object} 包含插入和删除操作的对象
8
+ */
9
+ function calculateTextDiff(oldText = '', newText = '') {
10
+ const insertions = [];
11
+ const deletions = [];
12
+
13
+ // 简单的差异计算算法
14
+ // 这里使用基础实现,生产环境可以使用更复杂的diff算法
15
+ if (oldText !== newText) {
16
+ // 如果完全不同,记录为完全替换
17
+ if (oldText.length > 0) {
18
+ deletions.push({
19
+ position: 0,
20
+ content: oldText,
21
+ length: oldText.length
22
+ });
23
+ }
24
+
25
+ if (newText.length > 0) {
26
+ insertions.push({
27
+ position: 0,
28
+ content: newText,
29
+ length: newText.length
30
+ });
31
+ }
32
+ }
33
+
34
+ return { insertions, deletions };
35
+ }
36
+
37
+ /**
38
+ * 记录审计日志
39
+ * @param {Object} logData - 日志数据
40
+ * @param {Object} req - Express请求对象(可选,用于获取IP等信息)
41
+ */
42
+ export async function logAuditAction(logData, req = null) {
43
+ try {
44
+ const auditData = {
45
+ action: logData.action,
46
+ user: logData.userId,
47
+ resourceType: logData.resourceType,
48
+ resourceId: logData.resourceId,
49
+ resourceTitle: logData.resourceTitle || '',
50
+ details: {
51
+ oldValue: logData.oldValue,
52
+ newValue: logData.newValue,
53
+ field: logData.field,
54
+ description: logData.description
55
+ },
56
+ metadata: {
57
+ groupId: logData.groupId
58
+ }
59
+ };
60
+
61
+ // 如果是内容编辑,计算文本差异
62
+ if (logData.action === 'content_edit' && logData.oldValue && logData.newValue) {
63
+ auditData.changes = calculateTextDiff(logData.oldValue, logData.newValue);
64
+ }
65
+
66
+ // 从请求中提取元数据
67
+ if (req) {
68
+ auditData.metadata.ipAddress = req.ip || req.connection.remoteAddress;
69
+ auditData.metadata.userAgent = req.get('User-Agent');
70
+ }
71
+
72
+ const auditLog = new AuditLog(auditData);
73
+ await auditLog.save();
74
+
75
+ return auditLog;
76
+ } catch (error) {
77
+ console.error('记录审计日志失败:', error);
78
+ // 审计日志记录失败不应该影响主业务逻辑
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 批量记录审计日志
85
+ * @param {Array} logDataArray - 日志数据数组
86
+ * @param {Object} req - Express请求对象
87
+ */
88
+ export async function logMultipleAuditActions(logDataArray, req = null) {
89
+ const promises = logDataArray.map(logData => logAuditAction(logData, req));
90
+ return Promise.allSettled(promises);
91
+ }
92
+
93
+ /**
94
+ * 查询审计日志
95
+ * @param {Object} filters - 查询过滤条件
96
+ * @param {Object} options - 查询选项(分页、排序等)
97
+ */
98
+ export async function queryAuditLogs(filters = {}, options = {}) {
99
+ try {
100
+ const {
101
+ page = 1,
102
+ limit = 50,
103
+ sortBy = 'createdAt',
104
+ sortOrder = -1
105
+ } = options;
106
+
107
+ const query = {};
108
+
109
+ // 构建查询条件
110
+ if (filters.userId) {
111
+ query.user = filters.userId;
112
+ }
113
+
114
+ if (filters.resourceId) {
115
+ query.resourceId = filters.resourceId;
116
+ }
117
+
118
+ if (filters.resourceType) {
119
+ query.resourceType = filters.resourceType;
120
+ }
121
+
122
+ if (filters.action) {
123
+ query.action = filters.action;
124
+ }
125
+
126
+ if (filters.groupId) {
127
+ query['metadata.groupId'] = filters.groupId;
128
+ }
129
+
130
+ if (filters.startDate && filters.endDate) {
131
+ query.createdAt = {
132
+ $gte: new Date(filters.startDate),
133
+ $lte: new Date(filters.endDate)
134
+ };
135
+ } else if (filters.startDate) {
136
+ query.createdAt = { $gte: new Date(filters.startDate) };
137
+ } else if (filters.endDate) {
138
+ query.createdAt = { $lte: new Date(filters.endDate) };
139
+ }
140
+
141
+ const skip = (page - 1) * limit;
142
+
143
+ const [logs, total] = await Promise.all([
144
+ AuditLog.find(query)
145
+ .populate('user', 'username email avatar')
146
+ .populate('metadata.groupId', 'name')
147
+ .sort({ [sortBy]: sortOrder })
148
+ .skip(skip)
149
+ .limit(limit)
150
+ .lean(),
151
+ AuditLog.countDocuments(query)
152
+ ]);
153
+
154
+ return {
155
+ logs,
156
+ pagination: {
157
+ page,
158
+ limit,
159
+ total,
160
+ pages: Math.ceil(total / limit)
161
+ }
162
+ };
163
+ } catch (error) {
164
+ console.error('查询审计日志失败:', error);
165
+ throw new Error('查询审计日志失败');
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 获取用户活动统计
171
+ * @param {string} userId - 用户ID
172
+ * @param {Object} timeRange - 时间范围
173
+ */
174
+ export async function getUserActivityStats(userId, timeRange = {}) {
175
+ try {
176
+ const { startDate, endDate } = timeRange;
177
+ const matchStage = { user: userId };
178
+
179
+ if (startDate && endDate) {
180
+ matchStage.createdAt = {
181
+ $gte: new Date(startDate),
182
+ $lte: new Date(endDate)
183
+ };
184
+ }
185
+
186
+ const stats = await AuditLog.aggregate([
187
+ { $match: matchStage },
188
+ {
189
+ $group: {
190
+ _id: '$action',
191
+ count: { $sum: 1 },
192
+ lastActivity: { $max: '$createdAt' }
193
+ }
194
+ },
195
+ { $sort: { count: -1 } }
196
+ ]);
197
+
198
+ const totalActions = stats.reduce((sum, stat) => sum + stat.count, 0);
199
+
200
+ return {
201
+ totalActions,
202
+ actionBreakdown: stats,
203
+ timeRange
204
+ };
205
+ } catch (error) {
206
+ console.error('获取用户活动统计失败:', error);
207
+ throw new Error('获取用户活动统计失败');
208
+ }
209
+ }
210
+
211
+ /**
212
+ * 获取文档编辑历史摘要
213
+ * @param {string} documentId - 文档ID
214
+ * @param {number} limit - 返回记录数限制
215
+ */
216
+ export async function getDocumentEditHistory(documentId, limit = 20) {
217
+ try {
218
+ const history = await AuditLog.find({
219
+ resourceId: documentId,
220
+ resourceType: 'document',
221
+ action: { $in: ['content_edit', 'title_edit', 'document_update'] }
222
+ })
223
+ .populate('user', 'username avatar')
224
+ .sort({ createdAt: -1 })
225
+ .limit(limit)
226
+ .lean();
227
+
228
+ return history;
229
+ } catch (error) {
230
+ console.error('获取文档编辑历史失败:', error);
231
+ throw new Error('获取文档编辑历史失败');
232
+ }
233
+ }
234
+
235
+
236
+
237
+
238
+
@@ -0,0 +1,51 @@
1
+ import User from '../models/User.js';
2
+
3
+ /**
4
+ * 初始化默认管理员账户
5
+ */
6
+ export async function initDefaultAdmin() {
7
+ try {
8
+ // 检查是否已存在管理员账户
9
+ const existingAdmin = await User.findOne({ role: 'admin' });
10
+
11
+ if (existingAdmin) {
12
+ console.log('✅ 管理员账户已存在:', existingAdmin.username);
13
+ return;
14
+ }
15
+
16
+ // 创建默认管理员账户
17
+ const defaultAdmin = new User({
18
+ username: 'admin',
19
+ email: 'admin@local.system',
20
+ password: 'admin123',
21
+ role: 'admin'
22
+ });
23
+
24
+ await defaultAdmin.save();
25
+ console.log('🎉 默认管理员账户创建成功!');
26
+ console.log('👤 用户名: admin');
27
+ console.log('🔐 密码: admin123');
28
+ console.log('🛡️ 角色: 管理员');
29
+
30
+ } catch (error) {
31
+ console.error('❌ 创建默认管理员失败:', error.message);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * 检查是否存在管理员账户
37
+ */
38
+ export async function checkAdminExists() {
39
+ try {
40
+ const adminCount = await User.countDocuments({ role: 'admin' });
41
+ return adminCount > 0;
42
+ } catch (error) {
43
+ console.error('检查管理员账户失败:', error);
44
+ return false;
45
+ }
46
+ }
47
+
48
+
49
+
50
+
51
+