SubscriptionLocator.mjs 7.0 KB

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