backfill_recurly_to_subscription_mapping.mjs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * This script backfills the account mapping for subscriptions that are active and have a group plan.
  3. *
  4. * The mapping joins a recurlySubscription_id to a subscription _id in BigQuery.
  5. *
  6. * This script has an assumption that it is being run in a clean slate condition, it will create some
  7. * duplicate mappings if run multiple times. The Analytics team will have the expectation
  8. * that this table may need to be deduplicated as it is an event sourcing record.
  9. *
  10. * Call it with `--commit` to actually register the mappings.
  11. * Call it with `--verbose` to see debug logs.
  12. * Call it with `--endDate=<subscription ID>` to stop processing at a certain date
  13. */
  14. import logger from '@overleaf/logger'
  15. import minimist from 'minimist'
  16. import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
  17. import { db } from '../../app/src/infrastructure/mongodb.js'
  18. import AccountMappingHelper from '../../app/src/Features/Analytics/AccountMappingHelper.js'
  19. import { registerAccountMapping } from '../../app/src/Features/Analytics/AnalyticsManager.js'
  20. import { triggerGracefulShutdown } from '../../app/src/infrastructure/GracefulShutdown.js'
  21. import Validation from '../../app/src/infrastructure/Validation.js'
  22. const paramsSchema = Validation.Joi.object({
  23. endDate: Validation.Joi.string().isoDate(),
  24. commit: Validation.Joi.boolean().default(false),
  25. verbose: Validation.Joi.boolean().default(false),
  26. }).unknown(true)
  27. let mapped = 0
  28. let subscriptionCount = 0
  29. const now = new Date().toISOString() // use the same timestamp for all mappings
  30. const seenSubscriptions = new Set()
  31. function registerMapping(subscription) {
  32. if (seenSubscriptions.has(subscription._id)) {
  33. logger.warn({ subscription }, 'duplicate subscription found, skipping')
  34. return
  35. }
  36. seenSubscriptions.add(subscription._id)
  37. subscriptionCount++
  38. const mapping = AccountMappingHelper.generateSubscriptionToRecurlyMapping(
  39. subscription._id,
  40. subscription.recurlySubscription_id,
  41. now
  42. )
  43. logger.debug(
  44. {
  45. recurly: subscription.recurlySubscription_id,
  46. mapping,
  47. },
  48. `processing subscription ${subscription._id}`
  49. )
  50. if (commit) {
  51. registerAccountMapping(mapping)
  52. mapped++
  53. }
  54. }
  55. async function main() {
  56. const additionalBatchedUpdateOptions = {}
  57. if (endDate) {
  58. additionalBatchedUpdateOptions.BATCH_RANGE_END = endDate
  59. }
  60. await batchedUpdate(
  61. db.subscriptions,
  62. {
  63. 'recurlyStatus.state': 'active',
  64. groupPlan: true,
  65. },
  66. subscriptions => subscriptions.forEach(registerMapping),
  67. {
  68. _id: 1,
  69. recurlySubscription_id: 1,
  70. },
  71. {
  72. readPreference: 'secondaryPreferred',
  73. },
  74. {
  75. verboseLogging: verbose,
  76. ...additionalBatchedUpdateOptions,
  77. }
  78. )
  79. logger.debug({}, `${subscriptionCount} subscriptions processed`)
  80. if (commit) {
  81. logger.debug({}, `${mapped} mappings registered`)
  82. }
  83. }
  84. const {
  85. error,
  86. value: { commit, endDate, verbose },
  87. } = paramsSchema.validate(
  88. minimist(process.argv.slice(2), {
  89. boolean: ['commit', 'verbose'],
  90. string: ['endDate'],
  91. })
  92. )
  93. logger.logger.level(verbose ? 'debug' : 'info')
  94. if (error) {
  95. logger.error({ error }, 'error with parameters')
  96. triggerGracefulShutdown(done => done(1))
  97. } else {
  98. logger.info({ verbose, commit, endDate }, commit ? 'COMMITTING' : 'DRY RUN')
  99. await main()
  100. triggerGracefulShutdown({
  101. close(done) {
  102. logger.info({}, 'shutting down')
  103. done()
  104. },
  105. })
  106. }