@tiledesk/tiledesk-server 2.19.12 → 2.19.14

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,336 @@
1
+ 'use strict';
2
+
3
+ function targetUserLabel(activity) {
4
+ const user = activity &&
5
+ activity.target &&
6
+ activity.target.object &&
7
+ activity.target.object.id_user;
8
+
9
+ if (!user) {
10
+ return 'unknown user';
11
+ }
12
+
13
+ const firstname = user.firstname || '';
14
+ const lastname = user.lastname || '';
15
+ const fullname = (firstname + ' ' + lastname).trim();
16
+ if (fullname) {
17
+ return fullname;
18
+ }
19
+
20
+ return String(user._id || user.id || 'unknown user');
21
+ }
22
+
23
+ function actorLabel(activity) {
24
+ const actor = activity && activity.actor;
25
+ if (!actor) {
26
+ return 'Someone';
27
+ }
28
+ if (actor.type === 'system') {
29
+ return 'System';
30
+ }
31
+ return actor.name || actor.id || 'Someone';
32
+ }
33
+
34
+ function requestLabel(activity) {
35
+ const request = activity && activity.target && activity.target.object;
36
+ if (request && request.request_id) {
37
+ return request.request_id;
38
+ }
39
+ if (activity && activity.target && activity.target.id) {
40
+ return String(activity.target.id);
41
+ }
42
+ return 'conversation';
43
+ }
44
+
45
+ function resolveParticipantLabel(activity, participantId) {
46
+ if (!participantId) {
47
+ return 'unknown agent';
48
+ }
49
+
50
+ const agents = activity &&
51
+ activity.target &&
52
+ activity.target.object &&
53
+ activity.target.object.participatingAgents;
54
+
55
+ if (Array.isArray(agents)) {
56
+ for (const agent of agents) {
57
+ const user = agent.id_user || agent;
58
+ const userId = user._id || user.id || user;
59
+ if (String(userId) === String(participantId)) {
60
+ const firstname = user.firstname || '';
61
+ const lastname = user.lastname || '';
62
+ const fullname = (firstname + ' ' + lastname).trim();
63
+ if (fullname) {
64
+ return fullname;
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ return String(participantId);
71
+ }
72
+
73
+ function inviteTargetLabel(activity) {
74
+ const target = activity && activity.target;
75
+ if (!target) {
76
+ return 'unknown user';
77
+ }
78
+
79
+ if (target.type === 'pendinginvitation') {
80
+ const pending = target.object || {};
81
+ return pending.email || (activity.actionObj && activity.actionObj.email) || 'unknown user';
82
+ }
83
+
84
+ const user = target.object && target.object.id_user;
85
+ if (user) {
86
+ const firstname = user.firstname || '';
87
+ const lastname = user.lastname || '';
88
+ const fullname = (firstname + ' ' + lastname).trim();
89
+ if (fullname) {
90
+ return fullname;
91
+ }
92
+ return user.email || 'unknown user';
93
+ }
94
+
95
+ return (activity.actionObj && activity.actionObj.email) || 'unknown user';
96
+ }
97
+
98
+ function inviteEmailLabel(activity) {
99
+ const actionObj = activity && activity.actionObj || {};
100
+ if (actionObj.email) {
101
+ return actionObj.email;
102
+ }
103
+
104
+ const target = activity && activity.target;
105
+ if (target && target.type === 'pendinginvitation' && target.object && target.object.email) {
106
+ return target.object.email;
107
+ }
108
+
109
+ const user = target && target.object && target.object.id_user;
110
+ if (user && user.email) {
111
+ return user.email;
112
+ }
113
+
114
+ return '';
115
+ }
116
+
117
+ function resolveAssigneeLabel(activity) {
118
+ const actionObj = activity && activity.actionObj || {};
119
+ if (actionObj.assigneeName) {
120
+ return actionObj.assigneeName;
121
+ }
122
+ if (actionObj.assigneeType === 'department') {
123
+ return actionObj.assigneeId || 'department';
124
+ }
125
+ if (actionObj.assigneeType === 'bot') {
126
+ return actionObj.assigneeId || 'chatbot';
127
+ }
128
+ return resolveParticipantLabel(activity, actionObj.assigneeId);
129
+ }
130
+
131
+ function namespaceLabel(activity) {
132
+ const object = activity && activity.target && activity.target.object;
133
+ if (object && object.name) {
134
+ return object.name;
135
+ }
136
+ if (activity && activity.actionObj && activity.actionObj.namespaceName) {
137
+ return activity.actionObj.namespaceName;
138
+ }
139
+ if (activity && activity.target && activity.target.id) {
140
+ return String(activity.target.id);
141
+ }
142
+ return 'namespace';
143
+ }
144
+
145
+ function chatbotLabel(activity) {
146
+ const object = activity && activity.target && activity.target.object;
147
+ if (object && object.name) {
148
+ return object.name;
149
+ }
150
+ if (activity && activity.actionObj && activity.actionObj.name) {
151
+ return activity.actionObj.name;
152
+ }
153
+ return 'chatbot';
154
+ }
155
+
156
+ function contentAddTypeLabel(contentAddType) {
157
+ switch (contentAddType) {
158
+ case 'content':
159
+ return 'content';
160
+ case 'url_list':
161
+ return 'URL list';
162
+ case 'csv':
163
+ return 'CSV file';
164
+ case 'sitemap':
165
+ return 'sitemap';
166
+ default:
167
+ return contentAddType || 'content';
168
+ }
169
+ }
170
+
171
+ function kbContentLabel(activity) {
172
+ const actionObj = (activity && activity.actionObj) || {};
173
+ const object = activity && activity.target && activity.target.object;
174
+ if (actionObj.source) {
175
+ return actionObj.source;
176
+ }
177
+ if (actionObj.name) {
178
+ return actionObj.name;
179
+ }
180
+ if (object && object.source) {
181
+ return object.source;
182
+ }
183
+ if (object && object.name) {
184
+ return object.name;
185
+ }
186
+ if (activity && activity.target && activity.target.id) {
187
+ return String(activity.target.id);
188
+ }
189
+ return 'content';
190
+ }
191
+
192
+ function buildDefaultActivityMessage(activity) {
193
+ if (!activity || !activity.verb) {
194
+ return null;
195
+ }
196
+
197
+ const actionObj = activity.actionObj || {};
198
+ const actor = actorLabel(activity);
199
+ const conversation = requestLabel(activity);
200
+ const assignee = resolveAssigneeLabel(activity);
201
+ const source = actionObj.source || 'unknown';
202
+
203
+ switch (activity.verb) {
204
+ case 'REQUEST_ASSIGNED_AUTO':
205
+ if (activity.actor && activity.actor.type === 'system') {
206
+ return 'System automatically assigned conversation ' + conversation +
207
+ ' to ' + assignee + ' (source: ' + source + ')';
208
+ }
209
+ return actor + ' triggered automatic assignment of conversation ' + conversation +
210
+ ' to ' + assignee + ' (source: ' + source + ')';
211
+
212
+ case 'REQUEST_ASSIGNED_SELF':
213
+ return actor + ' joined conversation ' + conversation + ' (source: ' + source + ')';
214
+
215
+ case 'REQUEST_ASSIGNED_MANUAL': {
216
+ const assigneeType = actionObj.assigneeType || 'user';
217
+ if (assigneeType === 'bot') {
218
+ let message = actor + ' reassigned conversation ' + conversation +
219
+ ' to chatbot ' + assignee + ' (source: ' + source + ')';
220
+ if (actionObj.previousAssigneeId) {
221
+ const previous = resolveParticipantLabel(activity, actionObj.previousAssigneeId);
222
+ message += ' (replaced ' + previous + ')';
223
+ }
224
+ return message;
225
+ }
226
+ if (assigneeType === 'department') {
227
+ let message = actor + ' reassigned conversation ' + conversation +
228
+ ' to department ' + assignee + ' (source: ' + source + ')';
229
+ if (actionObj.previousAssigneeId) {
230
+ const previous = resolveParticipantLabel(activity, actionObj.previousAssigneeId);
231
+ message += ' (replaced ' + previous + ')';
232
+ }
233
+ return message;
234
+ }
235
+ let message = actor + ' manually assigned conversation ' + conversation +
236
+ ' to ' + assignee + ' (source: ' + source + ')';
237
+ if (actionObj.previousAssigneeId) {
238
+ const previous = resolveParticipantLabel(activity, actionObj.previousAssigneeId);
239
+ message += ' (replaced ' + previous + ')';
240
+ }
241
+ return message;
242
+ }
243
+
244
+ case 'REQUEST_UNASSIGNED':
245
+ if (actionObj.source === 'subscription') {
246
+ return 'System unassigned ' + assignee + ' from conversation ' + conversation +
247
+ ' (source: subscription)';
248
+ }
249
+ return actor + ' unassigned ' + assignee + ' from conversation ' + conversation +
250
+ ' (source: ' + source + ')';
251
+
252
+ case 'LEAVE_CONVERSATION':
253
+ return actor + ' left conversation ' + conversation + ' (source: ' + source + ')';
254
+
255
+ case 'PROJECT_USER_AVAILABILITY_SELF': {
256
+ const targetUser = targetUserLabel(activity);
257
+ const newStatus = actionObj.newStatus || 'unknown';
258
+ return targetUser + ' changed availability status to ' + newStatus;
259
+ }
260
+
261
+ case 'PROJECT_USER_AVAILABILITY_SYSTEM': {
262
+ const targetUser = targetUserLabel(activity);
263
+ const newStatus = actionObj.newStatus || 'unknown';
264
+ return targetUser + ' availability status was changed to ' + newStatus + ' by the system';
265
+ }
266
+
267
+ case 'PROJECT_USER_INVITE': {
268
+ const target = inviteTargetLabel(activity);
269
+ const email = inviteEmailLabel(activity);
270
+ const role = actionObj.role || 'agent';
271
+ const emailPart = email && target !== email ? ' (' + email + ')' : '';
272
+ return actor + ' invited ' + target + emailPart + ' to take on the role of ' + role;
273
+ }
274
+
275
+ case 'PROJECT_USER_DELETE':
276
+ return actor + ' removed ' + targetUserLabel(activity) + ' from the project';
277
+
278
+ case 'FAQ_KB_CREATE':
279
+ return actor + ' created chatbot ' + chatbotLabel(activity);
280
+
281
+ case 'FAQ_KB_DELETE':
282
+ return actor + ' deleted chatbot ' + chatbotLabel(activity);
283
+
284
+ case 'FAQ_KB_PUBLISH':
285
+ return actor + ' published chatbot ' + chatbotLabel(activity);
286
+
287
+ case 'KB_NAMESPACE_CREATE':
288
+ return actor + ' created namespace ' + namespaceLabel(activity);
289
+
290
+ case 'KB_NAMESPACE_DELETE':
291
+ return actor + ' deleted namespace ' + namespaceLabel(activity);
292
+
293
+ case 'KB_CONTENTS_ADD': {
294
+ const namespace = namespaceLabel(activity);
295
+ const addType = contentAddTypeLabel(actionObj.contentAddType);
296
+ const count = actionObj.count;
297
+ if (count && count > 1) {
298
+ return actor + ' added ' + count + ' items (' + addType + ') to namespace ' + namespace;
299
+ }
300
+ if (actionObj.source) {
301
+ return actor + ' added ' + addType + ' to namespace ' + namespace + ' (' + actionObj.source + ')';
302
+ }
303
+ return actor + ' added ' + addType + ' to namespace ' + namespace;
304
+ }
305
+
306
+ case 'KB_CONTENTS_DELETE':
307
+ return actor + ' deleted all contents from namespace ' + namespaceLabel(activity);
308
+
309
+ case 'KB_CONTENT_DELETE':
310
+ return actor + ' deleted content ' + kbContentLabel(activity) +
311
+ ' from namespace ' + namespaceLabel(activity);
312
+
313
+ default:
314
+ return null;
315
+ }
316
+ }
317
+
318
+ function enrichActivityWithMessage(activity) {
319
+ const message = buildDefaultActivityMessage(activity);
320
+ if (!message) {
321
+ return activity;
322
+ }
323
+ return Object.assign({}, activity, { message: message });
324
+ }
325
+
326
+ module.exports = {
327
+ actorLabel,
328
+ targetUserLabel,
329
+ requestLabel,
330
+ namespaceLabel,
331
+ chatbotLabel,
332
+ kbContentLabel,
333
+ resolveParticipantLabel,
334
+ buildDefaultActivityMessage,
335
+ enrichActivityWithMessage
336
+ };
@@ -1,3 +1,3 @@
1
1
  const activityRoute = require("./routes/activity");
2
2
  const activityArchiver = require("./activityArchiver");
3
- module.exports = {activityArchiver:activityArchiver,activityRoute:activityRoute};
3
+ module.exports = { activityArchiver, activityRoute };
@@ -1,6 +1,19 @@
1
1
  var mongoose = require('mongoose');
2
2
  var Schema = mongoose.Schema;
3
3
 
4
+ const DEFAULT_ACTIVITY_TTL_SEC = 30 * 24 * 60 * 60; // 30 days
5
+
6
+ function ttlSecondsFromEnv(raw, fallbackSec) {
7
+ if (raw == null || String(raw).trim() === '') return fallbackSec;
8
+ const n = Number(raw);
9
+ return Number.isFinite(n) && n >= 0 ? n : fallbackSec;
10
+ }
11
+
12
+ let expireAfterSeconds = ttlSecondsFromEnv(
13
+ process.env.ACTIVITY_EXPIRATION_TIME,
14
+ DEFAULT_ACTIVITY_TTL_SEC
15
+ );
16
+
4
17
  // https://getstream.io/blog/designing-activity-stream-newsfeed-w3c-spec/
5
18
  // {
6
19
  // "@context": "http://www.w3.org/ns/activitystreams",
@@ -47,6 +60,65 @@ var ActorActivitySchema = new Schema({
47
60
  },
48
61
  });
49
62
 
63
+ // Secondary entity involved in / affected by the activity (a "third party" beyond actor and target).
64
+ // Example: "user1 assigned conversation xyz to user2" -> actor=user1, target=conversation, related=user2.
65
+ // `role` is kept as a free String (no enum) on purpose, to avoid save failures when new roles appear.
66
+ // Suggested role values: 'assignee', 'unassigned_user', 'previous_assignee', 'removed_participant'.
67
+ var RelatedActivitySchema = new Schema({
68
+ role: {
69
+ type: String,
70
+ required: false,
71
+ index: true
72
+ },
73
+ type: {
74
+ type: String,
75
+ required: false
76
+ },
77
+ id: {
78
+ type: String,
79
+ required: false,
80
+ index: true
81
+ },
82
+ name: {
83
+ type: String,
84
+ required: false
85
+ },
86
+ object: {
87
+ type: Schema.Types.Mixed,
88
+ required: false
89
+ }
90
+ }, { _id: false });
91
+
92
+ function userIdFromEntity(entity) {
93
+ if (!entity) {
94
+ return null;
95
+ }
96
+ if (entity.object && entity.object.id_user) {
97
+ var u = entity.object.id_user;
98
+ return String((u && (u._id || u.id)) || u);
99
+ }
100
+ if ((entity.type === 'user') && entity.id) {
101
+ return String(entity.id);
102
+ }
103
+ return null;
104
+ }
105
+
106
+ function computeInvolvedUserIds(doc) {
107
+ var ids = new Set();
108
+ if (doc.actor && doc.actor.type === 'user' && doc.actor.id) {
109
+ ids.add(String(doc.actor.id));
110
+ }
111
+ var targetUserId = userIdFromEntity(doc.target);
112
+ if (targetUserId) {
113
+ ids.add(targetUserId);
114
+ }
115
+ var relatedUserId = userIdFromEntity(doc.related);
116
+ if (relatedUserId) {
117
+ ids.add(relatedUserId);
118
+ }
119
+ return Array.from(ids);
120
+ }
121
+
50
122
  var ActivitySchema = new Schema({
51
123
 
52
124
  actor: {
@@ -71,6 +143,18 @@ var ActivitySchema = new Schema({
71
143
  required: true,
72
144
  // index: true
73
145
  },
146
+ related: {
147
+ type: RelatedActivitySchema,
148
+ required: false
149
+ },
150
+ // Denormalized list of user ids involved in the activity (actor, target user, related user).
151
+ // Auto-filled by the pre-save hook below. Indexed for efficient "activities of a user" queries.
152
+ involvedUserIds: {
153
+ type: [String],
154
+ required: false,
155
+ index: true,
156
+ default: undefined
157
+ },
74
158
  // summary A natural language summarization of the object encoded as HTML. Multiple language tagged summaries may be provided.
75
159
  // summaryMap https://www.w3.org/TR/activitystreams-vocabulary/#dfn-summary
76
160
  id_project: {
@@ -83,8 +167,21 @@ var ActivitySchema = new Schema({
83
167
  }
84
168
  );
85
169
 
86
- ActivitySchema.index({id_project: 1, createdAt: -1});
170
+ ActivitySchema.pre('save', function (next) {
171
+ try {
172
+ var ids = computeInvolvedUserIds(this);
173
+ this.involvedUserIds = ids.length > 0 ? ids : undefined;
174
+ } catch (e) {
175
+ // never block saving an activity because of the denormalized field
176
+ }
177
+ next();
178
+ });
179
+
180
+ ActivitySchema.index({ id_project: 1, createdAt: -1 });
181
+ ActivitySchema.index({ id_project: 1, 'actor.id': 1, createdAt: -1 });
182
+ ActivitySchema.index({ id_project: 1, 'target.object.id_user._id': 1, createdAt: -1 });
183
+ ActivitySchema.index({ id_project: 1, involvedUserIds: 1, createdAt: -1 });
184
+ ActivitySchema.index({ createdAt: 1 }, { expireAfterSeconds: expireAfterSeconds });
87
185
 
88
- // TODO metti indice per query è lentina
89
186
 
90
187
  module.exports = mongoose.model('activity', ActivitySchema);