backfill_recurly_to_subscription_mapping.mjs 3.4 KB

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