Merge branch 'hotfix/21.56.9' into master
[gitter.git] / modules / rooms / lib / one-to-one-room-service.js
blob775f5ec188a10d6de180c3790027d6819e503478
1 'use strict';
3 var env = require('gitter-web-env');
4 var stats = env.stats;
5 var userService = require('gitter-web-users');
6 var persistence = require('gitter-web-persistence');
7 var userDefaultFlagsService = require('./user-default-flags-service');
8 var Troupe = persistence.Troupe;
9 var assert = require('assert');
10 var mongoUtils = require('gitter-web-persistence-utils/lib/mongo-utils');
11 var Promise = require('bluebird');
12 var ObjectID = require('mongodb').ObjectID;
13 var mongooseUtils = require('gitter-web-persistence-utils/lib/mongoose-utils');
14 var StatusError = require('statuserror');
15 var roomMembershipService = require('./room-membership-service');
16 var policyFactory = require('gitter-web-permissions/lib/policy-factory');
17 var debug = require('debug')('gitter:app:one-to-one-room-service');
19 function getOneToOneRoomQuery(userId1, userId2) {
20   // Need to use $elemMatch due to a regression in Mongo 2.6, see https://jira.mongodb.org/browse/SERVER-13843
21   return {
22     $and: [
23       { oneToOne: true },
24       { oneToOneUsers: { $elemMatch: { userId: userId1 } } },
25       { oneToOneUsers: { $elemMatch: { userId: userId2 } } }
26     ]
27   };
30 function findOneToOneRoom(fromUserId, toUserId) {
31   assert(fromUserId, 'Need to provide fromUserId');
32   assert(toUserId, 'Need to provide toUserId');
34   fromUserId = mongoUtils.asObjectID(fromUserId);
35   toUserId = mongoUtils.asObjectID(toUserId);
37   if (mongoUtils.objectIDsEqual(fromUserId, toUserId)) throw new StatusError(417); // You cannot be in a troupe with yourself.
39   var query = getOneToOneRoomQuery(fromUserId, toUserId);
41   /* Find the existing one-to-one.... */
42   return persistence.Troupe.findOne(query).exec();
45 function findOneToOneRoomsForUserId(userId) {
46   assert(userId, 'userId required');
48   return persistence.Troupe.find({
49     oneToOne: true,
50     oneToOneUsers: {
51       $elemMatch: {
52         userId: mongoUtils.asObjectID(userId)
53       }
54     }
55   })
56     .lean()
57     .exec();
60 /**
61  * Internal method.
63  * Returns [troupe, existing]
64  */
65 function upsertNewOneToOneRoom(userId1, userId2) {
66   var query = getOneToOneRoomQuery(userId1, userId2);
68   // Second attempt is an upsert
69   var insertFields = {
70     oneToOne: true,
71     status: 'ACTIVE',
72     githubType: 'ONETOONE',
73     groupId: null, // One-to-ones are never in a group
74     oneToOneUsers: [
75       {
76         _id: new ObjectID(),
77         userId: userId1
78       },
79       {
80         _id: new ObjectID(),
81         userId: userId2
82       }
83     ],
84     userCount: 0,
85     sd: {
86       type: 'ONE_TO_ONE',
87       public: false // One-to-ones are always private
88     }
89   };
91   debug('Attempting upsert for new one-to-one room');
93   // Upsert returns [model, existing] already
94   return mongooseUtils.upsert(Troupe, query, {
95     $setOnInsert: insertFields
96   });
99 function addOneToOneMemberToRoom(troupeId, userId) {
100   // Deal with https://github.com/troupe/gitter-webapp/issues/1227
101   return userDefaultFlagsService.getDefaultFlagsOneToOneForUserId(userId).then(function(flags) {
102     return roomMembershipService.addRoomMember(troupeId, userId, flags, null);
103   });
107  * Ensure that the current user is in the one-to-one room
108  */
109 function ensureUsersInRoom(troupeId, fromUserId, toUserId) {
110   return roomMembershipService
111     .findMembershipForUsersInRoom(troupeId, [fromUserId, toUserId])
112     .then(function(userIds) {
113       // Both members are in the room
114       if (userIds.length === 2) return;
116       var fromUserInRoom = userIds.some(function(userId) {
117         return mongoUtils.objectIDsEqual(userId, fromUserId);
118       });
120       var toUserInRoom = userIds.some(function(userId) {
121         return mongoUtils.objectIDsEqual(userId, toUserId);
122       });
124       debug('Re-adding users to room: fromUser=%s, toUser=%s', fromUserInRoom, toUserInRoom);
126       return Promise.all([
127         !fromUserInRoom && addOneToOneMemberToRoom(troupeId, fromUserId),
128         !toUserInRoom && addOneToOneMemberToRoom(troupeId, toUserId)
129       ]);
130     });
134  * Ensure that both users are in the one-to-one room
135  */
136 function addOneToOneUsersToNewRoom(troupeId, fromUserId, toUserId) {
137   return userDefaultFlagsService
138     .getDefaultOneToOneFlagsForUserIds([fromUserId, toUserId])
139     .then(function(userFlags) {
140       var fromUserFlags = userFlags[fromUserId];
141       var toUserFlags = userFlags[toUserId];
143       if (!fromUserFlags) throw new StatusError(404);
144       if (!toUserFlags) throw new StatusError(404);
146       return Promise.join(
147         roomMembershipService.addRoomMember(troupeId, fromUserId, fromUserFlags, null),
148         roomMembershipService.addRoomMember(troupeId, toUserId, toUserFlags, null)
149       );
150     });
154  * Find a one-to-one troupe, otherwise create it
156  * @return {[ troupe, other-user ]}
157  */
158 function findOrCreateOneToOneRoom(fromUser, toUserId) {
159   assert(fromUser, 'Need to provide fromUser');
160   assert(fromUser._id, 'fromUser invalid');
161   assert(toUserId, 'Need to provide toUserId');
163   var fromUserId = fromUser._id;
164   toUserId = mongoUtils.asObjectID(toUserId);
166   return userService
167     .findById(toUserId)
168     .bind({
169       toUser: undefined,
170       troupe: undefined
171     })
172     .then(function(toUser) {
173       if (!toUser) throw new StatusError(404, 'User does not exist');
174       this.toUser = toUser;
175       return findOneToOneRoom(fromUserId, toUserId);
176     })
177     .then(function(existingRoom) {
178       if (existingRoom) {
179         return [existingRoom, true];
180       }
182       var toUser = this.toUser;
184       // TODO: in future we need to add request one-to-one here...
185       return policyFactory
186         .createPolicyForOneToOne(fromUser, toUser)
187         .then(function(policy) {
188           return policy.canJoin();
189         })
190         .then(function(canJoin) {
191           if (!canJoin) {
192             var err = new StatusError(404);
193             err.githubType = 'ONETOONE';
194             err.uri = toUser.username;
195             throw err;
196           }
198           return upsertNewOneToOneRoom(fromUserId, toUserId);
199         });
200     })
201     .spread(function(troupe, isAlreadyExisting) {
202       debug('findOrCreate isAlreadyExisting=%s', isAlreadyExisting);
204       var troupeId = troupe._id;
205       this.troupe = troupe;
207       if (isAlreadyExisting) {
208         return ensureUsersInRoom(troupeId, fromUserId, toUserId);
209       } else {
210         stats.event('new_troupe', {
211           troupeId: troupeId,
212           oneToOne: true,
213           userId: fromUserId
214         });
216         return addOneToOneUsersToNewRoom(troupeId, fromUserId, toUserId);
217       }
218     })
219     .then(function() {
220       return [this.troupe, this.toUser];
221     });
224 /* Exports */
225 module.exports = {
226   findOrCreateOneToOneRoom: Promise.method(findOrCreateOneToOneRoom),
227   findOneToOneRoom: Promise.method(findOneToOneRoom),
228   findOneToOneRoomsForUserId: findOneToOneRoomsForUserId