Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
Add v3.29.0
Browse files Browse the repository at this point in the history
  • Loading branch information
YannickRe committed Aug 10, 2020
1 parent 5e74119 commit f28976a
Show file tree
Hide file tree
Showing 25 changed files with 949 additions and 721 deletions.
10 changes: 10 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ const configureGrunt = function (grunt) {
grunt.loadNpmTasks('grunt-subgrunt');
grunt.loadNpmTasks('grunt-update-submodules');

/** This little bit of weirdness gives the express server chance to shutdown properly */
const waitBeforeExit = () => {
setTimeout(() => {
process.exit(0);
}, 1000);
};

process.on('SIGINT', waitBeforeExit);
process.on('SIGTERM', waitBeforeExit);

const cfg = {
// #### Common paths used by tasks
paths: {
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function makeGhost(options) {
err = new errors.GhostError({message: err.message, err: err});
}

return GhostServer.announceServerStopped(err)
return GhostServer.announceServerReadiness(err)
.finally(() => {
throw err;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const logging = require('../../../../../shared/logging');

module.exports = {
config: {
transaction: true
},

async up({transacting: knex}) {
if (knex.client.config.client !== 'mysql') {
logging.warn('Skipping cleanup of duplicate subscriptions - database is not MySQL');
return;
}

const duplicates = await knex('members_stripe_customers_subscriptions')
.select('subscription_id')
.count('subscription_id as count')
.groupBy('subscription_id')
.having('count', '>', 1);

if (!duplicates.length) {
logging.info('No duplicate subscriptions found');
return;
}

logging.info(`Found ${duplicates.length} duplicate stripe subscriptions`);
for (const duplicate of duplicates) {
const subscriptions = await knex('members_stripe_customers_subscriptions')
.select()
.where('subscription_id', duplicate.subscription_id);

const orderedSubscriptions = subscriptions.sort((subA, subB) => {
return subB.updated_at - subA.updated_at;
});

const [newestSubscription, ...olderSubscriptions] = orderedSubscriptions;

logging.info(`Keeping newest subscription ${newestSubscription.id} - ${newestSubscription.subscription_id}, last updated at ${newestSubscription.updated_at}`);

for (const subscriptionToDelete of olderSubscriptions) {
logging.info(`Deleting duplicate subscription ${subscriptionToDelete.id} - ${subscriptionToDelete.subscription_id}, last updated at ${subscriptionToDelete.updated_at}`);
await knex('members_stripe_customers_subscriptions')
.where({id: subscriptionToDelete.id})
.del();
}
}
},

// noop for down
async down() {}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const logging = require('../../../../../shared/logging');

module.exports = {
config: {
transaction: true
},

async up({transacting: knex}) {
if (knex.client.config.client !== 'mysql') {
logging.warn('Skipping cleanup of duplicate customers - database is not MySQL');
return;
}

const duplicates = await knex('members_stripe_customers')
.select('customer_id')
.count('customer_id as count')
.groupBy('customer_id')
.having('count', '>', 1);

if (!duplicates.length) {
logging.info('No duplicate customers found');
return;
}

logging.info(`Found ${duplicates.length} duplicate stripe customers`);
for (const duplicate of duplicates) {
const customers = await knex('members_stripe_customers')
.select()
.where('customer_id', duplicate.customer_id);

const orderedCustomers = customers.sort((subA, subB) => {
return subB.updated_at - subA.updated_at;
});

const [newestCustomer, ...olderCustomers] = orderedCustomers;

logging.info(`Keeping newest customer ${newestCustomer.id} - ${newestCustomer.customer_id}, last updated at ${newestCustomer.updated_at}`);

for (const customerToDelete of olderCustomers) {
logging.info(`Deleting duplicate customer ${customerToDelete.id} - ${customerToDelete.customer_id}, last updated at ${customerToDelete.updated_at}`);
await knex('members_stripe_customers')
.where({id: customerToDelete.id})
.del();
}
}
},

// noop for down
async down() {}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const logging = require('../../../../../shared/logging');

module.exports = {
config: {
transaction: true
},

async up({transacting: knex}) {
if (knex.client.config.client !== 'mysql') {
logging.warn('Skipping cleanup of orphaned customers - database is not MySQL');
return;
}

const orphanedCustomers = await knex('members_stripe_customers')
.select('id')
.whereNotIn(
'member_id',
knex('members')
.select('id')
);

if (!orphanedCustomers || !orphanedCustomers.length) {
logging.info('No orphaned customer records found');
return;
}

logging.info(`Deleting ${orphanedCustomers.length} orphaned customers`);
await knex('members_stripe_customers')
.whereIn('id', orphanedCustomers.map(customer => customer.id))
.del();
},

async down() {}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const logging = require('../../../../../shared/logging');

module.exports = {
config: {
transaction: true
},

async up({transacting: knex}) {
if (knex.client.config.client !== 'mysql') {
logging.warn('Skipping cleanup of orphaned subscriptions - database is not MySQL');
return;
}

const orphanedSubscriptions = await knex('members_stripe_customers_subscriptions')
.select('id')
.whereNotIn(
'customer_id',
knex('members_stripe_customers')
.select('customer_id')
);

if (!orphanedSubscriptions || !orphanedSubscriptions.length) {
logging.info('No orphaned subscription records found');
return;
}

logging.info(`Deleting ${orphanedSubscriptions.length} orphaned subscriptions`);
await knex('members_stripe_customers_subscriptions')
.whereIn('id', orphanedSubscriptions.map(subscription => subscription.id))
.del();
},

async down() {}
};
174 changes: 174 additions & 0 deletions core/server/data/migrations/versions/3.29/05-add-member-constraints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
const logging = require('../../../../../shared/logging');

module.exports = {
config: {
transaction: true
},

async up({transacting: knex}) {
if (knex.client.config.client !== 'mysql') {
return logging.warn('Skipping member tables index creation - database is not MySQL');
}

// member_labels already has a foreign key constraint, we want to add ON DELETE CASCADE

const dbName = knex.client.config.connection.database;
const [dbConstraints] = await knex.raw('SELECT * FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=?', [dbName]);

const memberIdConstraint = dbConstraints.find(constraint => constraint.CONSTRAINT_NAME === 'members_labels_member_id_foreign');
if (memberIdConstraint && memberIdConstraint.DELETE_RULE === 'CASCADE') {
logging.warn('Skipping ON DELETE CASCADE for "members_labels_member_id_foreign" constraint - already set');
} else if (memberIdConstraint) {
logging.info('Adding ON DELETE CASCADE to "members_labels_member_id_foreign" constraint');
// first drop the key
await knex.schema.alterTable('members_labels', (table) => {
table.dropForeign('member_id');
table.dropIndex('member_id', 'members_labels_member_id_foreign');
});
// then re-add with ON DELETE CASCADE
await knex.schema.alterTable('members_labels', (table) => {
table.foreign('member_id').references('members.id').onDelete('CASCADE');
});
}

const labelIdConstraint = dbConstraints.find(constraint => constraint.CONSTRAINT_NAME === 'members_labels_label_id_foreign');
if (labelIdConstraint && labelIdConstraint.DELETE_RULE === 'CASCADE') {
logging.warn('Skipping ON DELETE CASCADE for "members_labels_label_id_foreign" constraint - already set');
} else if (labelIdConstraint) {
logging.info('Adding ON DELETE CASCADE to "members_labels_label_id_foreign" constraint');
// first drop the key
await knex.schema.alterTable('members_labels', (table) => {
table.dropForeign('label_id');
table.dropIndex('label_id', 'members_labels_label_id_foreign');
});
// then re-add with ON DELETE CASCADE
await knex.schema.alterTable('members_labels', (table) => {
table.foreign('label_id').references('labels.id').onDelete('CASCADE');
});
}

// stripe tables have not had any indexes/constraints in the past, add them now with ON DELETE CASCADE

const [membersStripeCustomersIndexes] = await knex.raw('SHOW INDEXES FROM members_stripe_customers');
const [membersStripeCustomersSubscriptionsIndexes] = await knex.raw('SHOW INDEXES from members_stripe_customers_subscriptions');

if (membersStripeCustomersIndexes.find(index => index.Key_name === 'members_stripe_customers_member_id_foreign')) {
logging.warn('Skipping "members_stripe_customers_member_id_foreign" foreign key constraint creation - already exists');
} else {
logging.info('Adding "members_stripe_customers_member_id_foreign" foreign key constraint');
await knex.schema.alterTable('members_stripe_customers', (table) => {
table.foreign('member_id').references('members.id').onDelete('CASCADE');
});
}

if (membersStripeCustomersIndexes.find(index => index.Key_name === 'members_stripe_customers_customer_id_unique')) {
logging.warn('Skipping "members_stripe_customers_customer_id_unique" index creation - already exists');
} else {
logging.info('Adding "members_stripe_customers_customer_id_unique" index');
await knex.schema.alterTable('members_stripe_customers', (table) => {
table.unique('customer_id');
});
}

if (membersStripeCustomersIndexes.find(index => index.Key_name === 'members_stripe_customers_subscriptions_subscription_id_unique')) {
logging.warn('Skipping "members_stripe_customers_subscriptions_subscription_id_unique" index creation - already exists');
} else {
logging.info('Adding "members_stripe_customers_subscriptions_subscription_id_unique" index');
await knex.schema.alterTable('members_stripe_customers_subscriptions', (table) => {
table.unique('subscription_id');
});
}

if (membersStripeCustomersSubscriptionsIndexes.find(index => index.Key_name === 'members_stripe_customers_subscriptions_customer_id_foreign')) {
logging.warn('Skipping "members_stripe_customers_subscriptions_customer_id_foreign" foreign key constraint creation - already exists');
} else {
logging.info('Adding "members_stripe_customers_subscriptions_customer_id_foreign" foreign key constraint');
await knex.schema.alterTable('members_stripe_customers_subscriptions', (table) => {
table.foreign('customer_id').references('members_stripe_customers.customer_id').onDelete('CASCADE');
});
}
},

async down({transacting: knex}) {
if (knex.client.config.client !== 'mysql') {
return logging.warn('Skipping member tables index removal - database is not MySQL');
}

const [membersStripeCustomersIndexes] = await knex.raw('SHOW INDEXES FROM members_stripe_customers');
const [membersStripeCustomersSubscriptionsIndexes] = await knex.raw('SHOW INDEXES from members_stripe_customers_subscriptions');

if (!membersStripeCustomersSubscriptionsIndexes.find(index => index.Key_name === 'members_stripe_customers_subscriptions_customer_id_foreign')) {
logging.warn('Skipping "members_stripe_customers_subscriptions_customer_id_foreign" foreign key constraint removal - does not exist');
} else {
logging.info('Dropping "members_stripe_customers_subscriptions_customer_id_foreign" foreign key constraint');
await knex.schema.alterTable('members_stripe_customers_subscriptions', (table) => {
table.dropForeign('customer_id');
// mysql automatically creates an index for the foreign key which will be left behind after dropping foreign key constraint
table.dropIndex('customer_id', 'members_stripe_customers_subscriptions_customer_id_foreign');
});
}

if (!membersStripeCustomersSubscriptionsIndexes.find(index => index.Key_name === 'members_stripe_customers_subscriptions_subscription_id_unique')) {
logging.warn('Skipping "members_stripe_customers_subscriptions_subscription_id_unique" index removal - does not exist');
} else {
logging.info('Dropping "members_stripe_customers_subscriptions_subscription_id_unique" index');
await knex.schema.alterTable('members_stripe_customers_subscriptions', (table) => {
table.dropUnique('subscription_id');
});
}

if (!membersStripeCustomersIndexes.find(index => index.Key_name === 'members_stripe_customers_customer_id_unique')) {
logging.warn('Skipping "members_stripe_customers_customer_id_unique" index removal - does not exist');
} else {
logging.info('Dropping "members_stripe_customers_customer_id_unique" index');
await knex.schema.alterTable('members_stripe_customers', (table) => {
table.dropUnique('customer_id');
});
}

if (!membersStripeCustomersIndexes.find(index => index.Key_name === 'members_stripe_customers_member_id_foreign')) {
logging.warn('Skipping "members_stripe_customers_member_id_foreign" foreign key constraint removal - does not exist');
} else {
logging.info('Dropping "members_stripe_customers_member_id_foreign" foreign key constraint');
await knex.schema.alterTable('members_stripe_customers', (table) => {
table.dropForeign('member_id');
table.dropIndex('member_id', 'members_stripe_customers_member_id_foreign');
});
}

const dbName = knex.client.config.connection.database;
const [dbConstraints] = await knex.raw('SELECT * FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=?', [dbName]);

const memberIdConstraint = dbConstraints.find(constraint => constraint.CONSTRAINT_NAME === 'members_labels_member_id_foreign');
if (memberIdConstraint && memberIdConstraint.DELETE_RULE !== 'CASCADE') {
logging.warn('Skipping removal of ON DELETE CASCADE for "members_labels_member_id_foreign" constraint - not set');
} else if (memberIdConstraint) {
logging.info('Removing ON DELETE CASCADE from "members_labels_member_id_foreign" constraint');
// first drop the key
await knex.schema.alterTable('members_labels', (table) => {
table.dropForeign('member_id');
table.dropIndex('member_id', 'members_labels_member_id_foreign');
});
// then re-add without ON DELETE CASCADE
await knex.schema.alterTable('members_labels', (table) => {
table.foreign('member_id').references('members.id');
});
}

const labelIdConstraint = dbConstraints.find(constraint => constraint.CONSTRAINT_NAME === 'members_labels_label_id_foreign');
if (labelIdConstraint && labelIdConstraint.DELETE_RULE !== 'CASCADE') {
logging.warn('Skipping removal of ON DELETE CASCADE for "members_labels_label_id_foreign" constraint - not set');
} else if (labelIdConstraint) {
logging.info('Removing ON DELETE CASCADE from "members_labels_label_id_foreign" constraint');
// first drop the key
await knex.schema.alterTable('members_labels', (table) => {
table.dropForeign('label_id');
table.dropIndex('label_id', 'members_labels_label_id_foreign');
});
// then re-add without ON DELETE CASCADE
await knex.schema.alterTable('members_labels', (table) => {
table.foreign('label_id').references('labels.id');
});
}
}
};
3 changes: 3 additions & 0 deletions core/server/data/schema/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
// check if table exists?
column.references(columnSpec.references);
}
if (Object.prototype.hasOwnProperty.call(columnSpec, 'cascadeDelete') && columnSpec.cascadeDelete === true) {
column.onDelete('CASCADE');
}
if (Object.prototype.hasOwnProperty.call(columnSpec, 'defaultTo')) {
column.defaultTo(columnSpec.defaultTo);
}
Expand Down
Loading

0 comments on commit f28976a

Please sign in to comment.