FeaturesUpdater.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. const _ = require('lodash')
  2. const { callbackify } = require('util')
  3. const { callbackifyMultiResult } = require('../../util/promises')
  4. const PlansLocator = require('./PlansLocator')
  5. const SubscriptionLocator = require('./SubscriptionLocator')
  6. const UserFeaturesUpdater = require('./UserFeaturesUpdater')
  7. const FeaturesHelper = require('./FeaturesHelper')
  8. const Settings = require('@overleaf/settings')
  9. const logger = require('@overleaf/logger')
  10. const ReferalFeatures = require('../Referal/ReferalFeatures')
  11. const V1SubscriptionManager = require('./V1SubscriptionManager')
  12. const InstitutionsFeatures = require('../Institutions/InstitutionsFeatures')
  13. const UserGetter = require('../User/UserGetter')
  14. const AnalyticsManager = require('../Analytics/AnalyticsManager')
  15. const Queues = require('../../infrastructure/Queues')
  16. /**
  17. * Enqueue a job for refreshing features for the given user
  18. */
  19. async function scheduleRefreshFeatures(userId, reason) {
  20. const queue = Queues.getQueue('refresh-features')
  21. await queue.add({ userId, reason })
  22. }
  23. /* Check if user features refresh if needed, based on the global featuresEpoch setting */
  24. function featuresEpochIsCurrent(user) {
  25. return Settings.featuresEpoch
  26. ? user.featuresEpoch === Settings.featuresEpoch
  27. : true
  28. }
  29. /**
  30. * Refresh features for the given user
  31. */
  32. async function refreshFeatures(userId, reason) {
  33. const user = await UserGetter.promises.getUser(userId, {
  34. _id: 1,
  35. features: 1,
  36. })
  37. const oldFeatures = _.clone(user.features)
  38. const features = await computeFeatures(userId)
  39. logger.log({ userId, features }, 'updating user features')
  40. const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
  41. AnalyticsManager.setUserPropertyForUser(
  42. userId,
  43. 'feature-set',
  44. matchedFeatureSet
  45. )
  46. const { features: newFeatures, featuresChanged } =
  47. await UserFeaturesUpdater.promises.updateFeatures(userId, features)
  48. if (oldFeatures.dropbox === true && features.dropbox === false) {
  49. logger.log({ userId }, '[FeaturesUpdater] must unlink dropbox')
  50. const Modules = require('../../infrastructure/Modules')
  51. try {
  52. await Modules.promises.hooks.fire('removeDropbox', userId, reason)
  53. } catch (err) {
  54. logger.error(err)
  55. }
  56. }
  57. return { features: newFeatures, featuresChanged }
  58. }
  59. /**
  60. * Return the features that the given user should have.
  61. */
  62. async function computeFeatures(userId) {
  63. const individualFeatures = await _getIndividualFeatures(userId)
  64. const groupFeatureSets = await _getGroupFeatureSets(userId)
  65. const institutionFeatures =
  66. await InstitutionsFeatures.promises.getInstitutionsFeatures(userId)
  67. const user = await UserGetter.promises.getUser(userId, {
  68. featuresOverrides: 1,
  69. 'overleaf.id': 1,
  70. })
  71. const v1Features = await _getV1Features(user)
  72. const bonusFeatures = await ReferalFeatures.promises.getBonusFeatures(userId)
  73. const featuresOverrides = await _getFeaturesOverrides(user)
  74. logger.log(
  75. {
  76. userId,
  77. individualFeatures,
  78. groupFeatureSets,
  79. institutionFeatures,
  80. v1Features,
  81. bonusFeatures,
  82. featuresOverrides,
  83. },
  84. 'merging user features'
  85. )
  86. const featureSets = groupFeatureSets.concat([
  87. individualFeatures,
  88. institutionFeatures,
  89. v1Features,
  90. bonusFeatures,
  91. featuresOverrides,
  92. ])
  93. const features = _.reduce(
  94. featureSets,
  95. FeaturesHelper.mergeFeatures,
  96. Settings.defaultFeatures
  97. )
  98. return features
  99. }
  100. async function _getIndividualFeatures(userId) {
  101. const sub = await SubscriptionLocator.promises.getUserIndividualSubscription(
  102. userId
  103. )
  104. return _subscriptionToFeatures(sub)
  105. }
  106. async function _getGroupFeatureSets(userId) {
  107. const subs = await SubscriptionLocator.promises.getGroupSubscriptionsMemberOf(
  108. userId
  109. )
  110. return (subs || []).map(_subscriptionToFeatures)
  111. }
  112. async function _getFeaturesOverrides(user) {
  113. if (!user || !user.featuresOverrides || user.featuresOverrides.length === 0) {
  114. return {}
  115. }
  116. const activeFeaturesOverrides = []
  117. for (const featuresOverride of user.featuresOverrides) {
  118. if (
  119. !featuresOverride.expiresAt ||
  120. featuresOverride.expiresAt > new Date()
  121. ) {
  122. activeFeaturesOverrides.push(featuresOverride.features)
  123. }
  124. }
  125. const features = _.reduce(
  126. activeFeaturesOverrides,
  127. FeaturesHelper.mergeFeatures,
  128. {}
  129. )
  130. return features
  131. }
  132. async function _getV1Features(user) {
  133. const v1Id = user?.overleaf?.id
  134. return V1SubscriptionManager.getGrandfatheredFeaturesForV1User(v1Id) || {}
  135. }
  136. function _subscriptionToFeatures(subscription) {
  137. return _planCodeToFeatures(subscription && subscription.planCode)
  138. }
  139. function _planCodeToFeatures(planCode) {
  140. if (!planCode) {
  141. return {}
  142. }
  143. const plan = PlansLocator.findLocalPlanInSettings(planCode)
  144. if (!plan) {
  145. return {}
  146. } else {
  147. return plan.features
  148. }
  149. }
  150. async function doSyncFromV1(v1UserId) {
  151. logger.log({ v1UserId }, '[AccountSync] starting account sync')
  152. const user = await UserGetter.promises.getUser(
  153. { 'overleaf.id': v1UserId },
  154. { _id: 1 }
  155. )
  156. if (user == null) {
  157. logger.warn({ v1UserId }, '[AccountSync] no user found for v1 id')
  158. return
  159. }
  160. logger.log(
  161. { v1UserId, userId: user._id },
  162. '[AccountSync] updating user subscription and features'
  163. )
  164. return refreshFeatures(user._id, 'sync-v1')
  165. }
  166. module.exports = {
  167. featuresEpochIsCurrent,
  168. computeFeatures: callbackify(computeFeatures),
  169. refreshFeatures: callbackifyMultiResult(refreshFeatures, [
  170. 'features',
  171. 'featuresChanged',
  172. ]),
  173. doSyncFromV1: callbackifyMultiResult(doSyncFromV1, [
  174. 'features',
  175. 'featuresChanged',
  176. ]),
  177. scheduleRefreshFeatures: callbackify(scheduleRefreshFeatures),
  178. promises: {
  179. computeFeatures,
  180. refreshFeatures,
  181. scheduleRefreshFeatures,
  182. doSyncFromV1,
  183. },
  184. }