backfill_recurly_to_subscription_mapping.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. import { scriptRunner } from '../lib/ScriptRunner.mjs'
  23. const paramsSchema = Validation.Joi.object({
  24. endDate: Validation.Joi.string().isoDate(),
  25. commit: Validation.Joi.boolean().default(false),
  26. verbose: Validation.Joi.boolean().default(false),
  27. }).unknown(true)
  28. let mapped = 0
  29. let subscriptionCount = 0
  30. const now = new Date().toISOString() // use the same timestamp for all mappings
  31. const seenSubscriptions = new Set()
  32. function registerMapping(subscription) {
  33. if (seenSubscriptions.has(subscription._id)) {
  34. logger.warn({ subscription }, 'duplicate subscription found, skipping')
  35. return
  36. }
  37. seenSubscriptions.add(subscription._id)
  38. subscriptionCount++
  39. const mapping = AccountMappingHelper.generateSubscriptionToRecurlyMapping(
  40. subscription._id,
  41. subscription.recurlySubscription_id,
  42. now
  43. )
  44. logger.debug(
  45. {
  46. recurly: subscription.recurlySubscription_id,
  47. mapping,
  48. },
  49. `processing subscription ${subscription._id}`
  50. )
  51. if (commit) {
  52. registerAccountMapping(mapping)
  53. mapped++
  54. }
  55. }
  56. async function main(trackProgress) {
  57. const additionalBatchedUpdateOptions = {}
  58. if (endDate) {
  59. additionalBatchedUpdateOptions.BATCH_RANGE_END = endDate
  60. }
  61. await batchedUpdate(
  62. db.subscriptions,
  63. {
  64. 'recurlyStatus.state': 'active',
  65. groupPlan: true,
  66. },
  67. subscriptions => subscriptions.forEach(registerMapping),
  68. {
  69. _id: 1,
  70. recurlySubscription_id: 1,
  71. },
  72. {
  73. readPreference: 'secondaryPreferred',
  74. },
  75. {
  76. verboseLogging: verbose,
  77. ...additionalBatchedUpdateOptions,
  78. trackProgress,
  79. }
  80. )
  81. logger.debug({}, `${subscriptionCount} subscriptions processed`)
  82. if (commit) {
  83. logger.debug({}, `${mapped} mappings registered`)
  84. }
  85. }
  86. const {
  87. error,
  88. value: { commit, endDate, verbose },
  89. } = paramsSchema.validate(
  90. minimist(process.argv.slice(2), {
  91. boolean: ['commit', 'verbose'],
  92. string: ['endDate'],
  93. })
  94. )
  95. logger.logger.level(verbose ? 'debug' : 'info')
  96. if (error) {
  97. logger.error({ error }, 'error with parameters')
  98. triggerGracefulShutdown(done => done(1))
  99. } else {
  100. logger.info({ verbose, commit, endDate }, commit ? 'COMMITTING' : 'DRY RUN')
  101. await scriptRunner(main)
  102. triggerGracefulShutdown({
  103. close(done) {
  104. logger.info({}, 'shutting down')
  105. done()
  106. },
  107. })
  108. }