@tiledesk/tiledesk-server 2.19.13 → 2.19.15

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,283 @@
1
+ 'use strict';
2
+
3
+ const projectUserUpdateContextUtil = require('./projectUserUpdateContextUtil');
4
+ const winston = require('../config/winston');
5
+
6
+ function systemActor() {
7
+ return { type: 'system', id: 'system', name: 'System' };
8
+ }
9
+
10
+ function actorFromUser(user) {
11
+ return projectUserUpdateContextUtil.actorFromPrincipal(user);
12
+ }
13
+
14
+ function assignmentActorContext(req, defaultSource) {
15
+ const source = defaultSource || 'api';
16
+
17
+ if (!req || !req.user) {
18
+ return { actor: systemActor(), source: 'system' };
19
+ }
20
+
21
+ if (projectUserUpdateContextUtil.isSubscriptionActor(req.user)) {
22
+ return { actor: systemActor(), source: 'subscription' };
23
+ }
24
+
25
+ const actor = actorFromUser(req.user);
26
+ const isBot = projectUserUpdateContextUtil.isBotActor(req.user);
27
+
28
+ if (isBot && actor.type !== 'bot') {
29
+ winston.warn('assignmentActorContext bot principal resolved as non-bot actor', {
30
+ actor: actor,
31
+ principal_id: projectUserUpdateContextUtil.resolveUserId(req.user),
32
+ principal_name: req.user && req.user.name,
33
+ principal_type: req.user && req.user.type,
34
+ principal_subtype: req.user && req.user.subtype,
35
+ principal_sub: req.user && req.user.sub,
36
+ principal_model: req.user && req.user.constructor && req.user.constructor.modelName,
37
+ source: source,
38
+ url: req.originalUrl
39
+ });
40
+ } else {
41
+ winston.info('assignmentActorContext actor resolution', {
42
+ actor: actor,
43
+ principal_id: projectUserUpdateContextUtil.resolveUserId(req.user),
44
+ principal_name: req.user && req.user.name,
45
+ principal_type: req.user && req.user.type,
46
+ principal_subtype: req.user && req.user.subtype,
47
+ principal_sub: req.user && req.user.sub,
48
+ principal_model: req.user && req.user.constructor && req.user.constructor.modelName,
49
+ isBotActor: isBot,
50
+ source: source,
51
+ url: req.originalUrl
52
+ });
53
+ }
54
+
55
+ return { actor: actor, source: source };
56
+ }
57
+
58
+ function reconcileAssignmentPayload(data) {
59
+ const source = (data && data.source) || 'system';
60
+ let actor = (data && data.actor) || systemActor();
61
+
62
+ if (source === 'subscription' || actor.type === 'system') {
63
+ return {
64
+ actor: systemActor(),
65
+ source: source === 'subscription' ? 'subscription' : source
66
+ };
67
+ }
68
+
69
+ return { actor: actor, source: source };
70
+ }
71
+
72
+ function isHumanParticipant(participantId) {
73
+ return participantId && !String(participantId).startsWith('bot_');
74
+ }
75
+
76
+ function filterHumanParticipants(participantIds) {
77
+ return (participantIds || []).filter(isHumanParticipant);
78
+ }
79
+
80
+ function isBotParticipant(participantId) {
81
+ return participantId && String(participantId).startsWith('bot_');
82
+ }
83
+
84
+ function filterBotParticipants(participantIds) {
85
+ return (participantIds || []).filter(isBotParticipant);
86
+ }
87
+
88
+ function botIdFromParticipant(participantId) {
89
+ return String(participantId).replace('bot_', '');
90
+ }
91
+
92
+ function resolveBotName(requestComplete, botParticipantOrId) {
93
+ const botId = isBotParticipant(botParticipantOrId)
94
+ ? botIdFromParticipant(botParticipantOrId)
95
+ : String(botParticipantOrId);
96
+ const bots = requestComplete && requestComplete.participatingBots;
97
+ if (Array.isArray(bots)) {
98
+ for (const bot of bots) {
99
+ const id = String(bot._id || bot.id);
100
+ if (id === botId) {
101
+ return bot.name || id;
102
+ }
103
+ }
104
+ }
105
+ return botId;
106
+ }
107
+
108
+ function resolveDepartmentName(requestComplete) {
109
+ const dep = requestComplete && requestComplete.department;
110
+ if (!dep) {
111
+ return undefined;
112
+ }
113
+ if (dep.name) {
114
+ return dep.name;
115
+ }
116
+ return dep._id ? String(dep._id) : undefined;
117
+ }
118
+
119
+ function deriveAssignmentType({ actor, assigneeIds, isUnassign }) {
120
+ if (isUnassign) {
121
+ return 'unassign';
122
+ }
123
+
124
+ const humanAssignees = filterHumanParticipants(assigneeIds);
125
+ if (humanAssignees.length === 0) {
126
+ return null;
127
+ }
128
+
129
+ if (!actor || actor.type === 'system') {
130
+ return 'auto';
131
+ }
132
+
133
+ const assigneeId = String(humanAssignees[0]);
134
+ if (String(actor.id) === assigneeId) {
135
+ return 'self_join';
136
+ }
137
+
138
+ return 'manual_reassign';
139
+ }
140
+
141
+ function verbFromAssignmentType(assignmentType) {
142
+ switch (assignmentType) {
143
+ case 'auto':
144
+ return 'REQUEST_ASSIGNED_AUTO';
145
+ case 'self_join':
146
+ return 'REQUEST_ASSIGNED_SELF';
147
+ case 'manual_reassign':
148
+ case 'manual_reassign_bot':
149
+ case 'manual_reassign_department':
150
+ return 'REQUEST_ASSIGNED_MANUAL';
151
+ case 'unassign':
152
+ return 'REQUEST_UNASSIGNED';
153
+ default:
154
+ return null;
155
+ }
156
+ }
157
+
158
+ function buildSetParticipantsOptions(req, participants) {
159
+ const { actor, source } = assignmentActorContext(req, 'api');
160
+
161
+ if (!participants || participants.length === 0) {
162
+ return {
163
+ actor,
164
+ source,
165
+ assignmentType: 'unassign'
166
+ };
167
+ }
168
+
169
+ const botParticipants = filterBotParticipants(participants);
170
+ const humanParticipants = filterHumanParticipants(participants);
171
+
172
+ if (botParticipants.length > 0 && humanParticipants.length === 0) {
173
+ return {
174
+ actor,
175
+ source,
176
+ assignmentType: 'manual_reassign_bot'
177
+ };
178
+ }
179
+
180
+ return {
181
+ actor,
182
+ source,
183
+ assignmentType: deriveAssignmentType({
184
+ actor,
185
+ assigneeIds: participants,
186
+ isUnassign: false
187
+ })
188
+ };
189
+ }
190
+
191
+ function buildAddParticipantOptions(req, member) {
192
+ const { actor, source } = assignmentActorContext(req, 'api');
193
+
194
+ return {
195
+ actor,
196
+ source,
197
+ assignmentType: deriveAssignmentType({
198
+ actor,
199
+ assigneeIds: [member],
200
+ isUnassign: false
201
+ })
202
+ };
203
+ }
204
+
205
+ function buildRemoveParticipantOptions(req, member) {
206
+ if (!isHumanParticipant(member)) {
207
+ return { trackLeave: false };
208
+ }
209
+
210
+ const { actor: reqActor, source } = assignmentActorContext(req, 'api');
211
+ const actor = req && req.user && String(reqActor.id) === String(member)
212
+ ? reqActor
213
+ : { type: 'user', id: String(member), name: String(member) };
214
+
215
+ return {
216
+ actor,
217
+ source,
218
+ member,
219
+ trackLeave: true
220
+ };
221
+ }
222
+
223
+ function buildLeaveParticipantOptions(source, member) {
224
+ if (!isHumanParticipant(member)) {
225
+ return { trackLeave: false };
226
+ }
227
+
228
+ return {
229
+ actor: { type: 'user', id: String(member), name: String(member) },
230
+ source: source || 'webhook',
231
+ member,
232
+ trackLeave: true
233
+ };
234
+ }
235
+
236
+ function buildAutoRouteOptions(req, source) {
237
+ const ctx = assignmentActorContext(req, source || 'api');
238
+ return {
239
+ actor: ctx.actor,
240
+ source: ctx.source,
241
+ assignmentType: 'auto'
242
+ };
243
+ }
244
+
245
+ function buildDepartmentRouteOptions(req, source) {
246
+ const ctx = assignmentActorContext(req, source || 'api');
247
+ return {
248
+ actor: ctx.actor,
249
+ source: ctx.source,
250
+ assignmentType: 'manual_reassign_department'
251
+ };
252
+ }
253
+
254
+ function buildInternalOptions(source, assignmentType) {
255
+ return {
256
+ actor: systemActor(),
257
+ source: source || 'system',
258
+ assignmentType: assignmentType || 'auto'
259
+ };
260
+ }
261
+
262
+ module.exports = {
263
+ systemActor,
264
+ actorFromUser,
265
+ assignmentActorContext,
266
+ reconcileAssignmentPayload,
267
+ isHumanParticipant,
268
+ filterHumanParticipants,
269
+ isBotParticipant,
270
+ filterBotParticipants,
271
+ botIdFromParticipant,
272
+ resolveBotName,
273
+ resolveDepartmentName,
274
+ deriveAssignmentType,
275
+ verbFromAssignmentType,
276
+ buildSetParticipantsOptions,
277
+ buildAddParticipantOptions,
278
+ buildRemoveParticipantOptions,
279
+ buildLeaveParticipantOptions,
280
+ buildAutoRouteOptions,
281
+ buildDepartmentRouteOptions,
282
+ buildInternalOptions
283
+ };
@@ -224,7 +224,7 @@ function validateRowData(data, schema) {
224
224
  const col = schema[j];
225
225
  if (!Object.prototype.hasOwnProperty.call(data, col.name)) continue;
226
226
  const raw = data[col.name];
227
- if (raw === null || raw === undefined) continue;
227
+ if (raw === undefined) continue;
228
228
  result[col.name] = coerceValue(raw, col);
229
229
  }
230
230
  return result;
@@ -0,0 +1,325 @@
1
+ 'use strict';
2
+
3
+ const Subscription = require('../models/subscription');
4
+ const Faq_kb = require('../models/faq_kb');
5
+
6
+ function systemActor() {
7
+ return { type: 'system', id: 'system', name: 'System' };
8
+ }
9
+
10
+ function userDisplayName(user) {
11
+ if (!user) {
12
+ return undefined;
13
+ }
14
+
15
+ const fullName = user.fullName || user.fullname;
16
+ if (fullName && String(fullName).trim()) {
17
+ return String(fullName).trim();
18
+ }
19
+
20
+ const name = ((user.firstname || '') + ' ' + (user.lastname || '')).trim();
21
+ if (name) {
22
+ return name;
23
+ }
24
+
25
+ return undefined;
26
+ }
27
+
28
+ function resolveUserId(userRef) {
29
+ if (userRef === undefined || userRef === null) {
30
+ return '';
31
+ }
32
+
33
+ if (typeof userRef === 'string' || typeof userRef === 'number') {
34
+ return String(userRef);
35
+ }
36
+
37
+ if (userRef._id) {
38
+ return String(userRef._id);
39
+ }
40
+
41
+ if (userRef.id) {
42
+ return String(userRef.id);
43
+ }
44
+
45
+ return String(userRef);
46
+ }
47
+
48
+ function isBotActor(user) {
49
+ if (!user) {
50
+ return false;
51
+ }
52
+
53
+ if (user instanceof Faq_kb) {
54
+ return true;
55
+ }
56
+
57
+ if (user.constructor && user.constructor.modelName === 'faq_kb') {
58
+ return true;
59
+ }
60
+
61
+ if (user.sub === 'bot' || (typeof user.sub === 'string' && user.sub.endsWith('/bot'))) {
62
+ return true;
63
+ }
64
+
65
+ if (typeof user.aud === 'string' && user.aud.indexOf('/bots/') > -1) {
66
+ return true;
67
+ }
68
+
69
+ if (user.type === 'tilebot' || user.subtype === 'chatbot') {
70
+ return true;
71
+ }
72
+
73
+ // Cached/plain faq_kb payloads may miss type/subtype but still carry bot identity fields.
74
+ if (
75
+ user.slug &&
76
+ user.name &&
77
+ user.id_project &&
78
+ user.createdBy &&
79
+ !user.email &&
80
+ !user.firstname &&
81
+ !user.lastname
82
+ ) {
83
+ return true;
84
+ }
85
+
86
+ return false;
87
+ }
88
+
89
+ function botDisplayName(user) {
90
+ if (!user || !user.name) {
91
+ return undefined;
92
+ }
93
+ const name = String(user.name).trim();
94
+ return name || undefined;
95
+ }
96
+
97
+ function actorFromPrincipal(user) {
98
+ if (!user) {
99
+ return systemActor();
100
+ }
101
+
102
+ if (isSubscriptionActor(user)) {
103
+ return systemActor();
104
+ }
105
+
106
+ if (isBotActor(user)) {
107
+ const id = resolveUserId(user);
108
+ return {
109
+ type: 'bot',
110
+ id: id,
111
+ name: botDisplayName(user) || id
112
+ };
113
+ }
114
+
115
+ return {
116
+ type: 'user',
117
+ id: resolveUserId(user),
118
+ name: user.fullName || user.fullname || userDisplayName(user) || undefined
119
+ };
120
+ }
121
+
122
+ function isSubscriptionActor(user) {
123
+ if (!user) {
124
+ return false;
125
+ }
126
+
127
+ if (user instanceof Subscription) {
128
+ return true;
129
+ }
130
+
131
+ if (user.constructor && user.constructor.modelName === 'subscription') {
132
+ return true;
133
+ }
134
+
135
+ // Webhook subscription document (no user profile fields)
136
+ return Boolean(
137
+ user.event &&
138
+ user.target &&
139
+ user.id_project &&
140
+ user.createdBy &&
141
+ !user.email &&
142
+ !user.firstname &&
143
+ !user.lastname
144
+ );
145
+ }
146
+
147
+ function isSystemAvailabilityInitiator(req) {
148
+ if (!req || !req.body) {
149
+ return false;
150
+ }
151
+
152
+ return req.body.availabilityInitiator === 'system';
153
+ }
154
+
155
+ function isAvailabilityUpdate(body) {
156
+ if (!body) {
157
+ return false;
158
+ }
159
+ return body.user_available !== undefined || body.profileStatus !== undefined;
160
+ }
161
+
162
+ function buildProjectUserUpdateContext(req, previousUserAvailable, previousProfileStatus, targetUserId) {
163
+ const context = {
164
+ previousUserAvailable,
165
+ previousProfileStatus,
166
+ source: 'api',
167
+ updateType: 'admin'
168
+ };
169
+
170
+ if (!req || !req.user) {
171
+ context.source = 'system';
172
+ context.updateType = 'system';
173
+ return context;
174
+ }
175
+
176
+ if (isSubscriptionActor(req.user) || isSystemAvailabilityInitiator(req)) {
177
+ context.source = isSubscriptionActor(req.user) ? 'subscription' : 'api';
178
+ context.updateType = 'system';
179
+ return context;
180
+ }
181
+
182
+ const actorUserId = resolveUserId(req.user);
183
+ const targetId = resolveUserId(targetUserId);
184
+ const isSelfRoute = !req.params || !req.params.project_userid;
185
+
186
+ if (isSelfRoute) {
187
+ context.updateType = 'self';
188
+ return context;
189
+ }
190
+
191
+ if (actorUserId && targetId && actorUserId === targetId) {
192
+ context.updateType = 'self';
193
+ }
194
+
195
+ return context;
196
+ }
197
+
198
+ function actorFromUpdateContext(req, updateContext) {
199
+ if (
200
+ !req ||
201
+ !req.user ||
202
+ (updateContext && updateContext.updateType === 'system') ||
203
+ isSubscriptionActor(req.user) ||
204
+ isSystemAvailabilityInitiator(req)
205
+ ) {
206
+ return systemActor();
207
+ }
208
+
209
+ return {
210
+ type: 'user',
211
+ id: resolveUserId(req.user),
212
+ name: userDisplayName(req.user)
213
+ };
214
+ }
215
+
216
+ function actorFromProjectUserUpdate(event, verb, updateContext) {
217
+ const actor = actorFromUpdateContext(event.req, updateContext);
218
+
219
+ if (actor.name) {
220
+ return actor;
221
+ }
222
+
223
+ const project_user = event.updatedProject_userPopulated;
224
+ const targetUser = project_user && project_user.id_user;
225
+ const targetName = userDisplayName(targetUser);
226
+
227
+ if (
228
+ targetName &&
229
+ (verb === 'PROJECT_USER_AVAILABILITY_SELF' ||
230
+ resolveUserId(targetUser) === actor.id)
231
+ ) {
232
+ return Object.assign({}, actor, { name: targetName });
233
+ }
234
+
235
+ return actor;
236
+ }
237
+
238
+ function verbForProjectUserUpdate(body, updateContext) {
239
+ if (!isAvailabilityUpdate(body)) {
240
+ return 'PROJECT_USER_UPDATE';
241
+ }
242
+
243
+ if (updateContext && updateContext.updateType === 'system') {
244
+ return 'PROJECT_USER_AVAILABILITY_SYSTEM';
245
+ }
246
+
247
+ if (updateContext && updateContext.updateType === 'self') {
248
+ return 'PROJECT_USER_AVAILABILITY_SELF';
249
+ }
250
+
251
+ return 'PROJECT_USER_UPDATE';
252
+ }
253
+
254
+ function availabilityStatusLabel({ user_available, profileStatus }) {
255
+ if (profileStatus !== undefined && profileStatus !== null && profileStatus !== '') {
256
+ return String(profileStatus);
257
+ }
258
+ if (user_available === true) {
259
+ return 'available';
260
+ }
261
+ if (user_available === false) {
262
+ return 'unavailable';
263
+ }
264
+ return 'unknown';
265
+ }
266
+
267
+ function reconcileAvailabilityVerb(event, project_user, verb, updateContext) {
268
+ if (!isAvailabilityUpdate(event.req && event.req.body)) {
269
+ return { verb: verb, updateContext: updateContext, actor: null };
270
+ }
271
+
272
+ const context = Object.assign({}, updateContext);
273
+ let resolvedVerb = verb;
274
+ const targetUserId = resolveUserId(project_user && project_user.id_user);
275
+ const actorUserId = resolveUserId(event.req && event.req.user);
276
+
277
+ if (isSubscriptionActor(event.req && event.req.user) || isSystemAvailabilityInitiator(event.req)) {
278
+ context.updateType = 'system';
279
+ context.source = isSubscriptionActor(event.req.user) ? 'subscription' : (context.source || 'api');
280
+ resolvedVerb = 'PROJECT_USER_AVAILABILITY_SYSTEM';
281
+ return {
282
+ verb: resolvedVerb,
283
+ updateContext: context,
284
+ actor: systemActor()
285
+ };
286
+ }
287
+
288
+ if (resolvedVerb === 'PROJECT_USER_AVAILABILITY_SELF' && targetUserId && actorUserId && actorUserId !== targetUserId) {
289
+ context.updateType = 'admin';
290
+ resolvedVerb = 'PROJECT_USER_UPDATE';
291
+ return { verb: resolvedVerb, updateContext: context, actor: null };
292
+ }
293
+
294
+ return { verb: resolvedVerb, updateContext: context, actor: null };
295
+ }
296
+
297
+ function verbFromAvailabilityUpdateType(updateType) {
298
+ switch (updateType) {
299
+ case 'system':
300
+ return 'PROJECT_USER_AVAILABILITY_SYSTEM';
301
+ case 'self':
302
+ return 'PROJECT_USER_AVAILABILITY_SELF';
303
+ default:
304
+ return 'PROJECT_USER_UPDATE';
305
+ }
306
+ }
307
+
308
+ module.exports = {
309
+ systemActor,
310
+ userDisplayName,
311
+ resolveUserId,
312
+ isBotActor,
313
+ botDisplayName,
314
+ actorFromPrincipal,
315
+ isSubscriptionActor,
316
+ isSystemAvailabilityInitiator,
317
+ isAvailabilityUpdate,
318
+ buildProjectUserUpdateContext,
319
+ actorFromUpdateContext,
320
+ actorFromProjectUserUpdate,
321
+ verbForProjectUserUpdate,
322
+ availabilityStatusLabel,
323
+ reconcileAvailabilityVerb,
324
+ verbFromAvailabilityUpdateType
325
+ };