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:
parent
bb19eddeae
commit
25182b7b82
8 changed files with 123 additions and 1 deletions
|
@ -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',
|
||||
|
|
|
@ -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}
|
||||
});
|
|
@ -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}
|
||||
});
|
|
@ -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();
|
||||
}
|
||||
);
|
|
@ -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();
|
||||
}
|
||||
);
|
|
@ -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},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Reference in a new issue