SubscriptionLocator.mjs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. /**
  2. * @import { AddOn } from '../../../../types/subscription/plan'
  3. */
  4. import { callbackifyAll } from '@overleaf/promise-utils'
  5. import { Subscription } from '../../models/Subscription.mjs'
  6. import SubscriptionHelper from './SubscriptionHelper.mjs'
  7. import { DeletedSubscription } from '../../models/DeletedSubscription.mjs'
  8. import logger from '@overleaf/logger'
  9. import { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } from './AiHelper.mjs'
  10. import './GroupPlansData.mjs' // make sure dynamic group plans are loaded
  11. import Features from '../../infrastructure/Features.mjs'
  12. const SubscriptionLocator = {
  13. async getUsersSubscription(userOrId) {
  14. if (!Features.hasFeature('saas')) return undefined
  15. const userId = SubscriptionLocator._getUserId(userOrId)
  16. const subscription = await Subscription.findOne({ admin_id: userId }).exec()
  17. logger.debug({ userId }, 'got users subscription')
  18. if (subscription) {
  19. return await SubscriptionHelper.recomputeSubscriptionState(subscription)
  20. }
  21. return subscription
  22. },
  23. async getUserIndividualSubscription(userOrId) {
  24. if (!Features.hasFeature('saas')) return undefined
  25. const userId = SubscriptionLocator._getUserId(userOrId)
  26. const subscription = await Subscription.findOne({
  27. admin_id: userId,
  28. groupPlan: false,
  29. }).exec()
  30. logger.debug({ userId }, 'got users individual subscription')
  31. return subscription
  32. },
  33. async getManagedGroupSubscriptions(userOrId) {
  34. if (!Features.hasFeature('saas')) return []
  35. return await Subscription.find({
  36. manager_ids: userOrId,
  37. groupPlan: true,
  38. })
  39. .populate('admin_id', ['_id', 'email'])
  40. .exec()
  41. },
  42. async getMemberSubscriptions(userOrId, populate = []) {
  43. if (!Features.hasFeature('saas')) return []
  44. const userId = SubscriptionLocator._getUserId(userOrId)
  45. // eslint-disable-next-line no-restricted-syntax
  46. return await Subscription.find({ member_ids: userId })
  47. .populate('admin_id', 'email')
  48. .populate(populate)
  49. .exec()
  50. },
  51. async getAdminEmail(subscriptionId) {
  52. if (!Features.hasFeature('saas')) return undefined
  53. const subscription = await Subscription.findById(subscriptionId)
  54. .populate('admin_id', 'email')
  55. .exec()
  56. return subscription?.admin_id?.email
  57. },
  58. async getAdminEmailAndName(subscriptionId) {
  59. if (!Features.hasFeature('saas')) return undefined
  60. const subscription = await Subscription.findById(subscriptionId)
  61. .populate('admin_id', ['email', 'first_name', 'last_name'])
  62. .exec()
  63. return subscription?.admin_id
  64. },
  65. async hasRecurlyGroupSubscription(userOrId) {
  66. if (!Features.hasFeature('saas')) return false
  67. const userId = SubscriptionLocator._getUserId(userOrId)
  68. return await Subscription.exists({
  69. groupPlan: true,
  70. recurlySubscription_id: { $exists: true },
  71. $or: [
  72. { member_ids: userId },
  73. { manager_ids: userId },
  74. { admin_id: userId },
  75. ],
  76. }).exec()
  77. },
  78. async getSubscription(subscriptionId) {
  79. return await Subscription.findOne({ _id: subscriptionId }).exec()
  80. },
  81. async getSubscriptionByMemberIdAndId(userId, subscriptionId) {
  82. return await Subscription.findOne(
  83. { member_ids: userId, _id: subscriptionId },
  84. { _id: 1 }
  85. ).exec()
  86. },
  87. async getGroupSubscriptionsMemberOf(userId) {
  88. if (!Features.hasFeature('saas')) return []
  89. return await Subscription.find(
  90. { member_ids: userId },
  91. { _id: 1, planCode: 1, userFeaturesDisabled: 1 }
  92. )
  93. },
  94. async getUniqueManagedSubscriptionMemberOf(userId) {
  95. if (!Features.hasFeature('saas')) return null
  96. return await Subscription.findOne(
  97. { member_ids: userId, managedUsersEnabled: true },
  98. { _id: 1 }
  99. )
  100. },
  101. async getUniqueManagedSubscriptionUserAssociation(userId) {
  102. return await Subscription.findOne(
  103. {
  104. managedUsersEnabled: true,
  105. $or: [
  106. { member_ids: userId },
  107. { manager_ids: userId },
  108. { admin_id: userId },
  109. ],
  110. },
  111. { _id: 1 }
  112. )
  113. },
  114. async getGroupsWithEmailInvite(email) {
  115. return await Subscription.find({ invited_emails: email }).exec()
  116. },
  117. async getGroupsWithTeamInvitesEmail(email) {
  118. if (!Features.hasFeature('saas')) return []
  119. return await Subscription.find(
  120. {
  121. teamInvites: { $elemMatch: { email } },
  122. // Add partialFilterExpression from index.
  123. 'teamInvites.email': { $exists: true },
  124. },
  125. { teamInvites: 1 }
  126. ).exec()
  127. },
  128. async getGroupWithV1Id(v1TeamId) {
  129. return await Subscription.findOne({ 'overleaf.id': v1TeamId }).exec()
  130. },
  131. async getUserDeletedSubscriptions(userId) {
  132. if (!Features.hasFeature('saas')) return []
  133. return await DeletedSubscription.find({
  134. 'subscription.admin_id': userId,
  135. }).exec()
  136. },
  137. async getDeletedSubscription(subscriptionId) {
  138. return await DeletedSubscription.findOne({
  139. 'subscription._id': subscriptionId,
  140. }).exec()
  141. },
  142. async hasAiAssist(userOrId) {
  143. const userId = SubscriptionLocator._getUserId(userOrId)
  144. const subscription = await Subscription.findOne({ admin_id: userId }).exec()
  145. // todo: as opposed to recurlyEntities which use addon.code, subscription model uses addon.addOnCode
  146. // which we hope to align via https://github.com/overleaf/internal/issues/25494
  147. return Boolean(
  148. (subscription?.planCode &&
  149. isStandaloneAiAddOnPlanCode(subscription?.planCode)) ||
  150. subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE)
  151. )
  152. },
  153. _getUserId(userOrId) {
  154. if (userOrId && userOrId._id) {
  155. return userOrId._id
  156. } else if (userOrId) {
  157. return userOrId
  158. }
  159. },
  160. /**
  161. * Retrieves the last successful subscription for a given user.
  162. *
  163. * @async
  164. * @function
  165. * @param {string} recurlyId - The ID of the recurly subscription tied to the mongo subscription to check for a previous successful state.
  166. * @returns {Promise<{_id: ObjectId, planCode: string, addOns: [AddOn]}|null>} A promise that resolves to the last successful planCode and addon state,
  167. * or null if we havent stored a previous
  168. */
  169. async getLastSuccessfulSubscription(recurlyId) {
  170. const subscription = await Subscription.findOne({
  171. recurlySubscription_id: recurlyId,
  172. }).exec()
  173. return subscription && subscription.lastSuccesfulSubscription
  174. ? {
  175. ...subscription.lastSuccesfulSubscription,
  176. _id: subscription._id,
  177. }
  178. : null
  179. },
  180. async getUserSubscriptionStatus(userId) {
  181. let usersSubscription = { personal: false, group: false }
  182. if (!userId) {
  183. return usersSubscription
  184. }
  185. const memberSubscriptions =
  186. await SubscriptionLocator.getMemberSubscriptions(userId)
  187. const hasActiveGroupSubscription = memberSubscriptions.some(
  188. subscription =>
  189. subscription.groupPlan &&
  190. SubscriptionHelper.getPaidSubscriptionState(subscription) === 'active'
  191. )
  192. if (hasActiveGroupSubscription) {
  193. // Member of a group plan
  194. usersSubscription = { ...usersSubscription, group: true }
  195. }
  196. const personalSubscription =
  197. await SubscriptionLocator.getUsersSubscription(userId)
  198. if (personalSubscription) {
  199. const hasActivePersonalSubscription =
  200. SubscriptionHelper.getPaidSubscriptionState(personalSubscription) ===
  201. 'active'
  202. if (hasActivePersonalSubscription) {
  203. if (personalSubscription.groupPlan) {
  204. // Owner of a group plan
  205. usersSubscription = { ...usersSubscription, group: true }
  206. } else {
  207. // Owner of an individual plan
  208. usersSubscription = { ...usersSubscription, personal: true }
  209. }
  210. }
  211. }
  212. return usersSubscription
  213. },
  214. }
  215. export default {
  216. ...callbackifyAll(SubscriptionLocator),
  217. promises: SubscriptionLocator,
  218. }