2021-07-05 11:15:54 -04:00
|
|
|
const _ = require('lodash')
|
2021-09-20 08:20:14 -04:00
|
|
|
const { callbackify } = require('util')
|
|
|
|
const { callbackifyMultiResult } = require('../../util/promises')
|
|
|
|
const PlansLocator = require('./PlansLocator')
|
2019-05-29 05:21:06 -04:00
|
|
|
const SubscriptionLocator = require('./SubscriptionLocator')
|
|
|
|
const UserFeaturesUpdater = require('./UserFeaturesUpdater')
|
2021-09-20 08:20:14 -04:00
|
|
|
const FeaturesHelper = require('./FeaturesHelper')
|
2021-07-07 05:38:56 -04:00
|
|
|
const Settings = require('@overleaf/settings')
|
2021-11-10 08:40:18 -05:00
|
|
|
const logger = require('@overleaf/logger')
|
2019-05-29 05:21:06 -04:00
|
|
|
const ReferalFeatures = require('../Referal/ReferalFeatures')
|
|
|
|
const V1SubscriptionManager = require('./V1SubscriptionManager')
|
|
|
|
const InstitutionsFeatures = require('../Institutions/InstitutionsFeatures')
|
2019-10-05 13:43:21 -04:00
|
|
|
const UserGetter = require('../User/UserGetter')
|
2021-06-16 10:22:15 -04:00
|
|
|
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
2021-09-20 08:20:33 -04:00
|
|
|
const Queues = require('../../infrastructure/Queues')
|
2022-09-22 04:52:03 -04:00
|
|
|
const Modules = require('../../infrastructure/Modules')
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-09-20 08:20:33 -04:00
|
|
|
/**
|
|
|
|
* Enqueue a job for refreshing features for the given user
|
|
|
|
*/
|
|
|
|
async function scheduleRefreshFeatures(userId, reason) {
|
2021-10-25 08:07:45 -04:00
|
|
|
const queue = Queues.getQueue('refresh-features')
|
2021-09-20 08:20:33 -04:00
|
|
|
await queue.add({ userId, reason })
|
|
|
|
}
|
|
|
|
|
2021-10-26 09:31:24 -04:00
|
|
|
/* Check if user features refresh if needed, based on the global featuresEpoch setting */
|
|
|
|
function featuresEpochIsCurrent(user) {
|
|
|
|
return Settings.featuresEpoch
|
|
|
|
? user.featuresEpoch === Settings.featuresEpoch
|
|
|
|
: true
|
|
|
|
}
|
|
|
|
|
2021-09-20 08:20:33 -04:00
|
|
|
/**
|
|
|
|
* Refresh features for the given user
|
|
|
|
*/
|
2021-09-20 08:20:14 -04:00
|
|
|
async function refreshFeatures(userId, reason) {
|
|
|
|
const user = await UserGetter.promises.getUser(userId, {
|
|
|
|
_id: 1,
|
|
|
|
features: 1,
|
|
|
|
})
|
|
|
|
const oldFeatures = _.clone(user.features)
|
|
|
|
const features = await computeFeatures(userId)
|
2022-05-16 08:38:18 -04:00
|
|
|
logger.debug({ userId, features }, 'updating user features')
|
2021-09-20 08:20:14 -04:00
|
|
|
|
2022-01-25 05:18:07 -05:00
|
|
|
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
|
2021-09-20 08:20:14 -04:00
|
|
|
AnalyticsManager.setUserPropertyForUser(
|
|
|
|
userId,
|
|
|
|
'feature-set',
|
|
|
|
matchedFeatureSet
|
|
|
|
)
|
|
|
|
|
2022-01-10 05:23:05 -05:00
|
|
|
const { features: newFeatures, featuresChanged } =
|
|
|
|
await UserFeaturesUpdater.promises.updateFeatures(userId, features)
|
2021-09-20 08:20:14 -04:00
|
|
|
if (oldFeatures.dropbox === true && features.dropbox === false) {
|
2022-05-16 08:38:18 -04:00
|
|
|
logger.debug({ userId }, '[FeaturesUpdater] must unlink dropbox')
|
2021-09-20 08:20:14 -04:00
|
|
|
try {
|
|
|
|
await Modules.promises.hooks.fire('removeDropbox', userId, reason)
|
|
|
|
} catch (err) {
|
|
|
|
logger.error(err)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-09-20 08:20:14 -04:00
|
|
|
}
|
|
|
|
return { features: newFeatures, featuresChanged }
|
|
|
|
}
|
2019-12-02 08:51:57 -05:00
|
|
|
|
2021-09-20 08:20:33 -04:00
|
|
|
/**
|
|
|
|
* Return the features that the given user should have.
|
|
|
|
*/
|
2021-09-20 08:20:14 -04:00
|
|
|
async function computeFeatures(userId) {
|
|
|
|
const individualFeatures = await _getIndividualFeatures(userId)
|
|
|
|
const groupFeatureSets = await _getGroupFeatureSets(userId)
|
2022-01-10 05:23:05 -05:00
|
|
|
const institutionFeatures =
|
|
|
|
await InstitutionsFeatures.promises.getInstitutionsFeatures(userId)
|
2022-03-24 11:40:40 -04:00
|
|
|
const user = await UserGetter.promises.getUser(userId, {
|
|
|
|
featuresOverrides: 1,
|
|
|
|
'overleaf.id': 1,
|
|
|
|
})
|
|
|
|
const v1Features = await _getV1Features(user)
|
2021-09-20 08:20:14 -04:00
|
|
|
const bonusFeatures = await ReferalFeatures.promises.getBonusFeatures(userId)
|
2022-03-24 11:40:40 -04:00
|
|
|
const featuresOverrides = await _getFeaturesOverrides(user)
|
2022-05-16 08:38:18 -04:00
|
|
|
logger.debug(
|
2021-09-20 08:20:14 -04:00
|
|
|
{
|
2021-04-14 09:17:21 -04:00
|
|
|
userId,
|
2021-09-20 08:20:14 -04:00
|
|
|
individualFeatures,
|
|
|
|
groupFeatureSets,
|
|
|
|
institutionFeatures,
|
|
|
|
v1Features,
|
|
|
|
bonusFeatures,
|
|
|
|
featuresOverrides,
|
|
|
|
},
|
|
|
|
'merging user features'
|
|
|
|
)
|
|
|
|
const featureSets = groupFeatureSets.concat([
|
|
|
|
individualFeatures,
|
|
|
|
institutionFeatures,
|
|
|
|
v1Features,
|
|
|
|
bonusFeatures,
|
|
|
|
featuresOverrides,
|
|
|
|
])
|
2022-05-26 05:31:06 -04:00
|
|
|
return _.reduce(
|
2021-09-20 08:20:14 -04:00
|
|
|
featureSets,
|
|
|
|
FeaturesHelper.mergeFeatures,
|
|
|
|
Settings.defaultFeatures
|
|
|
|
)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2021-09-20 08:20:14 -04:00
|
|
|
async function _getIndividualFeatures(userId) {
|
|
|
|
const sub = await SubscriptionLocator.promises.getUserIndividualSubscription(
|
|
|
|
userId
|
|
|
|
)
|
|
|
|
return _subscriptionToFeatures(sub)
|
|
|
|
}
|
2021-07-05 11:15:54 -04:00
|
|
|
|
2021-09-20 08:20:14 -04:00
|
|
|
async function _getGroupFeatureSets(userId) {
|
|
|
|
const subs = await SubscriptionLocator.promises.getGroupSubscriptionsMemberOf(
|
|
|
|
userId
|
|
|
|
)
|
|
|
|
return (subs || []).map(_subscriptionToFeatures)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2022-03-24 11:40:40 -04:00
|
|
|
async function _getFeaturesOverrides(user) {
|
2021-09-20 08:20:14 -04:00
|
|
|
if (!user || !user.featuresOverrides || user.featuresOverrides.length === 0) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
const activeFeaturesOverrides = []
|
|
|
|
for (const featuresOverride of user.featuresOverrides) {
|
|
|
|
if (
|
|
|
|
!featuresOverride.expiresAt ||
|
|
|
|
featuresOverride.expiresAt > new Date()
|
|
|
|
) {
|
|
|
|
activeFeaturesOverrides.push(featuresOverride.features)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2021-09-20 08:20:14 -04:00
|
|
|
}
|
2022-05-26 05:31:06 -04:00
|
|
|
return _.reduce(activeFeaturesOverrides, FeaturesHelper.mergeFeatures, {})
|
2021-09-20 08:20:14 -04:00
|
|
|
}
|
2020-01-07 06:03:14 -05:00
|
|
|
|
2022-03-24 11:40:40 -04:00
|
|
|
async function _getV1Features(user) {
|
|
|
|
const v1Id = user?.overleaf?.id
|
|
|
|
return V1SubscriptionManager.getGrandfatheredFeaturesForV1User(v1Id) || {}
|
2021-09-20 08:20:14 -04:00
|
|
|
}
|
2020-01-07 06:03:14 -05:00
|
|
|
|
2021-09-20 08:20:14 -04:00
|
|
|
function _subscriptionToFeatures(subscription) {
|
|
|
|
return _planCodeToFeatures(subscription && subscription.planCode)
|
|
|
|
}
|
2020-01-07 06:03:14 -05:00
|
|
|
|
2021-09-20 08:20:14 -04:00
|
|
|
function _planCodeToFeatures(planCode) {
|
|
|
|
if (!planCode) {
|
|
|
|
return {}
|
|
|
|
}
|
|
|
|
const plan = PlansLocator.findLocalPlanInSettings(planCode)
|
|
|
|
if (!plan) {
|
|
|
|
return {}
|
|
|
|
} else {
|
|
|
|
return plan.features
|
|
|
|
}
|
|
|
|
}
|
2020-02-03 09:12:34 -05:00
|
|
|
|
2021-09-20 08:20:14 -04:00
|
|
|
async function doSyncFromV1(v1UserId) {
|
2022-05-16 08:38:18 -04:00
|
|
|
logger.debug({ v1UserId }, '[AccountSync] starting account sync')
|
2021-09-20 08:20:14 -04:00
|
|
|
const user = await UserGetter.promises.getUser(
|
|
|
|
{ 'overleaf.id': v1UserId },
|
|
|
|
{ _id: 1 }
|
|
|
|
)
|
|
|
|
if (user == null) {
|
|
|
|
logger.warn({ v1UserId }, '[AccountSync] no user found for v1 id')
|
|
|
|
return
|
|
|
|
}
|
2022-05-16 08:38:18 -04:00
|
|
|
logger.debug(
|
2021-09-20 08:20:14 -04:00
|
|
|
{ v1UserId, userId: user._id },
|
|
|
|
'[AccountSync] updating user subscription and features'
|
|
|
|
)
|
|
|
|
return refreshFeatures(user._id, 'sync-v1')
|
|
|
|
}
|
2021-06-16 10:22:15 -04:00
|
|
|
|
2021-09-20 08:20:14 -04:00
|
|
|
module.exports = {
|
2021-10-26 09:31:24 -04:00
|
|
|
featuresEpochIsCurrent,
|
2021-09-20 08:20:14 -04:00
|
|
|
computeFeatures: callbackify(computeFeatures),
|
|
|
|
refreshFeatures: callbackifyMultiResult(refreshFeatures, [
|
|
|
|
'features',
|
|
|
|
'featuresChanged',
|
|
|
|
]),
|
|
|
|
doSyncFromV1: callbackifyMultiResult(doSyncFromV1, [
|
|
|
|
'features',
|
|
|
|
'featuresChanged',
|
|
|
|
]),
|
2021-09-20 08:20:33 -04:00
|
|
|
scheduleRefreshFeatures: callbackify(scheduleRefreshFeatures),
|
2021-09-20 08:20:14 -04:00
|
|
|
promises: {
|
|
|
|
computeFeatures,
|
|
|
|
refreshFeatures,
|
2021-09-20 08:20:33 -04:00
|
|
|
scheduleRefreshFeatures,
|
2021-09-20 08:20:14 -04:00
|
|
|
doSyncFromV1,
|
|
|
|
},
|
2019-11-25 08:29:40 -05:00
|
|
|
}
|