SubscriptionLocator.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * @import { AddOn } from '../../../../types/subscription/plan'
  3. */
  4. const { callbackifyAll } = require('@overleaf/promise-utils')
  5. const { Subscription } = require('../../models/Subscription')
  6. const SubscriptionHelper = require('./SubscriptionHelper')
  7. const { DeletedSubscription } = require('../../models/DeletedSubscription')
  8. const logger = require('@overleaf/logger')
  9. const { AI_ADD_ON_CODE, isStandaloneAiAddOnPlanCode } = require('./AiHelper')
  10. require('./GroupPlansData') // 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 getGroupsWithEmailInvite(email) {
  92. return await Subscription.find({ invited_emails: email }).exec()
  93. },
  94. async getGroupsWithTeamInvitesEmail(email) {
  95. return await Subscription.find(
  96. { teamInvites: { $elemMatch: { email } } },
  97. { teamInvites: 1 }
  98. ).exec()
  99. },
  100. async getGroupWithV1Id(v1TeamId) {
  101. return await Subscription.findOne({ 'overleaf.id': v1TeamId }).exec()
  102. },
  103. async getUserDeletedSubscriptions(userId) {
  104. return await DeletedSubscription.find({
  105. 'subscription.admin_id': userId,
  106. }).exec()
  107. },
  108. async getDeletedSubscription(subscriptionId) {
  109. return await DeletedSubscription.findOne({
  110. 'subscription._id': subscriptionId,
  111. }).exec()
  112. },
  113. async hasAiAssist(userOrId) {
  114. const userId = SubscriptionLocator._getUserId(userOrId)
  115. const subscription = await Subscription.findOne({ admin_id: userId }).exec()
  116. // todo: as opposed to recurlyEntities which use addon.code, subscription model uses addon.addOnCode
  117. // which we hope to align via https://github.com/overleaf/internal/issues/25494
  118. return Boolean(
  119. (subscription?.planCode &&
  120. isStandaloneAiAddOnPlanCode(subscription?.planCode)) ||
  121. subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE)
  122. )
  123. },
  124. _getUserId(userOrId) {
  125. if (userOrId && userOrId._id) {
  126. return userOrId._id
  127. } else if (userOrId) {
  128. return userOrId
  129. }
  130. },
  131. /**
  132. * Retrieves the last successful subscription for a given user.
  133. *
  134. * @async
  135. * @function
  136. * @param {string} recurlyId - The ID of the recurly subscription tied to the mongo subscription to check for a previous successful state.
  137. * @returns {Promise<{_id: ObjectId, planCode: string, addOns: [AddOn]}|null>} A promise that resolves to the last successful planCode and addon state,
  138. * or null if we havent stored a previous
  139. */
  140. async getLastSuccessfulSubscription(recurlyId) {
  141. const subscription = await Subscription.findOne({
  142. recurlySubscription_id: recurlyId,
  143. }).exec()
  144. return subscription && subscription.lastSuccesfulSubscription
  145. ? {
  146. ...subscription.lastSuccesfulSubscription,
  147. _id: subscription._id,
  148. }
  149. : null
  150. },
  151. async getUserSubscriptionStatus(userId) {
  152. let usersSubscription = { personal: false, group: false }
  153. if (!userId) {
  154. return usersSubscription
  155. }
  156. const memberSubscriptions =
  157. await SubscriptionLocator.getMemberSubscriptions(userId)
  158. const hasActiveGroupSubscription = memberSubscriptions.some(
  159. subscription =>
  160. subscription.groupPlan &&
  161. SubscriptionHelper.getPaidSubscriptionState(subscription) === 'active'
  162. )
  163. if (hasActiveGroupSubscription) {
  164. // Member of a group plan
  165. usersSubscription = { ...usersSubscription, group: true }
  166. }
  167. const personalSubscription =
  168. await SubscriptionLocator.getUsersSubscription(userId)
  169. if (personalSubscription) {
  170. const hasActivePersonalSubscription =
  171. SubscriptionHelper.getPaidSubscriptionState(personalSubscription) ===
  172. 'active'
  173. if (hasActivePersonalSubscription) {
  174. if (personalSubscription.groupPlan) {
  175. // Owner of a group plan
  176. usersSubscription = { ...usersSubscription, group: true }
  177. } else {
  178. // Owner of an individual plan
  179. usersSubscription = { ...usersSubscription, personal: true }
  180. }
  181. }
  182. }
  183. return usersSubscription
  184. },
  185. }
  186. module.exports = {
  187. ...callbackifyAll(SubscriptionLocator),
  188. promises: SubscriptionLocator,
  189. }