mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-05 15:19:02 +02:00
8b89c31eed
[web] support canceling Stripe subscription GitOrigin-RevId: a72ccb20fbef9b6662cdfa1dcffacbd76dcb694c
220 lines
6.5 KiB
JavaScript
220 lines
6.5 KiB
JavaScript
// @ts-check
|
|
|
|
const logger = require('@overleaf/logger')
|
|
const ProjectGetter = require('../Project/ProjectGetter')
|
|
const UserGetter = require('../User/UserGetter')
|
|
const SubscriptionLocator = require('./SubscriptionLocator')
|
|
const Settings = require('@overleaf/settings')
|
|
const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
|
const CollaboratorsInvitesGetter = require('../Collaborators/CollaboratorsInviteGetter')
|
|
const PrivilegeLevels = require('../Authorization/PrivilegeLevels')
|
|
const {
|
|
callbackify,
|
|
callbackifyMultiResult,
|
|
} = require('@overleaf/promise-utils')
|
|
|
|
async function allowedNumberOfCollaboratorsInProject(projectId) {
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
owner_ref: true,
|
|
})
|
|
return await allowedNumberOfCollaboratorsForUser(project.owner_ref)
|
|
}
|
|
|
|
async function allowedNumberOfCollaboratorsForUser(userId) {
|
|
const user = await UserGetter.promises.getUser(userId, { features: 1 })
|
|
if (user.features && user.features.collaborators) {
|
|
return user.features.collaborators
|
|
} else {
|
|
return Settings.defaultFeatures.collaborators
|
|
}
|
|
}
|
|
|
|
async function canAcceptEditCollaboratorInvite(projectId) {
|
|
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
|
if (allowedNumber < 0) {
|
|
return true // -1 means unlimited
|
|
}
|
|
const currentEditors =
|
|
await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
|
projectId
|
|
)
|
|
return currentEditors + 1 <= allowedNumber
|
|
}
|
|
|
|
async function canAddXEditCollaborators(
|
|
projectId,
|
|
numberOfNewEditCollaborators
|
|
) {
|
|
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
|
if (allowedNumber < 0) {
|
|
return true // -1 means unlimited
|
|
}
|
|
const currentEditors =
|
|
await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
|
projectId
|
|
)
|
|
const editInviteCount =
|
|
await CollaboratorsInvitesGetter.promises.getEditInviteCount(projectId)
|
|
return (
|
|
currentEditors + editInviteCount + numberOfNewEditCollaborators <=
|
|
allowedNumber
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Check whether a collaborator can be switched to the given privilege level
|
|
*
|
|
* @param {string} projectId
|
|
* @param {string} userId
|
|
* @param {'readOnly' | 'review' | 'readAndWrite'} privilegeLevel
|
|
* @return {Promise<boolean>}
|
|
*/
|
|
async function canChangeCollaboratorPrivilegeLevel(
|
|
projectId,
|
|
userId,
|
|
privilegeLevel
|
|
) {
|
|
if (privilegeLevel === PrivilegeLevels.READ_ONLY) {
|
|
return true
|
|
}
|
|
|
|
const currentPrivilegeLevel =
|
|
await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
|
userId,
|
|
projectId
|
|
)
|
|
if (
|
|
currentPrivilegeLevel === PrivilegeLevels.READ_AND_WRITE ||
|
|
currentPrivilegeLevel === PrivilegeLevels.REVIEW
|
|
) {
|
|
// Current collaborator already takes a slot, so changing the privilege
|
|
// level won't increase the collaborator count
|
|
return true
|
|
}
|
|
|
|
const allowedNumber = await allowedNumberOfCollaboratorsInProject(projectId)
|
|
if (allowedNumber < 0) {
|
|
// -1 means unlimited
|
|
return true
|
|
}
|
|
|
|
const slotsTaken =
|
|
await CollaboratorsGetter.promises.getInvitedEditCollaboratorCount(
|
|
projectId
|
|
)
|
|
const inviteCount =
|
|
await CollaboratorsInvitesGetter.promises.getEditInviteCount(projectId)
|
|
|
|
return slotsTaken + inviteCount < allowedNumber
|
|
}
|
|
|
|
async function hasPaidSubscription(user) {
|
|
const { hasSubscription, subscription } = await userHasSubscription(user)
|
|
const { isMember } = await userIsMemberOfGroupSubscription(user)
|
|
return {
|
|
hasPaidSubscription: hasSubscription || isMember,
|
|
subscription,
|
|
}
|
|
}
|
|
|
|
// alias for backward-compatibility with modules. Use `haspaidsubscription` instead
|
|
async function userHasSubscriptionOrIsGroupMember(user) {
|
|
return await hasPaidSubscription(user)
|
|
}
|
|
|
|
async function userHasSubscription(user) {
|
|
const subscription = await SubscriptionLocator.promises.getUsersSubscription(
|
|
user._id
|
|
)
|
|
let hasValidSubscription = false
|
|
if (subscription) {
|
|
if (
|
|
subscription.recurlySubscription_id ||
|
|
subscription.paymentProvider?.subscriptionId ||
|
|
subscription.customAccount
|
|
) {
|
|
hasValidSubscription = true
|
|
}
|
|
}
|
|
return {
|
|
hasSubscription: hasValidSubscription,
|
|
subscription,
|
|
}
|
|
}
|
|
|
|
async function userIsMemberOfGroupSubscription(user) {
|
|
const subscriptions =
|
|
(await SubscriptionLocator.promises.getMemberSubscriptions(user._id)) || []
|
|
return { isMember: subscriptions.length > 0, subscriptions }
|
|
}
|
|
|
|
function teamHasReachedMemberLimit(subscription) {
|
|
const currentTotal =
|
|
(subscription.member_ids || []).length +
|
|
(subscription.teamInvites || []).length +
|
|
(subscription.invited_emails || []).length
|
|
|
|
return currentTotal >= subscription.membersLimit
|
|
}
|
|
|
|
async function hasGroupMembersLimitReached(subscriptionId, callback) {
|
|
const subscription =
|
|
await SubscriptionLocator.promises.getSubscription(subscriptionId)
|
|
if (!subscription) {
|
|
logger.warn({ subscriptionId }, 'no subscription found')
|
|
throw new Error('no subscription found')
|
|
}
|
|
const limitReached = teamHasReachedMemberLimit(subscription)
|
|
return { limitReached, subscription }
|
|
}
|
|
|
|
const LimitationsManager = {
|
|
allowedNumberOfCollaboratorsInProject: callbackify(
|
|
allowedNumberOfCollaboratorsInProject
|
|
),
|
|
allowedNumberOfCollaboratorsForUser: callbackify(
|
|
allowedNumberOfCollaboratorsForUser
|
|
),
|
|
canAddXEditCollaborators: callbackify(canAddXEditCollaborators),
|
|
canChangeCollaboratorPrivilegeLevel: callbackify(
|
|
canChangeCollaboratorPrivilegeLevel
|
|
),
|
|
hasPaidSubscription: callbackifyMultiResult(hasPaidSubscription, [
|
|
'hasPaidSubscription',
|
|
'subscription',
|
|
]),
|
|
userHasSubscriptionOrIsGroupMember: callbackifyMultiResult(
|
|
userHasSubscriptionOrIsGroupMember,
|
|
['hasPaidSubscription', 'subscription']
|
|
),
|
|
userHasSubscription: callbackifyMultiResult(userHasSubscription, [
|
|
'hasSubscription',
|
|
'subscription',
|
|
]),
|
|
userIsMemberOfGroupSubscription: callbackifyMultiResult(
|
|
userIsMemberOfGroupSubscription,
|
|
['isMember', 'subscriptions']
|
|
),
|
|
hasGroupMembersLimitReached: callbackifyMultiResult(
|
|
hasGroupMembersLimitReached,
|
|
['limitReached', 'subscription']
|
|
),
|
|
|
|
teamHasReachedMemberLimit,
|
|
|
|
promises: {
|
|
allowedNumberOfCollaboratorsInProject,
|
|
allowedNumberOfCollaboratorsForUser,
|
|
canAcceptEditCollaboratorInvite,
|
|
canAddXEditCollaborators,
|
|
canChangeCollaboratorPrivilegeLevel,
|
|
hasPaidSubscription,
|
|
userHasSubscriptionOrIsGroupMember,
|
|
userHasSubscription,
|
|
userIsMemberOfGroupSubscription,
|
|
hasGroupMembersLimitReached,
|
|
},
|
|
}
|
|
|
|
module.exports = LimitationsManager
|