0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added products and members_products tables (#12844)

refs https://github.com/TryGhost/Team/issues/586

- Add the products table, so that we can store Products in Ghost 
- Add the members_products table, so that we can associate Members w/ Products
- Use sort_order on the members_products table to follow the same convention in members_labels
- Populate the products table with a single product, using the name from the stripe_product_name setting
- Populate the members_products table with relations based on the status column of the members table

Populating the tables allows us to transition from the current system, which does not care about products, into the
new system, where Products are used to group members. The intention is that all existing paid members have the
same product
This commit is contained in:
Fabien 'egg' O'Carroll 2021-04-08 14:15:30 +01:00 committed by GitHub
parent bb19eddeae
commit 25182b7b82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 123 additions and 1 deletions

View file

@ -20,6 +20,7 @@ const BACKUP_TABLES = [
'labels',
'members',
'members_labels',
'members_products',
'members_stripe_customers',
'members_stripe_customers_subscriptions',
'migrations',
@ -27,6 +28,7 @@ const BACKUP_TABLES = [
'permissions',
'permissions_roles',
'permissions_users',
'products',
'webhooks',
'snippets',
'tokens',

View file

@ -0,0 +1,9 @@
const {addTable} = require('../../utils');
module.exports = addTable('products', {
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
name: {type: 'string', maxlength: 191, nullable: false, unique: true},
slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
created_at: {type: 'dateTime', nullable: false},
updated_at: {type: 'dateTime', nullable: true}
});

View file

@ -0,0 +1,8 @@
const {addTable} = require('../../utils');
module.exports = addTable('members_products', {
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id', cascadeDelete: true},
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
});

View file

@ -0,0 +1,39 @@
const {createTransactionalMigration} = require('../../utils');
const ObjectID = require('bson-objectid');
const {slugify} = require('@tryghost/string');
const logging = require('../../../../../shared/logging');
module.exports = createTransactionalMigration(
async function up(knex) {
const [result] = await knex
.count('id', {as: 'total'})
.from('products');
if (result.total !== 0) {
logging.warn(`Not adding default product, a product already exists`);
return;
}
const productNameSetting = await knex
.select('value')
.from('settings')
.where('key', 'stripe_product_name')
.first();
const nameSettingHasValue = !!(productNameSetting && productNameSetting.value);
const name = nameSettingHasValue ? productNameSetting.value : 'Ghost Subscription';
logging.info(`Adding product "${name}"`);
await knex('products')
.insert({
id: ObjectID.generate(),
name: name,
slug: slugify(name),
created_at: knex.raw(`CURRENT_TIMESTAMP`)
});
},
async function down(knex) {
logging.info('Removing all products');
await knex('products').del();
}
);

View file

@ -0,0 +1,49 @@
const {createTransactionalMigration} = require('../../utils');
const ObjectID = require('bson-objectid');
const {chunk} = require('lodash');
const logging = require('../../../../../shared/logging');
module.exports = createTransactionalMigration(
async function up(knex) {
const membersWithProduct = await knex
.select('id')
.from('members')
.whereIn('status', ['comped', 'paid']);
if (membersWithProduct.length === 0) {
logging.info(`No members found with product`);
return;
}
const product = await knex
.select('id', 'name')
.from('products')
.first();
if (!product) {
logging.warn(`No product found to attach members to`);
return;
}
logging.info(`Attaching product ${product.name} to ${membersWithProduct.length} members`);
const memberProductRelations = membersWithProduct.map((member) => {
return {
id: ObjectID.generate(),
member_id: member.id,
product_id: product.id
};
});
// SQLite max variables is 999, we have 3 per insert (id, member_id, product_id) so most inserts in a query is 999/3 = 333
const chunkSize = 333;
const memberProductRelationsChunks = chunk(memberProductRelations, chunkSize);
for (const relations of memberProductRelationsChunks) {
await knex.insert(relations).into('members_products');
}
},
async function down(knex) {
logging.info('Removing all members_products relations');
await knex('members_products').del();
}
);

View file

@ -359,6 +359,19 @@ module.exports = {
updated_at: {type: 'dateTime', nullable: true},
updated_by: {type: 'string', maxlength: 24, nullable: true}
},
products: {
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
name: {type: 'string', maxlength: 191, nullable: false, unique: true},
slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
created_at: {type: 'dateTime', nullable: false},
updated_at: {type: 'dateTime', nullable: true}
},
members_products: {
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
product_id: {type: 'string', maxlength: 24, nullable: false, references: 'products.id', cascadeDelete: true},
sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}
},
members_payment_events: {
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},

View file

@ -37,6 +37,7 @@ describe('Exporter', function () {
'members_login_events',
'members_paid_subscription_events',
'members_payment_events',
'members_products',
'members_status_events',
'members_stripe_customers',
'members_stripe_customers_subscriptions',
@ -51,6 +52,7 @@ describe('Exporter', function () {
'posts_authors',
'posts_meta',
'posts_tags',
'products',
'roles',
'roles_users',
'sessions',

View file

@ -32,7 +32,7 @@ const defaultSettings = require('../../../../core/server/data/schema/default-set
*/
describe('DB version integrity', function () {
// Only these variables should need updating
const currentSchemaHash = '559cdbb49a7eeb5758caf0c6e3bf790d';
const currentSchemaHash = '9d62f0a673a4f02af8a980495793ac4f';
const currentFixturesHash = '779f29a247161414025637e10e99a278';
const currentSettingsHash = '7ac732b994a5bb1565f88c8a84872964';
const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01';