2020-10-01 04:30:26 -04:00
|
|
|
const { db, ObjectId } = require('../../infrastructure/mongodb')
|
2020-08-11 05:35:08 -04:00
|
|
|
const OError = require('@overleaf/o-error')
|
2019-05-29 05:21:06 -04:00
|
|
|
const async = require('async')
|
2019-08-28 08:59:41 -04:00
|
|
|
const { promisifyAll } = require('../../util/promises')
|
2019-05-29 05:21:06 -04:00
|
|
|
const { Subscription } = require('../../models/Subscription')
|
|
|
|
const SubscriptionLocator = require('./SubscriptionLocator')
|
|
|
|
const UserGetter = require('../User/UserGetter')
|
|
|
|
const PlansLocator = require('./PlansLocator')
|
|
|
|
const FeaturesUpdater = require('./FeaturesUpdater')
|
2019-09-09 07:51:34 -04:00
|
|
|
const { DeletedSubscription } = require('../../models/DeletedSubscription')
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2019-06-05 11:30:13 -04:00
|
|
|
const SubscriptionUpdater = {
|
|
|
|
/**
|
2020-02-28 10:08:20 -05:00
|
|
|
* Change the admin of the given subscription.
|
|
|
|
*
|
|
|
|
* If the subscription is a group, add the new admin as manager while keeping
|
|
|
|
* the old admin. Otherwise, replace the manager.
|
2019-06-05 11:30:13 -04:00
|
|
|
*
|
|
|
|
* Validation checks are assumed to have been made:
|
|
|
|
* * subscription exists
|
|
|
|
* * user exists
|
|
|
|
* * user does not have another subscription
|
|
|
|
* * subscription is not a Recurly subscription
|
|
|
|
*
|
|
|
|
* If the subscription is Recurly, we silently do nothing.
|
|
|
|
*/
|
2020-02-28 10:08:20 -05:00
|
|
|
updateAdmin(subscription, adminId, callback) {
|
2019-06-05 11:30:13 -04:00
|
|
|
const query = {
|
2020-02-28 10:08:20 -05:00
|
|
|
_id: ObjectId(subscription._id),
|
2019-06-05 11:30:13 -04:00
|
|
|
customAccount: true
|
|
|
|
}
|
|
|
|
const update = {
|
2020-02-28 10:08:20 -05:00
|
|
|
$set: { admin_id: ObjectId(adminId) }
|
|
|
|
}
|
|
|
|
if (subscription.groupPlan) {
|
|
|
|
update.$addToSet = { manager_ids: ObjectId(adminId) }
|
|
|
|
} else {
|
|
|
|
update.$set.manager_ids = [ObjectId(adminId)]
|
2019-06-05 11:30:13 -04:00
|
|
|
}
|
2020-11-03 04:19:05 -05:00
|
|
|
Subscription.updateOne(query, update, callback)
|
2019-06-05 11:30:13 -04:00
|
|
|
},
|
2019-05-29 05:21:06 -04:00
|
|
|
|
2019-09-09 07:51:34 -04:00
|
|
|
syncSubscription(recurlySubscription, adminUserId, requesterData, callback) {
|
|
|
|
if (!callback) {
|
|
|
|
callback = requesterData
|
|
|
|
requesterData = {}
|
|
|
|
}
|
2019-06-05 11:30:13 -04:00
|
|
|
SubscriptionLocator.getUsersSubscription(adminUserId, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
subscription
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
if (subscription != null) {
|
2019-06-05 11:30:13 -04:00
|
|
|
SubscriptionUpdater._updateSubscriptionFromRecurly(
|
2019-05-29 05:21:06 -04:00
|
|
|
recurlySubscription,
|
|
|
|
subscription,
|
2019-09-09 07:51:34 -04:00
|
|
|
requesterData,
|
2019-05-29 05:21:06 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
} else {
|
2019-06-05 11:30:13 -04:00
|
|
|
SubscriptionUpdater._createNewSubscription(adminUserId, function(
|
|
|
|
err,
|
|
|
|
subscription
|
|
|
|
) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-06-05 11:30:13 -04:00
|
|
|
SubscriptionUpdater._updateSubscriptionFromRecurly(
|
|
|
|
recurlySubscription,
|
|
|
|
subscription,
|
2019-09-09 07:51:34 -04:00
|
|
|
requesterData,
|
2019-06-05 11:30:13 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
addUserToGroup(subscriptionId, userId, callback) {
|
2019-08-28 08:59:41 -04:00
|
|
|
SubscriptionUpdater.addUsersToGroup(subscriptionId, [userId], callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
addUsersToGroup(subscriptionId, memberIds, callback) {
|
2019-08-28 08:59:41 -04:00
|
|
|
SubscriptionUpdater.addUsersToGroupWithoutFeaturesRefresh(
|
2019-05-29 05:21:06 -04:00
|
|
|
subscriptionId,
|
|
|
|
memberIds,
|
|
|
|
function(err) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2019-10-01 07:27:15 -04:00
|
|
|
async.map(memberIds, FeaturesUpdater.refreshFeatures, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
|
|
|
addUsersToGroupWithoutFeaturesRefresh(subscriptionId, memberIds, callback) {
|
|
|
|
const searchOps = { _id: subscriptionId }
|
|
|
|
const insertOperation = { $addToSet: { member_ids: { $each: memberIds } } }
|
|
|
|
|
2020-11-03 04:19:05 -05:00
|
|
|
Subscription.updateOne(searchOps, insertOperation, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
2019-06-05 11:30:13 -04:00
|
|
|
removeUserFromGroups(filter, userId, callback) {
|
|
|
|
const removeOperation = { $pull: { member_ids: userId } }
|
|
|
|
Subscription.updateMany(filter, removeOperation, function(err) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (err != null) {
|
2020-08-11 05:35:08 -04:00
|
|
|
OError.tag(err, 'error removing user from groups', {
|
|
|
|
filter,
|
|
|
|
removeOperation
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
return callback(err)
|
|
|
|
}
|
2019-10-01 07:27:15 -04:00
|
|
|
UserGetter.getUser(userId, function(error, user) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
2019-06-05 11:30:13 -04:00
|
|
|
FeaturesUpdater.refreshFeatures(userId, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-06-05 11:30:13 -04:00
|
|
|
removeUserFromGroup(subscriptionId, userId, callback) {
|
|
|
|
SubscriptionUpdater.removeUserFromGroups(
|
2019-05-29 05:21:06 -04:00
|
|
|
{ _id: subscriptionId },
|
2019-06-05 11:30:13 -04:00
|
|
|
userId,
|
2019-05-29 05:21:06 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2019-06-05 11:30:13 -04:00
|
|
|
removeUserFromAllGroups(userId, callback) {
|
|
|
|
SubscriptionLocator.getMemberSubscriptions(userId, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
error,
|
|
|
|
subscriptions
|
|
|
|
) {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (!subscriptions) {
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
const subscriptionIds = subscriptions.map(sub => sub._id)
|
2019-06-05 11:30:13 -04:00
|
|
|
SubscriptionUpdater.removeUserFromGroups(
|
2019-05-29 05:21:06 -04:00
|
|
|
{ _id: subscriptionIds },
|
2019-06-05 11:30:13 -04:00
|
|
|
userId,
|
2019-05-29 05:21:06 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
deleteWithV1Id(v1TeamId, callback) {
|
2019-06-05 11:30:13 -04:00
|
|
|
Subscription.deleteOne({ 'overleaf.id': v1TeamId }, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
2019-09-09 07:51:34 -04:00
|
|
|
deleteSubscription(subscription, deleterData, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (callback == null) {
|
2019-06-05 11:30:13 -04:00
|
|
|
callback = function() {}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-09-10 10:30:51 -04:00
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
cb =>
|
|
|
|
// 1. create deletedSubscription
|
|
|
|
SubscriptionUpdater._createDeletedSubscription(
|
|
|
|
subscription,
|
|
|
|
deleterData,
|
|
|
|
cb
|
|
|
|
),
|
|
|
|
cb =>
|
|
|
|
// 2. remove subscription
|
2020-11-03 04:19:05 -05:00
|
|
|
Subscription.deleteOne({ _id: subscription._id }, cb),
|
2019-09-10 10:30:51 -04:00
|
|
|
cb =>
|
|
|
|
// 3. refresh users features
|
|
|
|
SubscriptionUpdater._refreshUsersFeatures(subscription, cb)
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
2019-09-10 10:03:29 -04:00
|
|
|
restoreSubscription(subscriptionId, callback) {
|
|
|
|
SubscriptionLocator.getDeletedSubscription(subscriptionId, function(
|
|
|
|
err,
|
|
|
|
deletedSubscription
|
|
|
|
) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
let subscription = deletedSubscription.subscription
|
|
|
|
async.series(
|
|
|
|
[
|
|
|
|
cb =>
|
|
|
|
// 1. upsert subscription
|
2020-10-01 04:30:26 -04:00
|
|
|
db.subscriptions.updateOne(
|
2019-09-10 10:03:29 -04:00
|
|
|
{ _id: subscription._id },
|
|
|
|
subscription,
|
|
|
|
{ upsert: true },
|
|
|
|
cb
|
|
|
|
),
|
|
|
|
cb =>
|
2019-09-10 10:30:51 -04:00
|
|
|
// 2. refresh users features. Do this before removing the
|
|
|
|
// subscription so the restore can be retried if this fails
|
|
|
|
SubscriptionUpdater._refreshUsersFeatures(subscription, cb),
|
|
|
|
cb =>
|
|
|
|
// 3. remove deleted subscription
|
2019-09-10 10:03:29 -04:00
|
|
|
DeletedSubscription.deleteOne(
|
|
|
|
{ 'subscription._id': subscription._id },
|
|
|
|
callback
|
2019-09-10 10:30:51 -04:00
|
|
|
)
|
2019-09-10 10:03:29 -04:00
|
|
|
],
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-09-10 10:30:51 -04:00
|
|
|
_refreshUsersFeatures(subscription, callback) {
|
2019-09-10 10:03:29 -04:00
|
|
|
const userIds = [subscription.admin_id].concat(
|
|
|
|
subscription.member_ids || []
|
|
|
|
)
|
|
|
|
async.mapSeries(userIds, FeaturesUpdater.refreshFeatures, callback)
|
|
|
|
},
|
|
|
|
|
2019-09-09 07:51:34 -04:00
|
|
|
_createDeletedSubscription(subscription, deleterData, callback) {
|
|
|
|
subscription.teamInvites = []
|
|
|
|
subscription.invited_emails = []
|
|
|
|
const filter = { 'subscription._id': subscription._id }
|
|
|
|
const data = {
|
|
|
|
deleterData: {
|
|
|
|
deleterId: deleterData.id,
|
|
|
|
deleterIpAddress: deleterData.ip
|
|
|
|
},
|
|
|
|
subscription: subscription
|
|
|
|
}
|
|
|
|
const options = { upsert: true, new: true, setDefaultsOnInsert: true }
|
|
|
|
DeletedSubscription.findOneAndUpdate(filter, data, options, callback)
|
|
|
|
},
|
|
|
|
|
2019-06-05 11:30:13 -04:00
|
|
|
_createNewSubscription(adminUserId, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
const subscription = new Subscription({
|
2019-06-05 11:30:13 -04:00
|
|
|
admin_id: adminUserId,
|
|
|
|
manager_ids: [adminUserId]
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
2019-06-05 11:30:13 -04:00
|
|
|
subscription.save(err => callback(err, subscription))
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
2019-09-09 07:51:34 -04:00
|
|
|
_updateSubscriptionFromRecurly(
|
|
|
|
recurlySubscription,
|
|
|
|
subscription,
|
|
|
|
requesterData,
|
|
|
|
callback
|
|
|
|
) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (recurlySubscription.state === 'expired') {
|
2019-09-09 07:51:34 -04:00
|
|
|
return SubscriptionUpdater.deleteSubscription(
|
|
|
|
subscription,
|
|
|
|
requesterData,
|
|
|
|
callback
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
subscription.recurlySubscription_id = recurlySubscription.uuid
|
|
|
|
subscription.planCode = recurlySubscription.plan.plan_code
|
|
|
|
const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
|
|
|
|
if (plan == null) {
|
|
|
|
return callback(
|
|
|
|
new Error(`plan code not found: ${subscription.planCode}`)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (plan.groupPlan) {
|
2020-05-06 10:58:29 -04:00
|
|
|
if (!subscription.groupPlan) {
|
|
|
|
subscription.member_ids = subscription.member_ids || []
|
|
|
|
subscription.member_ids.push(subscription.admin_id)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
subscription.groupPlan = true
|
|
|
|
subscription.membersLimit = plan.membersLimit
|
2021-02-18 06:47:17 -05:00
|
|
|
|
|
|
|
// Some plans allow adding more seats than the base plan provides.
|
|
|
|
// This is recorded as a subscription add on.
|
|
|
|
if (
|
|
|
|
plan.membersLimitAddOn &&
|
|
|
|
Array.isArray(recurlySubscription.subscription_add_ons)
|
|
|
|
) {
|
|
|
|
recurlySubscription.subscription_add_ons.forEach(addOn => {
|
|
|
|
if (addOn.add_on_code === plan.membersLimitAddOn) {
|
|
|
|
subscription.membersLimit += addOn.quantity
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-09-10 10:30:51 -04:00
|
|
|
subscription.save(function(error) {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
SubscriptionUpdater._refreshUsersFeatures(subscription, callback)
|
2019-05-29 05:21:06 -04:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2019-06-05 11:30:13 -04:00
|
|
|
|
2019-08-28 08:59:41 -04:00
|
|
|
SubscriptionUpdater.promises = promisifyAll(SubscriptionUpdater)
|
2019-06-05 11:30:13 -04:00
|
|
|
module.exports = SubscriptionUpdater
|