import { Subscription } from '../../app/src/models/Subscription.js' import RecurlyWrapper from '../../app/src/Features/Subscription/RecurlyWrapper.js' import SubscriptionUpdater from '../../app/src/Features/Subscription/SubscriptionUpdater.js' import minimist from 'minimist' import { setTimeout } from 'node:timers/promises' import util from 'node:util' import pLimit from 'p-limit' util.inspect.defaultOptions.maxArrayLength = null const ScriptLogger = { checkedSubscriptionsCount: 0, mismatchSubscriptionsCount: 0, allMismatchReasons: {}, // make sure all `allMismatchReasons` are displayed in the output recordMismatch: (subscription, recurlySubscription) => { const mismatchReasons = {} if (subscription.planCode !== recurlySubscription.plan.plan_code) { mismatchReasons.recurlyPlan = recurlySubscription.plan.plan_code mismatchReasons.olPlan = subscription.planCode } if (recurlySubscription.state === 'expired') { mismatchReasons.state = 'expired' } if (!Object.keys(mismatchReasons).length) { return } ScriptLogger.mismatchSubscriptionsCount += 1 const mismatchReasonsString = JSON.stringify(mismatchReasons) if (ScriptLogger.allMismatchReasons[mismatchReasonsString]) { ScriptLogger.allMismatchReasons[mismatchReasonsString].push({ id: subscription._id, name: subscription.planCode, }) } else { ScriptLogger.allMismatchReasons[mismatchReasonsString] = [ { id: subscription._id, name: subscription.planCode, }, ] } }, printProgress: () => { console.warn( `Subscriptions checked: ${ScriptLogger.checkedSubscriptionsCount}. Mismatches: ${ScriptLogger.mismatchSubscriptionsCount}` ) }, printSummary: () => { console.log('All Mismatch Reasons:', ScriptLogger.allMismatchReasons) console.log( 'Mismatch Subscriptions Count', ScriptLogger.mismatchSubscriptionsCount ) }, } const handleSyncSubscriptionError = async (subscription, error) => { console.warn(`Errors with subscription id=${subscription._id}:`, error) if (typeof error === 'string' && error.match(/429$/)) { await setTimeout(1000 * 60 * 5) return } if (typeof error === 'string' && error.match(/5\d\d$/)) { await setTimeout(1000 * 60) await syncSubscription(subscription) return } await setTimeout(80) } const syncSubscription = async subscription => { let recurlySubscription try { recurlySubscription = await RecurlyWrapper.promises.getSubscription( subscription.recurlySubscription_id ) } catch (error) { await handleSyncSubscriptionError(subscription, error) return } ScriptLogger.recordMismatch(subscription, recurlySubscription) if (!COMMIT) { return } try { await SubscriptionUpdater.promises.updateSubscriptionFromRecurly( recurlySubscription, subscription, {} ) } catch (error) { await handleSyncSubscriptionError(subscription, error) return } await setTimeout(80) } const syncSubscriptions = async subscriptions => { const limit = pLimit(ASYNC_LIMIT) return await Promise.all( subscriptions.map(subscription => limit(() => syncSubscription(subscription)) ) ) } const loopForSubscriptions = async skipInitial => { let skip = skipInitial // iterate while there are more subscriptions to fetch while (true) { const subscriptions = await Subscription.find({ recurlySubscription_id: { $exists: true, $ne: '' }, }) .sort('_id') .skip(skip) .limit(FETCH_LIMIT) .exec() if (subscriptions.length === 0) { console.warn('DONE') return } await syncSubscriptions(subscriptions) ScriptLogger.checkedSubscriptionsCount += subscriptions.length retryCounter = 0 ScriptLogger.printProgress() ScriptLogger.printSummary() skip += FETCH_LIMIT } } let retryCounter = 0 const run = async () => { while (true) { try { await loopForSubscriptions( MONGO_SKIP + ScriptLogger.checkedSubscriptionsCount ) break } catch (error) { if (retryCounter < 3) { console.error(error) retryCounter += 1 console.warn(`RETRYING IN 60 SECONDS. (${retryCounter}/3)`) await setTimeout(60000) } else { console.error('Failed after 3 retries') throw error } } } } let FETCH_LIMIT, ASYNC_LIMIT, COMMIT, MONGO_SKIP const setup = () => { const argv = minimist(process.argv.slice(2)) FETCH_LIMIT = argv.fetch ? argv.fetch : 100 ASYNC_LIMIT = argv.async ? argv.async : 10 MONGO_SKIP = argv.skip ? argv.skip : 0 COMMIT = argv.commit !== undefined if (!COMMIT) { console.warn('Doing dry run without --commit') } if (MONGO_SKIP) { console.warn(`Skipping first ${MONGO_SKIP} records`) } } setup() await run() process.exit()