0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Added support for ON DELETE CASCADE to the schema (#12105)

no-issue

We are in the process of creating migrations to add foreign key constraints
and cascading deletes to the members_stripe_* tables to make listing members
and deleting members faster. As well as the migrations we need to update the
database schema so that new installations have the correct indexes and constraints.

Co-authored-by: Kevin Ansfield <kevin@lookingsideways.co.uk>
This commit is contained in:
Fabien 'egg' O'Carroll 2020-08-05 13:20:30 +02:00 committed by GitHub
parent 0eed0d8c88
commit d15446593a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 21 deletions

View file

@ -40,6 +40,9 @@ function addTableColumn(tableName, table, columnName, columnSpec = schema[tableN
// check if table exists? // check if table exists?
column.references(columnSpec.references); column.references(columnSpec.references);
} }
if (Object.prototype.hasOwnProperty.call(columnSpec, 'cascadeDelete') && columnSpec.cascadeDelete === true) {
column.onDelete('CASCADE');
}
if (Object.prototype.hasOwnProperty.call(columnSpec, 'defaultTo')) { if (Object.prototype.hasOwnProperty.call(columnSpec, 'defaultTo')) {
column.defaultTo(columnSpec.defaultTo); column.defaultTo(columnSpec.defaultTo);
} }

View file

@ -387,15 +387,14 @@ module.exports = {
}, },
members_labels: { members_labels: {
id: {type: 'string', maxlength: 24, nullable: false, primary: true}, id: {type: 'string', maxlength: 24, nullable: false, primary: true},
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id'}, member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
label_id: {type: 'string', maxlength: 24, nullable: false, references: 'labels.id'}, label_id: {type: 'string', maxlength: 24, nullable: false, references: 'labels.id', cascadeDelete: true},
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0} sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
}, },
members_stripe_customers: { members_stripe_customers: {
id: {type: 'string', maxlength: 24, nullable: false, primary: true}, id: {type: 'string', maxlength: 24, nullable: false, primary: true},
member_id: {type: 'string', maxlength: 24, nullable: false, unique: false}, member_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'members.id', cascadeDelete: true},
// customer_id is unique: false because mysql with innodb utf8mb4 cannot have unqiue columns larger than 191 chars customer_id: {type: 'string', maxlength: 255, nullable: false, unique: true},
customer_id: {type: 'string', maxlength: 255, nullable: false, unique: false},
name: {type: 'string', maxlength: 191, nullable: true}, name: {type: 'string', maxlength: 191, nullable: true},
email: {type: 'string', maxlength: 191, nullable: true}, email: {type: 'string', maxlength: 191, nullable: true},
created_at: {type: 'dateTime', nullable: false}, created_at: {type: 'dateTime', nullable: false},
@ -405,8 +404,8 @@ module.exports = {
}, },
members_stripe_customers_subscriptions: { members_stripe_customers_subscriptions: {
id: {type: 'string', maxlength: 24, nullable: false, primary: true}, id: {type: 'string', maxlength: 24, nullable: false, primary: true},
customer_id: {type: 'string', maxlength: 255, nullable: false, unique: false}, customer_id: {type: 'string', maxlength: 255, nullable: false, unique: false, references: 'members_stripe_customers.customer_id', cascadeDelete: true},
subscription_id: {type: 'string', maxlength: 255, nullable: false, unique: false}, subscription_id: {type: 'string', maxlength: 255, nullable: false, unique: true},
plan_id: {type: 'string', maxlength: 255, nullable: false, unique: false}, plan_id: {type: 'string', maxlength: 255, nullable: false, unique: false},
status: {type: 'string', maxlength: 50, nullable: false}, status: {type: 'string', maxlength: 50, nullable: false},
cancel_at_period_end: {type: 'bool', nullable: false, defaultTo: false}, cancel_at_period_end: {type: 'bool', nullable: false, defaultTo: false},

View file

@ -15,8 +15,13 @@ describe('MemberStripeCustomer Model', function run() {
// For some reason the initial .add of MemberStripeCustomer is **not** adding a StripeCustomerSubscription :( // For some reason the initial .add of MemberStripeCustomer is **not** adding a StripeCustomerSubscription :(
it.skip('Is correctly mapped to the stripe subscriptions', async function () { it.skip('Is correctly mapped to the stripe subscriptions', async function () {
const context = testUtils.context.admin; const context = testUtils.context.admin;
const member = await Member.add({
email: 'test@test.test'
});
await MemberStripeCustomer.add({ await MemberStripeCustomer.add({
member_id: 'fake_member_id', member_id: member.get('id'),
customer_id: 'fake_customer_id', customer_id: 'fake_customer_id',
subscriptions: [{ subscriptions: [{
subscription_id: 'fake_subscription_id1', subscription_id: 'fake_subscription_id1',
@ -72,13 +77,12 @@ describe('MemberStripeCustomer Model', function run() {
describe('member', function () { describe('member', function () {
it('Is correctly mapped to the member', async function () { it('Is correctly mapped to the member', async function () {
const context = testUtils.context.admin; const context = testUtils.context.admin;
await Member.add({ const member = await Member.add({
id: 'fake_member_id',
email: 'test@test.member' email: 'test@test.member'
}, context); }, context);
await MemberStripeCustomer.add({ await MemberStripeCustomer.add({
member_id: 'fake_member_id', member_id: member.get('id'),
customer_id: 'fake_customer_id' customer_id: 'fake_customer_id'
}, context); }, context);
@ -88,25 +92,29 @@ describe('MemberStripeCustomer Model', function run() {
withRelated: ['member'] withRelated: ['member']
})); }));
const member = customer.related('member'); const memberFromRelation = customer.related('member');
should.exist(member, 'MemberStripeCustomer should have been fetched with member'); should.exist(memberFromRelation, 'MemberStripeCustomer should have been fetched with member');
should.equal(member.get('id'), 'fake_member_id'); should.equal(memberFromRelation.get('id'), member.get('id'));
should.equal(member.get('email'), 'test@test.member'); should.equal(memberFromRelation.get('email'), 'test@test.member');
}); });
}); });
describe('destroy', function () { describe('destroy', function () {
it('Cascades to members_stripe_customers_subscriptions', async function () { it('Cascades to members_stripe_customers_subscriptions', async function () {
const context = testUtils.context.admin; const context = testUtils.context.admin;
const member = await Member.add({
email: 'test@test.member'
}, context);
await MemberStripeCustomer.add({ await MemberStripeCustomer.add({
member_id: 'fake_member_id', member_id: member.get('id'),
customer_id: 'fake_customer_id' customer_id: 'fake_customer_id'
}, context); }, context);
const customer = await MemberStripeCustomer.findOne({ const customer = await MemberStripeCustomer.findOne({
member_id: 'fake_member_id' customer_id: 'fake_customer_id'
}, context); }, context);
should.exist(customer, 'Customer should have been created'); should.exist(customer, 'Customer should have been created');
@ -136,12 +144,12 @@ describe('MemberStripeCustomer Model', function run() {
}, context)); }, context));
const customerAfterDestroy = await MemberStripeCustomer.findOne({ const customerAfterDestroy = await MemberStripeCustomer.findOne({
member_id: 'fake_member_id' customer_id: 'fake_customer_id'
}); });
should.not.exist(customerAfterDestroy, 'MemberStripeCustomer should have been destroyed'); should.not.exist(customerAfterDestroy, 'MemberStripeCustomer should have been destroyed');
const subscriptionAfterDestroy = await StripeCustomerSubscription.findOne({ const subscriptionAfterDestroy = await StripeCustomerSubscription.findOne({
customer_id: customer.get('customer_id') customer_id: 'fake_customer_id'
}); });
should.not.exist(subscriptionAfterDestroy, 'StripeCustomerSubscription should have been destroyed'); should.not.exist(subscriptionAfterDestroy, 'StripeCustomerSubscription should have been destroyed');
}); });

View file

@ -1,4 +1,5 @@
const should = require('should'); const should = require('should');
const {Member} = require('../../../core/server/models/member');
const {MemberStripeCustomer} = require('../../../core/server/models/member-stripe-customer'); const {MemberStripeCustomer} = require('../../../core/server/models/member-stripe-customer');
const {StripeCustomerSubscription} = require('../../../core/server/models/stripe-customer-subscription'); const {StripeCustomerSubscription} = require('../../../core/server/models/stripe-customer-subscription');
@ -12,8 +13,11 @@ describe('StripeCustomerSubscription Model', function run() {
describe('customer', function () { describe('customer', function () {
it('Is correctly mapped to the stripe customer', async function () { it('Is correctly mapped to the stripe customer', async function () {
const context = testUtils.context.admin; const context = testUtils.context.admin;
const member = await Member.add({
email: 'test@test.member'
}, context);
await MemberStripeCustomer.add({ await MemberStripeCustomer.add({
member_id: 'fake_member_id', member_id: member.get('id'),
customer_id: 'fake_customer_id' customer_id: 'fake_customer_id'
}, context); }, context);

View file

@ -21,7 +21,7 @@ const defaultSettings = require('../../../../core/server/data/schema/default-set
*/ */
describe('DB version integrity', function () { describe('DB version integrity', function () {
// Only these variables should need updating // Only these variables should need updating
const currentSchemaHash = '134c9de4e59b31ec6b73f03638a01396'; const currentSchemaHash = '42a966364eb4b5851e807133374821da';
const currentFixturesHash = '3d942c46e8487c4aee1e9ac898ed29ca'; const currentFixturesHash = '3d942c46e8487c4aee1e9ac898ed29ca';
const currentSettingsHash = 'a4ac78d3810175428b4833645231d6d5'; const currentSettingsHash = 'a4ac78d3810175428b4833645231d6d5';