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,90 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const auditLogSchema = new mongoose.Schema({
4
+ action: {
5
+ type: String,
6
+ required: true,
7
+ enum: [
8
+ 'content_edit',
9
+ 'title_edit',
10
+ 'document_create',
11
+ 'document_update',
12
+ 'document_delete',
13
+ 'document_share',
14
+ 'user_login',
15
+ 'user_logout',
16
+ 'user_register',
17
+ 'group_create',
18
+ 'group_update',
19
+ 'group_delete',
20
+ 'member_add',
21
+ 'member_remove',
22
+ 'permission_change',
23
+ 'task_create',
24
+ 'task_update',
25
+ 'task_delete',
26
+ 'comment_create',
27
+ 'comment_update',
28
+ 'comment_delete'
29
+ ]
30
+ },
31
+ user: {
32
+ type: mongoose.Schema.Types.ObjectId,
33
+ ref: 'User',
34
+ required: true
35
+ },
36
+ resourceType: {
37
+ type: String,
38
+ required: true,
39
+ enum: ['document', 'group', 'user', 'task', 'comment', 'message']
40
+ },
41
+ resourceId: {
42
+ type: mongoose.Schema.Types.ObjectId,
43
+ required: true
44
+ },
45
+ resourceTitle: {
46
+ type: String,
47
+ default: ''
48
+ },
49
+ details: {
50
+ oldValue: mongoose.Schema.Types.Mixed,
51
+ newValue: mongoose.Schema.Types.Mixed,
52
+ field: String,
53
+ description: String
54
+ },
55
+ changes: {
56
+ insertions: [{
57
+ position: Number,
58
+ content: String,
59
+ length: Number
60
+ }],
61
+ deletions: [{
62
+ position: Number,
63
+ content: String,
64
+ length: Number
65
+ }]
66
+ },
67
+ metadata: {
68
+ groupId: {
69
+ type: mongoose.Schema.Types.ObjectId,
70
+ ref: 'Group'
71
+ },
72
+ ipAddress: String,
73
+ userAgent: String
74
+ }
75
+ }, {
76
+ timestamps: true
77
+ });
78
+
79
+ // 索引优化查询性能
80
+ auditLogSchema.index({ user: 1, createdAt: -1 });
81
+ auditLogSchema.index({ resourceId: 1, resourceType: 1, createdAt: -1 });
82
+ auditLogSchema.index({ action: 1, createdAt: -1 });
83
+ auditLogSchema.index({ 'metadata.groupId': 1, createdAt: -1 });
84
+
85
+ export default mongoose.model('AuditLog', auditLogSchema);
86
+
87
+
88
+
89
+
90
+
@@ -0,0 +1,59 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const documentSchema = new mongoose.Schema({
4
+ title: {
5
+ type: String,
6
+ required: true,
7
+ trim: true
8
+ },
9
+ content: {
10
+ type: String,
11
+ default: ''
12
+ },
13
+ group: {
14
+ type: mongoose.Schema.Types.ObjectId,
15
+ ref: 'Group',
16
+ required: true
17
+ },
18
+ creator: {
19
+ type: mongoose.Schema.Types.ObjectId,
20
+ ref: 'User',
21
+ required: true
22
+ },
23
+ permission: {
24
+ type: String,
25
+ enum: ['readonly', 'editable'],
26
+ default: 'editable'
27
+ },
28
+ editors: [{
29
+ user: {
30
+ type: mongoose.Schema.Types.ObjectId,
31
+ ref: 'User'
32
+ },
33
+ lastEdit: {
34
+ type: Date,
35
+ default: Date.now
36
+ }
37
+ }],
38
+ versions: [{
39
+ content: String,
40
+ editor: {
41
+ type: mongoose.Schema.Types.ObjectId,
42
+ ref: 'User'
43
+ },
44
+ timestamp: {
45
+ type: Date,
46
+ default: Date.now
47
+ }
48
+ }],
49
+ yjsState: {
50
+ type: Buffer,
51
+ default: null
52
+ }
53
+ }, {
54
+ timestamps: true
55
+ });
56
+
57
+ export default mongoose.model('Document', documentSchema);
58
+
59
+
@@ -0,0 +1,43 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const fileSchema = new mongoose.Schema({
4
+ filename: {
5
+ type: String,
6
+ required: true
7
+ },
8
+ originalName: {
9
+ type: String,
10
+ required: true
11
+ },
12
+ path: {
13
+ type: String,
14
+ required: true
15
+ },
16
+ mimetype: {
17
+ type: String,
18
+ required: true
19
+ },
20
+ size: {
21
+ type: Number,
22
+ required: true
23
+ },
24
+ group: {
25
+ type: mongoose.Schema.Types.ObjectId,
26
+ ref: 'Group',
27
+ required: true
28
+ },
29
+ uploader: {
30
+ type: mongoose.Schema.Types.ObjectId,
31
+ ref: 'User',
32
+ required: true
33
+ },
34
+ description: {
35
+ type: String,
36
+ default: ''
37
+ }
38
+ }, {
39
+ timestamps: true
40
+ });
41
+
42
+ export default mongoose.model('File', fileSchema);
43
+
@@ -0,0 +1,61 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const groupSchema = new mongoose.Schema({
4
+ name: {
5
+ type: String,
6
+ required: true,
7
+ trim: true
8
+ },
9
+ description: {
10
+ type: String,
11
+ default: ''
12
+ },
13
+ admin: {
14
+ type: mongoose.Schema.Types.ObjectId,
15
+ ref: 'User',
16
+ required: true
17
+ },
18
+ members: [{
19
+ type: mongoose.Schema.Types.ObjectId,
20
+ ref: 'User'
21
+ }],
22
+ documents: [{
23
+ type: mongoose.Schema.Types.ObjectId,
24
+ ref: 'Document'
25
+ }],
26
+ files: [{
27
+ type: mongoose.Schema.Types.ObjectId,
28
+ ref: 'File'
29
+ }],
30
+ tasks: [{
31
+ type: mongoose.Schema.Types.ObjectId,
32
+ ref: 'Task'
33
+ }],
34
+ mutedAll: {
35
+ type: Boolean,
36
+ default: false
37
+ },
38
+ mutedUsers: [{
39
+ type: mongoose.Schema.Types.ObjectId,
40
+ ref: 'User'
41
+ }],
42
+ callHistory: [{
43
+ calledUser: {
44
+ type: mongoose.Schema.Types.ObjectId,
45
+ ref: 'User'
46
+ },
47
+ timestamp: {
48
+ type: Date,
49
+ default: Date.now
50
+ },
51
+ responded: {
52
+ type: Boolean,
53
+ default: false
54
+ }
55
+ }]
56
+ }, {
57
+ timestamps: true
58
+ });
59
+
60
+ export default mongoose.model('Group', groupSchema);
61
+
@@ -0,0 +1,31 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const messageSchema = new mongoose.Schema({
4
+ group: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'Group',
7
+ required: true
8
+ },
9
+ sender: {
10
+ type: mongoose.Schema.Types.ObjectId,
11
+ ref: 'User',
12
+ required: true
13
+ },
14
+ username: {
15
+ type: String,
16
+ required: true
17
+ },
18
+ content: {
19
+ type: String,
20
+ required: true
21
+ },
22
+ timestamp: {
23
+ type: Date,
24
+ default: Date.now
25
+ }
26
+ }, {
27
+ timestamps: true
28
+ });
29
+
30
+ export default mongoose.model('Message', messageSchema);
31
+
@@ -0,0 +1,55 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const taskSchema = new mongoose.Schema({
4
+ title: {
5
+ type: String,
6
+ required: true,
7
+ trim: true
8
+ },
9
+ description: {
10
+ type: String,
11
+ default: ''
12
+ },
13
+ group: {
14
+ type: mongoose.Schema.Types.ObjectId,
15
+ ref: 'Group',
16
+ required: true
17
+ },
18
+ creator: {
19
+ type: mongoose.Schema.Types.ObjectId,
20
+ ref: 'User',
21
+ required: true
22
+ },
23
+ assignedTo: [{
24
+ type: mongoose.Schema.Types.ObjectId,
25
+ ref: 'User'
26
+ }],
27
+ relatedDocument: {
28
+ type: mongoose.Schema.Types.ObjectId,
29
+ ref: 'Document'
30
+ },
31
+ status: {
32
+ type: String,
33
+ enum: ['pending', 'in_progress', 'completed', 'terminated'],
34
+ default: 'pending'
35
+ },
36
+ deadline: {
37
+ type: Date
38
+ },
39
+ completedBy: [{
40
+ user: {
41
+ type: mongoose.Schema.Types.ObjectId,
42
+ ref: 'User'
43
+ },
44
+ completedAt: {
45
+ type: Date,
46
+ default: Date.now
47
+ }
48
+ }]
49
+ }, {
50
+ timestamps: true
51
+ });
52
+
53
+ export default mongoose.model('Task', taskSchema);
54
+
55
+
@@ -0,0 +1,60 @@
1
+ import mongoose from 'mongoose';
2
+ import bcrypt from 'bcryptjs';
3
+
4
+ const userSchema = new mongoose.Schema({
5
+ username: {
6
+ type: String,
7
+ required: true,
8
+ unique: true,
9
+ trim: true
10
+ },
11
+ email: {
12
+ type: String,
13
+ required: true,
14
+ unique: true,
15
+ lowercase: true
16
+ },
17
+ password: {
18
+ type: String,
19
+ required: true
20
+ },
21
+ role: {
22
+ type: String,
23
+ enum: ['admin', 'user'],
24
+ default: 'user'
25
+ },
26
+ avatar: {
27
+ type: String,
28
+ default: ''
29
+ },
30
+ groups: [{
31
+ type: mongoose.Schema.Types.ObjectId,
32
+ ref: 'Group'
33
+ }],
34
+ isOnline: {
35
+ type: Boolean,
36
+ default: false
37
+ },
38
+ lastSeen: {
39
+ type: Date,
40
+ default: Date.now
41
+ }
42
+ }, {
43
+ timestamps: true
44
+ });
45
+
46
+ // 密码加密
47
+ userSchema.pre('save', async function(next) {
48
+ if (!this.isModified('password')) return next();
49
+ this.password = await bcrypt.hash(this.password, 10);
50
+ next();
51
+ });
52
+
53
+ // 密码验证
54
+ userSchema.methods.comparePassword = async function(candidatePassword) {
55
+ return await bcrypt.compare(candidatePassword, this.password);
56
+ };
57
+
58
+ export default mongoose.model('User', userSchema);
59
+
60
+
@@ -0,0 +1,210 @@
1
+ import express from 'express';
2
+ import { authenticate, isAdmin } from '../middleware/auth.js';
3
+ import {
4
+ queryAuditLogs,
5
+ getUserActivityStats,
6
+ getDocumentEditHistory
7
+ } from '../utils/auditLogger.js';
8
+
9
+ const router = express.Router();
10
+
11
+ // 获取审计日志列表(仅管理员)
12
+ router.get('/', authenticate, isAdmin, async (req, res) => {
13
+ try {
14
+ const {
15
+ page = 1,
16
+ limit = 50,
17
+ userId,
18
+ resourceId,
19
+ resourceType,
20
+ action,
21
+ groupId,
22
+ startDate,
23
+ endDate,
24
+ sortBy = 'createdAt',
25
+ sortOrder = -1
26
+ } = req.query;
27
+
28
+ const filters = {};
29
+ if (userId) filters.userId = userId;
30
+ if (resourceId) filters.resourceId = resourceId;
31
+ if (resourceType) filters.resourceType = resourceType;
32
+ if (action) filters.action = action;
33
+ if (groupId) filters.groupId = groupId;
34
+ if (startDate) filters.startDate = startDate;
35
+ if (endDate) filters.endDate = endDate;
36
+
37
+ const options = {
38
+ page: parseInt(page),
39
+ limit: parseInt(limit),
40
+ sortBy,
41
+ sortOrder: parseInt(sortOrder)
42
+ };
43
+
44
+ const result = await queryAuditLogs(filters, options);
45
+
46
+ res.json({
47
+ message: '获取审计日志成功',
48
+ ...result
49
+ });
50
+ } catch (error) {
51
+ console.error('获取审计日志失败:', error);
52
+ res.status(500).json({
53
+ message: '获取审计日志失败',
54
+ error: error.message
55
+ });
56
+ }
57
+ });
58
+
59
+ // 获取特定用户的活动统计(仅管理员)
60
+ router.get('/user-stats/:userId', authenticate, isAdmin, async (req, res) => {
61
+ try {
62
+ const { userId } = req.params;
63
+ const { startDate, endDate } = req.query;
64
+
65
+ const timeRange = {};
66
+ if (startDate) timeRange.startDate = startDate;
67
+ if (endDate) timeRange.endDate = endDate;
68
+
69
+ const stats = await getUserActivityStats(userId, timeRange);
70
+
71
+ res.json({
72
+ message: '获取用户活动统计成功',
73
+ stats
74
+ });
75
+ } catch (error) {
76
+ console.error('获取用户活动统计失败:', error);
77
+ res.status(500).json({
78
+ message: '获取用户活动统计失败',
79
+ error: error.message
80
+ });
81
+ }
82
+ });
83
+
84
+ // 获取文档编辑历史(仅管理员)
85
+ router.get('/document-history/:documentId', authenticate, isAdmin, async (req, res) => {
86
+ try {
87
+ const { documentId } = req.params;
88
+ const { limit = 20 } = req.query;
89
+
90
+ const history = await getDocumentEditHistory(documentId, parseInt(limit));
91
+
92
+ res.json({
93
+ message: '获取文档编辑历史成功',
94
+ history
95
+ });
96
+ } catch (error) {
97
+ console.error('获取文档编辑历史失败:', error);
98
+ res.status(500).json({
99
+ message: '获取文档编辑历史失败',
100
+ error: error.message
101
+ });
102
+ }
103
+ });
104
+
105
+ // 获取群组活动日志(仅管理员)
106
+ router.get('/group/:groupId', authenticate, isAdmin, async (req, res) => {
107
+ try {
108
+ const { groupId } = req.params;
109
+ const {
110
+ page = 1,
111
+ limit = 50,
112
+ action,
113
+ userId,
114
+ startDate,
115
+ endDate
116
+ } = req.query;
117
+
118
+ const filters = { groupId };
119
+ if (action) filters.action = action;
120
+ if (userId) filters.userId = userId;
121
+ if (startDate) filters.startDate = startDate;
122
+ if (endDate) filters.endDate = endDate;
123
+
124
+ const options = {
125
+ page: parseInt(page),
126
+ limit: parseInt(limit),
127
+ sortBy: 'createdAt',
128
+ sortOrder: -1
129
+ };
130
+
131
+ const result = await queryAuditLogs(filters, options);
132
+
133
+ res.json({
134
+ message: '获取群组活动日志成功',
135
+ ...result
136
+ });
137
+ } catch (error) {
138
+ console.error('获取群组活动日志失败:', error);
139
+ res.status(500).json({
140
+ message: '获取群组活动日志失败',
141
+ error: error.message
142
+ });
143
+ }
144
+ });
145
+
146
+ // 获取审计日志统计信息(仅管理员)
147
+ router.get('/stats/summary', authenticate, isAdmin, async (req, res) => {
148
+ try {
149
+ const { startDate, endDate, groupId } = req.query;
150
+
151
+ const filters = {};
152
+ if (groupId) filters.groupId = groupId;
153
+ if (startDate || endDate) {
154
+ filters.startDate = startDate;
155
+ filters.endDate = endDate;
156
+ }
157
+
158
+ // 获取总体统计
159
+ const result = await queryAuditLogs(filters, { limit: 1 });
160
+ const totalLogs = result.pagination.total;
161
+
162
+ // 获取按操作类型统计
163
+ const actionStats = await queryAuditLogs(filters, { limit: totalLogs });
164
+ const actionBreakdown = {};
165
+
166
+ actionStats.logs.forEach(log => {
167
+ actionBreakdown[log.action] = (actionBreakdown[log.action] || 0) + 1;
168
+ });
169
+
170
+ // 获取最活跃用户
171
+ const userActivity = {};
172
+ actionStats.logs.forEach(log => {
173
+ const userId = log.user._id.toString();
174
+ if (!userActivity[userId]) {
175
+ userActivity[userId] = {
176
+ user: log.user,
177
+ count: 0
178
+ };
179
+ }
180
+ userActivity[userId].count++;
181
+ });
182
+
183
+ const topUsers = Object.values(userActivity)
184
+ .sort((a, b) => b.count - a.count)
185
+ .slice(0, 10);
186
+
187
+ res.json({
188
+ message: '获取统计信息成功',
189
+ summary: {
190
+ totalLogs,
191
+ actionBreakdown,
192
+ topUsers,
193
+ timeRange: { startDate, endDate }
194
+ }
195
+ });
196
+ } catch (error) {
197
+ console.error('获取统计信息失败:', error);
198
+ res.status(500).json({
199
+ message: '获取统计信息失败',
200
+ error: error.message
201
+ });
202
+ }
203
+ });
204
+
205
+ export default router;
206
+
207
+
208
+
209
+
210
+