FeaturesUpdater.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. max-len,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const async = require('async')
  16. const PlansLocator = require('./PlansLocator')
  17. const _ = require('underscore')
  18. const SubscriptionLocator = require('./SubscriptionLocator')
  19. const UserFeaturesUpdater = require('./UserFeaturesUpdater')
  20. const Settings = require('settings-sharelatex')
  21. const logger = require('logger-sharelatex')
  22. const ReferalFeatures = require('../Referal/ReferalFeatures')
  23. const V1SubscriptionManager = require('./V1SubscriptionManager')
  24. const InstitutionsFeatures = require('../Institutions/InstitutionsFeatures')
  25. const UserGetter = require('../User/UserGetter')
  26. const oneMonthInSeconds = 60 * 60 * 24 * 30
  27. const FeaturesUpdater = {
  28. refreshFeatures(user_id, callback) {
  29. if (callback == null) {
  30. callback = function(error, features, featuresChanged) {}
  31. }
  32. const jobs = {
  33. individualFeatures(cb) {
  34. return FeaturesUpdater._getIndividualFeatures(user_id, cb)
  35. },
  36. groupFeatureSets(cb) {
  37. return FeaturesUpdater._getGroupFeatureSets(user_id, cb)
  38. },
  39. institutionFeatures(cb) {
  40. return InstitutionsFeatures.getInstitutionsFeatures(user_id, cb)
  41. },
  42. v1Features(cb) {
  43. return FeaturesUpdater._getV1Features(user_id, cb)
  44. },
  45. bonusFeatures(cb) {
  46. return ReferalFeatures.getBonusFeatures(user_id, cb)
  47. },
  48. samlFeatures(cb) {
  49. return FeaturesUpdater._getSamlFeatures(user_id, cb)
  50. }
  51. }
  52. return async.series(jobs, function(err, results) {
  53. if (err != null) {
  54. logger.warn(
  55. { err, user_id },
  56. 'error getting subscription or group for refreshFeatures'
  57. )
  58. return callback(err)
  59. }
  60. const {
  61. individualFeatures,
  62. groupFeatureSets,
  63. institutionFeatures,
  64. v1Features,
  65. bonusFeatures,
  66. samlFeatures
  67. } = results
  68. logger.log(
  69. {
  70. user_id,
  71. individualFeatures,
  72. groupFeatureSets,
  73. institutionFeatures,
  74. v1Features,
  75. bonusFeatures,
  76. samlFeatures
  77. },
  78. 'merging user features'
  79. )
  80. const featureSets = groupFeatureSets.concat([
  81. individualFeatures,
  82. institutionFeatures,
  83. v1Features,
  84. bonusFeatures,
  85. samlFeatures
  86. ])
  87. const features = _.reduce(
  88. featureSets,
  89. FeaturesUpdater._mergeFeatures,
  90. Settings.defaultFeatures
  91. )
  92. logger.log({ user_id, features }, 'updating user features')
  93. return UserFeaturesUpdater.updateFeatures(user_id, features, callback)
  94. })
  95. },
  96. _getIndividualFeatures(user_id, callback) {
  97. if (callback == null) {
  98. callback = function(error, features) {}
  99. }
  100. return SubscriptionLocator.getUsersSubscription(user_id, (err, sub) =>
  101. callback(err, FeaturesUpdater._subscriptionToFeatures(sub))
  102. )
  103. },
  104. _getGroupFeatureSets(user_id, callback) {
  105. if (callback == null) {
  106. callback = function(error, featureSets) {}
  107. }
  108. return SubscriptionLocator.getGroupSubscriptionsMemberOf(
  109. user_id,
  110. (err, subs) =>
  111. callback(err, (subs || []).map(FeaturesUpdater._subscriptionToFeatures))
  112. )
  113. },
  114. _getSamlFeatures(user_id, callback) {
  115. UserGetter.getUser(user_id, (err, user) => {
  116. if (err) {
  117. return callback(err)
  118. }
  119. if (
  120. !user ||
  121. !Array.isArray(user.samlIdentifiers) ||
  122. !user.samlIdentifiers.length
  123. ) {
  124. return callback(null, {})
  125. }
  126. for (const samlIdentifier of user.samlIdentifiers) {
  127. if (samlIdentifier && samlIdentifier.hasEntitlement) {
  128. return callback(
  129. null,
  130. FeaturesUpdater._planCodeToFeatures('professional')
  131. )
  132. }
  133. }
  134. return callback(null, {})
  135. })
  136. },
  137. _getV1Features(user_id, callback) {
  138. if (callback == null) {
  139. callback = function(error, features) {}
  140. }
  141. return V1SubscriptionManager.getPlanCodeFromV1(user_id, function(
  142. err,
  143. planCode,
  144. v1Id
  145. ) {
  146. if (err != null) {
  147. if ((err != null ? err.name : undefined) === 'NotFoundError') {
  148. return callback(null, [])
  149. }
  150. return callback(err)
  151. }
  152. return callback(
  153. err,
  154. FeaturesUpdater._mergeFeatures(
  155. V1SubscriptionManager.getGrandfatheredFeaturesForV1User(v1Id) || {},
  156. FeaturesUpdater._planCodeToFeatures(planCode)
  157. )
  158. )
  159. })
  160. },
  161. _mergeFeatures(featuresA, featuresB) {
  162. const features = Object.assign({}, featuresA)
  163. for (let key in featuresB) {
  164. // Special merging logic for non-boolean features
  165. const value = featuresB[key]
  166. if (key === 'compileGroup') {
  167. if (
  168. features['compileGroup'] === 'priority' ||
  169. featuresB['compileGroup'] === 'priority'
  170. ) {
  171. features['compileGroup'] = 'priority'
  172. } else {
  173. features['compileGroup'] = 'standard'
  174. }
  175. } else if (key === 'collaborators') {
  176. if (
  177. features['collaborators'] === -1 ||
  178. featuresB['collaborators'] === -1
  179. ) {
  180. features['collaborators'] = -1
  181. } else {
  182. features['collaborators'] = Math.max(
  183. features['collaborators'] || 0,
  184. featuresB['collaborators'] || 0
  185. )
  186. }
  187. } else if (key === 'compileTimeout') {
  188. features['compileTimeout'] = Math.max(
  189. features['compileTimeout'] || 0,
  190. featuresB['compileTimeout'] || 0
  191. )
  192. } else {
  193. // Boolean keys, true is better
  194. features[key] = features[key] || featuresB[key]
  195. }
  196. }
  197. return features
  198. },
  199. _subscriptionToFeatures(subscription) {
  200. return FeaturesUpdater._planCodeToFeatures(
  201. subscription != null ? subscription.planCode : undefined
  202. )
  203. },
  204. _planCodeToFeatures(planCode) {
  205. if (planCode == null) {
  206. return {}
  207. }
  208. const plan = PlansLocator.findLocalPlanInSettings(planCode)
  209. if (plan == null) {
  210. return {}
  211. } else {
  212. return plan.features
  213. }
  214. }
  215. }
  216. const refreshFeaturesPromise = user_id =>
  217. new Promise(function(resolve, reject) {
  218. FeaturesUpdater.refreshFeatures(
  219. user_id,
  220. (error, features, featuresChanged) => {
  221. if (error) {
  222. reject(error)
  223. } else {
  224. resolve({ features, featuresChanged })
  225. }
  226. }
  227. )
  228. })
  229. FeaturesUpdater.promises = {
  230. refreshFeatures: refreshFeaturesPromise
  231. }
  232. module.exports = FeaturesUpdater