1 /* eslint complexity: ["error", 24] */
4 var Promise = require('bluebird');
5 var debug = require('debug')('gitter:infra:serializer:troupe');
6 var getVersion = require('gitter-web-serialization/lib/get-model-version');
7 var UserIdStrategy = require('./user-id-strategy');
8 var mongoUtils = require('gitter-web-persistence-utils/lib/mongo-utils');
9 var avatars = require('gitter-web-avatars');
10 var getRoomNameFromTroupeName = require('gitter-web-shared/get-room-name-from-troupe-name');
11 var AllUnreadItemCountStrategy = require('./troupes/all-unread-item-count-strategy');
12 var FavouriteTroupesForUserStrategy = require('./troupes/favourite-troupes-for-user-strategy');
13 var LastTroupeAccessTimesForUserStrategy = require('./troupes/last-access-times-for-user-strategy');
14 var LurkAndActivityForUserStrategy = require('./troupes/lurk-and-activity-for-user-strategy');
15 var ProOrgStrategy = require('./troupes/pro-org-strategy');
16 var RoomMembershipStrategy = require('./troupes/room-membership-strategy');
17 var TagsStrategy = require('./troupes/tags-strategy');
18 var TroupePermissionsStrategy = require('./troupes/troupe-permissions-strategy');
19 var GroupIdStrategy = require('./group-id-strategy');
20 var SecurityDescriptorStrategy = require('./security-descriptor-strategy');
21 var AssociatedRepoStrategy = require('./troupes/associated-repo-strategy');
22 const MatrixBridgedRoomStrategy = require('./troupes/matrix-bridged-room-strategy');
24 getCanonicalAliasForGitterRoomUri
25 } = require('gitter-web-matrix-bridge/lib/matrix-alias-utils');
28 room-based-feature-toggle
29 We used to use this strategy for the threadedConversations feature toggle but don't need it now that is generally available.
30 If you're introducing another room based feature toggle, uncomment all of these pieces of code (search for room-based-feature-toggle)
32 // const TroupeMetaIdStrategy = require('./troupes/troupe-meta-id-strategy');
34 function getAvatarUrlForTroupe(serializedTroupe, options) {
35 if (serializedTroupe.oneToOne && options && options.user) {
36 return avatars.getForUser(options.user);
37 } else if (serializedTroupe.oneToOne && (!options || !options.user)) {
38 return avatars.getForRoomUri(options.name);
39 } else if (options && options.group) {
40 return options.group.avatarUrl || avatars.getForGroup(options.group);
42 return avatars.getForRoomUri(serializedTroupe.uri);
47 * Given the currentUser and a sequence of troupes
48 * returns the 'other' userId for all one to one rooms
50 function oneToOneOtherUserSequence(currentUserId, troupes) {
52 .filter(function(troupe) {
53 return troupe.oneToOne;
55 .map(function(troupe) {
56 var a = troupe.oneToOneUsers[0] && troupe.oneToOneUsers[0].userId;
57 var b = troupe.oneToOneUsers[1] && troupe.oneToOneUsers[1].userId;
59 if (mongoUtils.objectIDsEqual(currentUserId, a)) {
67 /** Best guess efforts */
68 function guessLegacyGitHubType(item) {
69 if (item.githubType) {
70 return item.githubType;
77 if (!item.sd) return 'REPO_CHANNEL'; // Could we do better?
79 var linkPath = item.sd.linkPath;
81 switch (item.sd.type) {
83 if (item.uri === linkPath) {
86 return 'REPO_CHANNEL';
91 if (item.uri === linkPath) {
94 return 'REPO_CHANNEL';
99 return 'USER_CHANNEL';
102 return 'REPO_CHANNEL';
105 /** Best guess efforts */
106 function guessLegacySecurity(item) {
108 return item.security;
111 // One-to-one rooms in legacy had security=null
116 if (item.sd.public) {
120 var type = item.sd.type;
121 if (type === 'GH_REPO' || type === 'GH_ORG') {
122 if (item.sd.linkPath && item.sd.linkPath !== item.uri) {
130 function TroupeStrategy(options) {
131 if (!options) options = {};
133 var currentUserId = mongoUtils.asObjectID(options.currentUserId);
135 var unreadItemStrategy;
136 var lastAccessTimeStrategy;
137 var favouriteStrategy;
138 var lurkActivityStrategy;
142 var permissionsStrategy;
143 var roomMembershipStrategy;
145 var securityDescriptorStrategy;
146 var associatedRepoStrategy;
147 let matrixBridgedRoomStrategy;
149 // eslint-disable-next-line max-statements
150 this.preload = function(items) {
151 // eslint-disable-line max-statements
152 if (items.isEmpty()) return;
154 var troupeIds = items.map(function(troupe) {
161 if (options.includePremium !== false) {
162 proOrgStrategy = new ProOrgStrategy(options);
163 strategies.push(proOrgStrategy.preload(items));
167 if (currentUserId || options.isRoomMember !== undefined) {
168 roomMembershipStrategy = new RoomMembershipStrategy(options);
169 strategies.push(roomMembershipStrategy.preload(troupeIds));
173 if (currentUserId && !options.skipUnreadCounts) {
174 unreadItemStrategy = new AllUnreadItemCountStrategy(options);
175 strategies.push(unreadItemStrategy.preload(troupeIds));
179 // The other user in one-to-one rooms
180 var otherUserIds = oneToOneOtherUserSequence(currentUserId, items);
181 if (!otherUserIds.isEmpty()) {
182 userIdStrategy = new UserIdStrategy(options);
183 strategies.push(userIdStrategy.preload(otherUserIds));
186 // Favourites for user
187 favouriteStrategy = new FavouriteTroupesForUserStrategy(options);
188 strategies.push(favouriteStrategy.preload());
191 lastAccessTimeStrategy = new LastTroupeAccessTimesForUserStrategy(options);
192 strategies.push(lastAccessTimeStrategy.preload());
195 lurkActivityStrategy = new LurkAndActivityForUserStrategy(options);
196 strategies.push(lurkActivityStrategy.preload());
200 if ((currentUserId || options.currentUser) && options.includePermissions) {
201 permissionsStrategy = new TroupePermissionsStrategy(options);
202 strategies.push(permissionsStrategy.preload(items));
206 if (options.includeTags) {
207 tagsStrategy = new TagsStrategy(options);
208 strategies.push(tagsStrategy.preload(items));
211 groupIdStrategy = new GroupIdStrategy(options);
213 .map(function(troupe) {
214 return troupe.groupId;
216 .filter(function(f) {
220 strategies.push(groupIdStrategy.preload(groupIds));
222 if (options.includeBackend) {
223 securityDescriptorStrategy = SecurityDescriptorStrategy.slim();
224 // Backend strategy needs no mapping stage
227 if (options.includeAssociatedRepo) {
228 associatedRepoStrategy = new AssociatedRepoStrategy();
229 strategies.push(associatedRepoStrategy.preload(items));
232 /* room-based-feature-toggle */
233 // troupeMetaIdStrategy = new TroupeMetaIdStrategy();
234 // strategies.push(troupeMetaIdStrategy.preload(troupeIds));
236 matrixBridgedRoomStrategy = new MatrixBridgedRoomStrategy();
237 strategies.push(matrixBridgedRoomStrategy.preload(troupeIds));
239 return Promise.all(strategies);
242 function mapOtherUser(users) {
243 var otherUser = users.filter(function(troupeUser) {
244 return '' + troupeUser.userId !== '' + currentUserId;
248 var user = userIdStrategy.map(otherUser.userId);
255 function resolveOneToOneOtherUser(item) {
256 if (!currentUserId) {
258 'TroupeStrategy initiated without currentUserId, but generating oneToOne troupes. This can be a problem!'
263 var otherUser = mapOtherUser(item.oneToOneUsers);
266 debug('Troupe %s appears to contain bad users', item._id);
273 // eslint-disable-next-line complexity, max-statements
274 this.map = function(item) {
275 var id = item.id || item._id;
278 var isPro = proOrgStrategy ? proOrgStrategy.map(item) : undefined;
279 var group = groupIdStrategy && item.groupId ? groupIdStrategy.map(item.groupId) : undefined;
281 var troupeName, troupeUrl;
283 var otherUser = resolveOneToOneOtherUser(item);
285 troupeName = otherUser.displayName;
286 troupeUrl = '/' + otherUser.username;
291 var roomName = getRoomNameFromTroupeName(uri);
292 troupeName = group ? group.name + '/' + getRoomNameFromTroupeName(uri) : uri;
293 if (roomName === uri) {
294 troupeName = group ? group.name : uri;
297 troupeUrl = '/' + uri;
300 var unreadCounts = unreadItemStrategy && unreadItemStrategy.map(id);
302 // mongoose is upgrading old undefineds to [] on load and we don't want to
303 // send through that no providers are allowed in that case
304 const providers = item.providers && item.providers.length ? item.providers : undefined;
308 if (lurkActivityStrategy) {
309 isLurking = lurkActivityStrategy.mapLurkStatus(id);
311 // Can only have activity if you're lurking
312 hasActivity = lurkActivityStrategy.mapActivity(id);
321 isPublic = item.sd.public;
324 var avatarUrl = getAvatarUrlForTroupe(item, {
330 // Let's only have a Matrix room link for public rooms otherwise people will
331 // be confused when they can't see their own private or one to one rooms on
332 // Matrix even they are still bridged behind the scenes
334 if (matrixBridgedRoomStrategy && isPublic) {
335 const matrixRoomId = matrixBridgedRoomStrategy.map(id);
337 // ONE_TO_ONE rooms don't have a `uri` so lets default to the matrixRoomId instead
338 let roomAliasOrId = matrixRoomId;
340 roomAliasOrId = getCanonicalAliasForGitterRoomUri(item.uri);
343 matrixRoomLink = `https://matrix.to/#/${roomAliasOrId}?utm_source=gitter`;
351 // This is a fallback for the change to the suggestions API
352 // It can be removed once the mobile clients are using topic instead
353 // of description. See https://github.com/troupe/gitter-webapp/issues/2115
354 description: options.includeDescription ? item.topic : undefined,
355 avatarUrl: avatarUrl,
357 oneToOne: item.oneToOne,
358 userCount: item.userCount,
360 unreadItems: unreadCounts ? unreadCounts.unreadItems : undefined,
361 mentions: unreadCounts ? unreadCounts.mentions : undefined,
362 lastAccessTime: lastAccessTimeStrategy ? lastAccessTimeStrategy.map(id).time : undefined,
363 favourite: favouriteStrategy ? favouriteStrategy.map(id) : undefined,
365 activity: hasActivity,
367 githubType: guessLegacyGitHubType(item),
368 associatedRepo: associatedRepoStrategy ? associatedRepoStrategy.map(item) : undefined,
369 security: guessLegacySecurity(item),
371 noindex: item.noindex, // TODO: this should not always be here
372 tags: tagsStrategy ? tagsStrategy.map(item) : undefined,
373 providers: providers,
374 permissions: permissionsStrategy ? permissionsStrategy.map(item) : undefined,
375 roomMember: roomMembershipStrategy ? roomMembershipStrategy.map(id) : undefined,
376 groupId: item.groupId,
377 group: options.includeGroups ? group : undefined,
378 backend: securityDescriptorStrategy ? securityDescriptorStrategy.map(item.sd) : undefined,
380 exists: options.includeExists ? !!id : undefined,
382 /* room-based-feature-toggle */
383 // meta: troupeMetaIdStrategy.map(id) || {},
389 TroupeStrategy.prototype = {
390 name: 'TroupeStrategy'
393 TroupeStrategy.createSuggestionStrategy = function() {
394 return new TroupeStrategy({
395 includePremium: false,
398 // TODO: remove this option in future
399 includeDescription: true,
405 module.exports = TroupeStrategy;
406 module.exports.testOnly = {
407 oneToOneOtherUserSequence: oneToOneOtherUserSequence