resync_subscriptions.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. const { Subscription } = require('../../app/src/models/Subscription')
  2. const RecurlyWrapper = require('../../app/src/Features/Subscription/RecurlyWrapper')
  3. const SubscriptionUpdater = require('../../app/src/Features/Subscription/SubscriptionUpdater')
  4. const async = require('async')
  5. const minimist = require('minimist')
  6. // make sure all `allMismatchReasons` are displayed in the output
  7. const util = require('util')
  8. util.inspect.defaultOptions.maxArrayLength = null
  9. const ScriptLogger = {
  10. checkedSubscriptionsCount: 0,
  11. mismatchSubscriptionsCount: 0,
  12. allMismatchReasons: {},
  13. recordMismatch: (subscription, recurlySubscription) => {
  14. const mismatchReasons = {}
  15. if (subscription.planCode !== recurlySubscription.plan.plan_code) {
  16. mismatchReasons.recurlyPlan = recurlySubscription.plan.plan_code
  17. mismatchReasons.olPlan = subscription.planCode
  18. }
  19. if (recurlySubscription.state === 'expired') {
  20. mismatchReasons.state = 'expired'
  21. }
  22. if (!Object.keys(mismatchReasons).length) {
  23. return
  24. }
  25. ScriptLogger.mismatchSubscriptionsCount += 1
  26. const mismatchReasonsString = JSON.stringify(mismatchReasons)
  27. if (ScriptLogger.allMismatchReasons[mismatchReasonsString]) {
  28. ScriptLogger.allMismatchReasons[mismatchReasonsString].push(
  29. subscription._id
  30. )
  31. } else {
  32. ScriptLogger.allMismatchReasons[mismatchReasonsString] = [
  33. subscription._id,
  34. ]
  35. }
  36. },
  37. printProgress: () => {
  38. console.warn(
  39. `Subscriptions checked: ${ScriptLogger.checkedSubscriptionsCount}. Mismatches: ${ScriptLogger.mismatchSubscriptionsCount}`
  40. )
  41. },
  42. printSummary: () => {
  43. console.log('All Mismatch Reasons:', ScriptLogger.allMismatchReasons)
  44. console.log(
  45. 'Mismatch Subscriptions Count',
  46. ScriptLogger.mismatchSubscriptionsCount
  47. )
  48. },
  49. }
  50. const slowCallback = callback => setTimeout(callback, 80)
  51. const handleSyncSubscriptionError = (subscription, error, callback) => {
  52. console.warn(`Errors with subscription id=${subscription._id}:`, error)
  53. if (typeof error === 'string' && error.match(/429$/)) {
  54. return setTimeout(callback, 1000 * 60 * 5)
  55. }
  56. if (typeof error === 'string' && error.match(/5\d\d$/)) {
  57. return setTimeout(() => {
  58. syncSubscription(subscription, callback)
  59. }, 1000 * 60)
  60. }
  61. slowCallback(callback)
  62. }
  63. const syncSubscription = (subscription, callback) => {
  64. RecurlyWrapper.getSubscription(
  65. subscription.recurlySubscription_id,
  66. (error, recurlySubscription) => {
  67. if (error) {
  68. return handleSyncSubscriptionError(subscription, error, callback)
  69. }
  70. ScriptLogger.recordMismatch(subscription, recurlySubscription)
  71. if (!COMMIT) {
  72. return callback()
  73. }
  74. SubscriptionUpdater.updateSubscriptionFromRecurly(
  75. recurlySubscription,
  76. subscription,
  77. {},
  78. error => {
  79. if (error) {
  80. return handleSyncSubscriptionError(subscription, error, callback)
  81. }
  82. slowCallback(callback)
  83. }
  84. )
  85. }
  86. )
  87. }
  88. const syncSubscriptions = (subscriptions, callback) => {
  89. async.eachLimit(subscriptions, ASYNC_LIMIT, syncSubscription, callback)
  90. }
  91. const loopForSubscriptions = (skip, callback) => {
  92. Subscription.find({
  93. recurlySubscription_id: { $exists: true, $ne: '' },
  94. })
  95. .sort('_id')
  96. .skip(skip)
  97. .limit(FETCH_LIMIT)
  98. .exec((error, subscriptions) => {
  99. if (error) {
  100. return callback(error)
  101. }
  102. if (subscriptions.length === 0) {
  103. console.warn('DONE')
  104. return callback()
  105. }
  106. syncSubscriptions(subscriptions, error => {
  107. if (error) {
  108. return callback(error)
  109. }
  110. ScriptLogger.checkedSubscriptionsCount += subscriptions.length
  111. retryCounter = 0
  112. ScriptLogger.printProgress()
  113. ScriptLogger.printSummary()
  114. loopForSubscriptions(
  115. MONGO_SKIP + ScriptLogger.checkedSubscriptionsCount,
  116. callback
  117. )
  118. })
  119. })
  120. }
  121. let retryCounter = 0
  122. const run = () =>
  123. loopForSubscriptions(
  124. MONGO_SKIP + ScriptLogger.checkedSubscriptionsCount,
  125. error => {
  126. if (error) {
  127. if (retryCounter < 3) {
  128. console.error(error)
  129. retryCounter += 1
  130. console.warn(`RETRYING IN 60 SECONDS. (${retryCounter}/3)`)
  131. return setTimeout(run, 60000)
  132. }
  133. throw error
  134. }
  135. process.exit()
  136. }
  137. )
  138. let FETCH_LIMIT, ASYNC_LIMIT, COMMIT, MONGO_SKIP
  139. const setup = () => {
  140. const argv = minimist(process.argv.slice(2))
  141. FETCH_LIMIT = argv.fetch ? argv.fetch : 100
  142. ASYNC_LIMIT = argv.async ? argv.async : 10
  143. MONGO_SKIP = argv.skip ? argv.skip : 0
  144. COMMIT = argv.commit !== undefined
  145. if (!COMMIT) {
  146. console.warn('Doing dry run without --commit')
  147. }
  148. if (MONGO_SKIP) {
  149. console.warn(`Skipping first ${MONGO_SKIP} records`)
  150. }
  151. }
  152. setup()
  153. run()