SubscriptionUpdater.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. const { db, ObjectId } = require('../../infrastructure/mongodb')
  2. const OError = require('@overleaf/o-error')
  3. const async = require('async')
  4. const { promisifyAll } = require('../../util/promises')
  5. const { Subscription } = require('../../models/Subscription')
  6. const SubscriptionLocator = require('./SubscriptionLocator')
  7. const UserGetter = require('../User/UserGetter')
  8. const PlansLocator = require('./PlansLocator')
  9. const FeaturesUpdater = require('./FeaturesUpdater')
  10. const { DeletedSubscription } = require('../../models/DeletedSubscription')
  11. const SubscriptionUpdater = {
  12. /**
  13. * Change the admin of the given subscription.
  14. *
  15. * If the subscription is a group, add the new admin as manager while keeping
  16. * the old admin. Otherwise, replace the manager.
  17. *
  18. * Validation checks are assumed to have been made:
  19. * * subscription exists
  20. * * user exists
  21. * * user does not have another subscription
  22. * * subscription is not a Recurly subscription
  23. *
  24. * If the subscription is Recurly, we silently do nothing.
  25. */
  26. updateAdmin(subscription, adminId, callback) {
  27. const query = {
  28. _id: ObjectId(subscription._id),
  29. customAccount: true,
  30. }
  31. const update = {
  32. $set: { admin_id: ObjectId(adminId) },
  33. }
  34. if (subscription.groupPlan) {
  35. update.$addToSet = { manager_ids: ObjectId(adminId) }
  36. } else {
  37. update.$set.manager_ids = [ObjectId(adminId)]
  38. }
  39. Subscription.updateOne(query, update, callback)
  40. },
  41. syncSubscription(recurlySubscription, adminUserId, requesterData, callback) {
  42. if (!callback) {
  43. callback = requesterData
  44. requesterData = {}
  45. }
  46. SubscriptionLocator.getUsersSubscription(
  47. adminUserId,
  48. function (err, subscription) {
  49. if (err != null) {
  50. return callback(err)
  51. }
  52. if (subscription != null) {
  53. SubscriptionUpdater._updateSubscriptionFromRecurly(
  54. recurlySubscription,
  55. subscription,
  56. requesterData,
  57. callback
  58. )
  59. } else {
  60. SubscriptionUpdater._createNewSubscription(
  61. adminUserId,
  62. function (err, subscription) {
  63. if (err != null) {
  64. return callback(err)
  65. }
  66. SubscriptionUpdater._updateSubscriptionFromRecurly(
  67. recurlySubscription,
  68. subscription,
  69. requesterData,
  70. callback
  71. )
  72. }
  73. )
  74. }
  75. }
  76. )
  77. },
  78. addUserToGroup(subscriptionId, userId, callback) {
  79. SubscriptionUpdater.addUsersToGroup(subscriptionId, [userId], callback)
  80. },
  81. addUsersToGroup(subscriptionId, memberIds, callback) {
  82. SubscriptionUpdater.addUsersToGroupWithoutFeaturesRefresh(
  83. subscriptionId,
  84. memberIds,
  85. function (err) {
  86. if (err != null) {
  87. return callback(err)
  88. }
  89. async.map(
  90. memberIds,
  91. function (userId, cb) {
  92. FeaturesUpdater.refreshFeatures(userId, 'add-to-group', cb)
  93. },
  94. callback
  95. )
  96. }
  97. )
  98. },
  99. addUsersToGroupWithoutFeaturesRefresh(subscriptionId, memberIds, callback) {
  100. const searchOps = { _id: subscriptionId }
  101. const insertOperation = { $addToSet: { member_ids: { $each: memberIds } } }
  102. Subscription.updateOne(searchOps, insertOperation, callback)
  103. },
  104. removeUserFromGroups(filter, userId, callback) {
  105. const removeOperation = { $pull: { member_ids: userId } }
  106. Subscription.updateMany(filter, removeOperation, function (err) {
  107. if (err != null) {
  108. OError.tag(err, 'error removing user from groups', {
  109. filter,
  110. removeOperation,
  111. })
  112. return callback(err)
  113. }
  114. UserGetter.getUser(userId, function (error, user) {
  115. if (error) {
  116. return callback(error)
  117. }
  118. FeaturesUpdater.refreshFeatures(
  119. userId,
  120. 'remove-user-from-groups',
  121. callback
  122. )
  123. })
  124. })
  125. },
  126. removeUserFromGroup(subscriptionId, userId, callback) {
  127. SubscriptionUpdater.removeUserFromGroups(
  128. { _id: subscriptionId },
  129. userId,
  130. callback
  131. )
  132. },
  133. removeUserFromAllGroups(userId, callback) {
  134. SubscriptionLocator.getMemberSubscriptions(
  135. userId,
  136. function (error, subscriptions) {
  137. if (error) {
  138. return callback(error)
  139. }
  140. if (!subscriptions) {
  141. return callback()
  142. }
  143. const subscriptionIds = subscriptions.map(sub => sub._id)
  144. SubscriptionUpdater.removeUserFromGroups(
  145. { _id: subscriptionIds },
  146. userId,
  147. callback
  148. )
  149. }
  150. )
  151. },
  152. deleteWithV1Id(v1TeamId, callback) {
  153. Subscription.deleteOne({ 'overleaf.id': v1TeamId }, callback)
  154. },
  155. deleteSubscription(subscription, deleterData, callback) {
  156. if (callback == null) {
  157. callback = function () {}
  158. }
  159. async.series(
  160. [
  161. cb =>
  162. // 1. create deletedSubscription
  163. SubscriptionUpdater._createDeletedSubscription(
  164. subscription,
  165. deleterData,
  166. cb
  167. ),
  168. cb =>
  169. // 2. remove subscription
  170. Subscription.deleteOne({ _id: subscription._id }, cb),
  171. cb =>
  172. // 3. refresh users features
  173. SubscriptionUpdater._refreshUsersFeatures(subscription, cb),
  174. ],
  175. callback
  176. )
  177. },
  178. restoreSubscription(subscriptionId, callback) {
  179. SubscriptionLocator.getDeletedSubscription(
  180. subscriptionId,
  181. function (err, deletedSubscription) {
  182. if (err) {
  183. return callback(err)
  184. }
  185. const subscription = deletedSubscription.subscription
  186. async.series(
  187. [
  188. cb =>
  189. // 1. upsert subscription
  190. db.subscriptions.updateOne(
  191. { _id: subscription._id },
  192. subscription,
  193. { upsert: true },
  194. cb
  195. ),
  196. cb =>
  197. // 2. refresh users features. Do this before removing the
  198. // subscription so the restore can be retried if this fails
  199. SubscriptionUpdater._refreshUsersFeatures(subscription, cb),
  200. cb =>
  201. // 3. remove deleted subscription
  202. DeletedSubscription.deleteOne(
  203. { 'subscription._id': subscription._id },
  204. callback
  205. ),
  206. ],
  207. callback
  208. )
  209. }
  210. )
  211. },
  212. _refreshUsersFeatures(subscription, callback) {
  213. const userIds = [subscription.admin_id].concat(
  214. subscription.member_ids || []
  215. )
  216. async.mapSeries(
  217. userIds,
  218. function (userId, cb) {
  219. FeaturesUpdater.refreshFeatures(userId, 'subscription-updater', cb)
  220. },
  221. callback
  222. )
  223. },
  224. _createDeletedSubscription(subscription, deleterData, callback) {
  225. subscription.teamInvites = []
  226. subscription.invited_emails = []
  227. const filter = { 'subscription._id': subscription._id }
  228. const data = {
  229. deleterData: {
  230. deleterId: deleterData.id,
  231. deleterIpAddress: deleterData.ip,
  232. },
  233. subscription: subscription,
  234. }
  235. const options = { upsert: true, new: true, setDefaultsOnInsert: true }
  236. DeletedSubscription.findOneAndUpdate(filter, data, options, callback)
  237. },
  238. _createNewSubscription(adminUserId, callback) {
  239. const subscription = new Subscription({
  240. admin_id: adminUserId,
  241. manager_ids: [adminUserId],
  242. })
  243. subscription.save(err => callback(err, subscription))
  244. },
  245. _updateSubscriptionFromRecurly(
  246. recurlySubscription,
  247. subscription,
  248. requesterData,
  249. callback
  250. ) {
  251. if (recurlySubscription.state === 'expired') {
  252. return SubscriptionUpdater.deleteSubscription(
  253. subscription,
  254. requesterData,
  255. callback
  256. )
  257. }
  258. subscription.recurlySubscription_id = recurlySubscription.uuid
  259. subscription.planCode = recurlySubscription.plan.plan_code
  260. const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
  261. if (plan == null) {
  262. return callback(
  263. new Error(`plan code not found: ${subscription.planCode}`)
  264. )
  265. }
  266. if (plan.groupPlan) {
  267. if (!subscription.groupPlan) {
  268. subscription.member_ids = subscription.member_ids || []
  269. subscription.member_ids.push(subscription.admin_id)
  270. }
  271. subscription.groupPlan = true
  272. subscription.membersLimit = plan.membersLimit
  273. // Some plans allow adding more seats than the base plan provides.
  274. // This is recorded as a subscription add on.
  275. if (
  276. plan.membersLimitAddOn &&
  277. Array.isArray(recurlySubscription.subscription_add_ons)
  278. ) {
  279. recurlySubscription.subscription_add_ons.forEach(addOn => {
  280. if (addOn.add_on_code === plan.membersLimitAddOn) {
  281. subscription.membersLimit += addOn.quantity
  282. }
  283. })
  284. }
  285. }
  286. subscription.save(function (error) {
  287. if (error) {
  288. return callback(error)
  289. }
  290. SubscriptionUpdater._refreshUsersFeatures(subscription, callback)
  291. })
  292. },
  293. }
  294. SubscriptionUpdater.promises = promisifyAll(SubscriptionUpdater)
  295. module.exports = SubscriptionUpdater