Merge branch 'hotfix/21.56.9' into master
[gitter.git] / server / serializers / rest / user-strategy.js
blobbbcc09c35a8514cf2d49c6a164db8e9157e77c96
1 /* eslint complexity: ["error", 21] */
2 'use strict';
4 var Promise = require('bluebird');
5 var identityService = require('gitter-web-identity');
6 var presenceService = require('gitter-web-presence');
7 var avatars = require('gitter-web-avatars');
8 var resolveUserAvatarUrl = require('gitter-web-shared/avatars/resolve-user-avatar-url');
9 var userScopes = require('gitter-web-identity/lib/user-scopes');
10 var collections = require('gitter-web-utils/lib/collections');
11 var getVersion = require('gitter-web-serialization/lib/get-model-version');
12 var troupeService = require('gitter-web-rooms/lib/troupe-service');
13 var adminFilter = require('gitter-web-permissions/lib/known-external-access/admin-filter');
15 function UserRoleInTroupeStrategy(options) {
16   var contributors;
18   this.preload = function(userIds) {
19     if (userIds.isEmpty()) return;
21     return Promise.try(function() {
22       if (options.includeRolesForTroupe) {
23         return options.includeRolesForTroupe;
24       }
26       if (options.includeRolesForTroupeId) {
27         // TODO: don't do this
28         return troupeService.findById(options.includeRolesForTroupeId);
29       }
30     })
31       .then(function(troupe) {
32         if (!troupe || !troupe.sd) return;
34         if (troupe.sd.members === troupe.sd.admins) {
35           // If all members of the room are always
36           // admins, no point in showing who the
37           // admins are
38           return;
39         }
41         return adminFilter(troupe, userIds);
42       })
43       .then(function(adminUserIds) {
44         if (adminUserIds) {
45           contributors = {};
47           adminUserIds.forEach(function(userId) {
48             contributors[userId] = 'admin';
49           });
50         }
51       });
52   };
54   this.map = function(userId) {
55     return contributors && contributors[userId];
56   };
59 UserRoleInTroupeStrategy.prototype = {
60   name: 'UserRoleInTroupeStrategy'
63 function UserPresenceInTroupeStrategy(troupeId) {
64   var onlineUsers;
66   this.preload = function() {
67     return presenceService.findOnlineUsersForTroupe(troupeId).then(function(result) {
68       onlineUsers = collections.hashArray(result);
69     });
70   };
72   this.map = function(userId) {
73     return !!onlineUsers[userId];
74   };
76 UserPresenceInTroupeStrategy.prototype = {
77   name: 'UserPresenceInTroupeStrategy'
80 function UserProvidersStrategy() {
81   var providersByUser = {};
83   this.preload = function(users) {
84     // NOTE: This is currently operating on the assumption that a user can only
85     // have one identity. Once we allow multiple identities per user we'll have
86     // to revisit this.
87     var nonGitHub = [];
88     users.each(function(user) {
89       const userId = user.id || user._id;
91       if (userScopes.isGitHubUser(user)) {
92         // github user so no need to look up identities at the time of writing
93         providersByUser[userId] = ['github'];
94       } else {
95         // non-github, so we have to look up the user's identities.
96         nonGitHub.push(user);
97       }
98     });
100     if (!nonGitHub.length) {
101       return Promise.resolve();
102     }
104     return Promise.map(nonGitHub, function(user) {
105       return identityService.listProvidersForUser(user).then(function(providers) {
106         providersByUser[user.id] = providers;
107       });
108     });
109   };
111   this.map = function(userId) {
112     return providersByUser[userId] || [];
113   };
115 UserProvidersStrategy.prototype = {
116   name: 'UserProvidersStrategy'
119 function UserStrategy(options) {
120   options = options ? options : {};
121   var lean = !!options.lean;
123   var userRoleInTroupeStrategy;
124   var userPresenceInTroupeStrategy;
125   var userProvidersStrategy;
127   this.preload = function(users) {
128     if (users.isEmpty()) return;
130     var strategies = [];
132     if (options.includeRolesForTroupeId || options.includeRolesForTroupe) {
133       userRoleInTroupeStrategy = new UserRoleInTroupeStrategy(options);
134       strategies.push(
135         userRoleInTroupeStrategy.preload(
136           users.map(function(user) {
137             const userId = user.id || user._id;
138             return userId;
139           })
140         )
141       );
142     }
144     if (options.showPresenceForTroupeId) {
145       userPresenceInTroupeStrategy = new UserPresenceInTroupeStrategy(
146         options.showPresenceForTroupeId
147       );
148       strategies.push(userPresenceInTroupeStrategy.preload());
149     }
151     if (options.includeProviders) {
152       userProvidersStrategy = new UserProvidersStrategy();
153       strategies.push(userProvidersStrategy.preload(users));
154     }
156     return Promise.all(strategies);
157   };
159   function displayNameForUser(user) {
160     return options.exposeRawDisplayName ? user.displayName : user.displayName || user.username;
161   }
163   // eslint-disable-next-line complexity
164   this.map = function(user) {
165     if (!user) return null;
166     var scopes;
168     if (options.includeScopes && userScopes.isGitHubUser(user)) {
169       scopes = userScopes.getScopesHash(user);
170     }
172     var obj;
174     const userId = user.id || user._id;
176     if (lean) {
177       obj = {
178         id: userId,
179         status: options.includeEmail ? user.status : undefined,
180         username: user.username,
181         online:
182           (userPresenceInTroupeStrategy && userPresenceInTroupeStrategy.map(userId)) || undefined,
183         role: (userRoleInTroupeStrategy && userRoleInTroupeStrategy.map(userId)) || undefined,
184         removed: user.state === 'REMOVED' || undefined, // true or undefined
185         v: getVersion(user)
186       };
188       if (user.gravatarVersion) {
189         // github
190         obj.gv = user.gravatarVersion;
191       } else {
192         // non-github
193         obj.gravatarImageUrl = user.gravatarImageUrl;
194       }
196       return obj;
197     }
199     obj = {
200       id: userId,
201       status: options.includeEmail ? user.status : undefined,
202       username: user.username,
203       displayName: displayNameForUser(user),
204       url: '/' + user.username,
205       avatarUrl: avatars.getForUser(user),
206       avatarUrlSmall: resolveUserAvatarUrl(user, 60),
207       avatarUrlMedium: resolveUserAvatarUrl(user, 128),
208       scopes: scopes,
209       online:
210         (userPresenceInTroupeStrategy && userPresenceInTroupeStrategy.map(userId)) || undefined,
211       staff: user.staff,
212       role: (userRoleInTroupeStrategy && userRoleInTroupeStrategy.map(userId)) || undefined,
213       providers: (userProvidersStrategy && userProvidersStrategy.map(userId)) || undefined,
214       removed: user.state === 'REMOVED' || undefined, // true or undefined
215       v: getVersion(user)
216     };
218     // NOTE: does it make sense to send gv (or the full url) AND small&medium?
219     if (user.gravatarVersion) {
220       // github
221       obj.gv = user.gravatarVersion;
222     } else {
223       // non-github
224       // NOTE: should we just remove this and keep using avatarUrlSmall for now?
225       //obj.gravatarImageUrl = user.gravatarImageUrl;
226     }
228     return obj;
229   };
232 UserStrategy.prototype = {
233   name: 'UserStrategy'
236 function SlimUserStrategy() {}
237 SlimUserStrategy.prototype = {
238   preload: function() {},
239   map: function(user) {
240     const userId = user.id || user._id;
242     return {
243       id: userId,
244       username: user.username,
245       displayName: user.displayName,
246       avatarUrl: avatars.getForUser(user)
247     };
248   },
249   name: 'SlimUserStrategy'
252 UserStrategy.slim = function() {
253   return new SlimUserStrategy();
255 module.exports = UserStrategy;