resync_subscriptions.js 4.8 KB

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