resync_subscriptions.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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. id: subscription._id,
  30. name: subscription.planCode,
  31. })
  32. } else {
  33. ScriptLogger.allMismatchReasons[mismatchReasonsString] = [
  34. {
  35. id: subscription._id,
  36. name: subscription.planCode,
  37. },
  38. ]
  39. }
  40. },
  41. printProgress: () => {
  42. console.warn(
  43. `Subscriptions checked: ${ScriptLogger.checkedSubscriptionsCount}. Mismatches: ${ScriptLogger.mismatchSubscriptionsCount}`
  44. )
  45. },
  46. printSummary: () => {
  47. console.log('All Mismatch Reasons:', ScriptLogger.allMismatchReasons)
  48. console.log(
  49. 'Mismatch Subscriptions Count',
  50. ScriptLogger.mismatchSubscriptionsCount
  51. )
  52. },
  53. }
  54. const slowCallback = callback => setTimeout(callback, 80)
  55. const handleSyncSubscriptionError = (subscription, error, callback) => {
  56. console.warn(`Errors with subscription id=${subscription._id}:`, error)
  57. if (typeof error === 'string' && error.match(/429$/)) {
  58. return setTimeout(callback, 1000 * 60 * 5)
  59. }
  60. if (typeof error === 'string' && error.match(/5\d\d$/)) {
  61. return setTimeout(() => {
  62. syncSubscription(subscription, callback)
  63. }, 1000 * 60)
  64. }
  65. slowCallback(callback)
  66. }
  67. const syncSubscription = (subscription, callback) => {
  68. RecurlyWrapper.getSubscription(
  69. subscription.recurlySubscription_id,
  70. (error, recurlySubscription) => {
  71. if (error) {
  72. return handleSyncSubscriptionError(subscription, error, callback)
  73. }
  74. ScriptLogger.recordMismatch(subscription, recurlySubscription)
  75. if (!COMMIT) {
  76. return callback()
  77. }
  78. SubscriptionUpdater.updateSubscriptionFromRecurly(
  79. recurlySubscription,
  80. subscription,
  81. {},
  82. error => {
  83. if (error) {
  84. return handleSyncSubscriptionError(subscription, error, callback)
  85. }
  86. slowCallback(callback)
  87. }
  88. )
  89. }
  90. )
  91. }
  92. const syncSubscriptions = (subscriptions, callback) => {
  93. async.eachLimit(subscriptions, ASYNC_LIMIT, syncSubscription, callback)
  94. }
  95. const loopForSubscriptions = (skip, callback) => {
  96. Subscription.find({
  97. recurlySubscription_id: { $exists: true, $ne: '' },
  98. })
  99. .sort('_id')
  100. .skip(skip)
  101. .limit(FETCH_LIMIT)
  102. .exec((error, subscriptions) => {
  103. if (error) {
  104. return callback(error)
  105. }
  106. if (subscriptions.length === 0) {
  107. console.warn('DONE')
  108. return callback()
  109. }
  110. syncSubscriptions(subscriptions, error => {
  111. if (error) {
  112. return callback(error)
  113. }
  114. ScriptLogger.checkedSubscriptionsCount += subscriptions.length
  115. retryCounter = 0
  116. ScriptLogger.printProgress()
  117. ScriptLogger.printSummary()
  118. loopForSubscriptions(
  119. MONGO_SKIP + ScriptLogger.checkedSubscriptionsCount,
  120. callback
  121. )
  122. })
  123. })
  124. }
  125. let retryCounter = 0
  126. const run = () =>
  127. loopForSubscriptions(
  128. MONGO_SKIP + ScriptLogger.checkedSubscriptionsCount,
  129. error => {
  130. if (error) {
  131. if (retryCounter < 3) {
  132. console.error(error)
  133. retryCounter += 1
  134. console.warn(`RETRYING IN 60 SECONDS. (${retryCounter}/3)`)
  135. return setTimeout(run, 60000)
  136. }
  137. throw error
  138. }
  139. process.exit()
  140. }
  141. )
  142. let FETCH_LIMIT, ASYNC_LIMIT, COMMIT, MONGO_SKIP
  143. const setup = () => {
  144. const argv = minimist(process.argv.slice(2))
  145. FETCH_LIMIT = argv.fetch ? argv.fetch : 100
  146. ASYNC_LIMIT = argv.async ? argv.async : 10
  147. MONGO_SKIP = argv.skip ? argv.skip : 0
  148. COMMIT = argv.commit !== undefined
  149. if (!COMMIT) {
  150. console.warn('Doing dry run without --commit')
  151. }
  152. if (MONGO_SKIP) {
  153. console.warn(`Skipping first ${MONGO_SKIP} records`)
  154. }
  155. }
  156. setup()
  157. run()