diff --git a/core/server/data/migrations/versions/4.0/14-populate-members-paid-subscription-events-table.js b/core/server/data/migrations/versions/4.0/14-populate-members-paid-subscription-events-table.js new file mode 100644 index 0000000000..4752f2a0f3 --- /dev/null +++ b/core/server/data/migrations/versions/4.0/14-populate-members-paid-subscription-events-table.js @@ -0,0 +1,104 @@ +const {chunk} = require('lodash'); +const ObjectID = require('bson-objectid'); +const {createTransactionalMigration} = require('../../utils'); +const logging = require('../../../../../shared/logging'); + +module.exports = createTransactionalMigration( + async function up(knex) { + logging.info('Populating members_paid_subscription_events from members_stripe_customers_subscriptions'); + const allSubscriptions = await knex + .select( + 'c.member_id', + 's.status', + 's.start_date', + 's.updated_at', + 's.created_at', + 's.plan_id', + 's.plan_amount', + 's.plan_currency', + 's.plan_interval' + ) + .from('members_stripe_customers_subscriptions as s') + .join('members_stripe_customers as c', 'c.customer_id', '=', 's.customer_id'); + + function calculateMrrDelta({interval, amount}) { + if (interval === 'year') { + return Math.floor(amount / 12); + } + + if (interval === 'month') { + return amount; + } + } + + const allEvents = allSubscriptions.reduce((allEventsAcc, subscription) => { + if (['incomplete', 'incomplete_expired'].includes(subscription.status)) { + return allEventsAcc; + } + + const events = []; + + if (subscription.status === 'trialing') { + const subscriptionCreatedEvent = { + id: ObjectID.generate(), + member_id: subscription.member_id, + from_plan: null, + to_plan: subscription.plan_id, + currency: subscription.plan_currency, + source: 'stripe', + mrr_delta: 0, + created_at: subscription.start_date + }; + events.push(subscriptionCreatedEvent); + } else { + const subscriptionCreatedEvent = { + id: ObjectID.generate(), + member_id: subscription.member_id, + from_plan: null, + to_plan: subscription.plan_id, + currency: subscription.plan_currency, + source: 'stripe', + mrr_delta: calculateMrrDelta({ + amount: subscription.plan_amount, + interval: subscription.plan_interval + }), + created_at: subscription.start_date + }; + events.push(subscriptionCreatedEvent); + } + + if (subscription.status === 'canceled') { + const subscriptionCancelledEvent = { + id: ObjectID.generate(), + member_id: subscription.member_id, + from_plan: subscription.plan_id, + to_plan: null, + currency: subscription.plan_currency, + source: 'stripe', + mrr_delta: -1 * calculateMrrDelta({ + amount: subscription.plan_amount, + interval: subscription.plan_interval + }), + created_at: subscription.updated_at + }; + events.push(subscriptionCancelledEvent); + } + + return allEventsAcc.concat(events); + }, []); + + // SQLite3 supports 999 variables max, each row uses 8 variables so ⌊999/8⌋ = 124 + const chunkSize = 124; + + const eventChunks = chunk(allEvents, chunkSize); + + for (const events of eventChunks) { + await knex.insert(events).into('members_paid_subscription_events'); + } + }, + async function down(knex) { + logging.info('Deleting all members_paid_subscription_events'); + return knex('members_paid_subscription_events').del(); + } +); +