Updated mapping for stripe_products when product import is skipped (#14965)

refs d63e9256ea

- Following the ref'd commit, when migrating a site the default and free tiers would be skipped because they exist by default in the new site
- As the product is skipped, we don't have the ID available in the imported data to map the stripe_product to
- If the stripe_product isn't mapped, imported members won't be mapped to the correct tier
- This commit adds a lookup for the product by name and slug to restore the correct stripe_product mapping

Co-authored-by: Simon Backx <simon@ghost.org>
This commit is contained in:
Matt Hanley 2022-06-03 17:31:53 +01:00 committed by GitHub
parent 2ecb4acc85
commit 859d49626c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 122 additions and 2 deletions

View File

@ -8,6 +8,7 @@ class StripeProductsImporter extends BaseImporter {
super(allDataFromFile, {
modelName: 'StripeProduct',
dataKeyToImport: 'stripe_products',
requiredFromFile: ['products'],
requiredImportedData: ['products'],
requiredExistingData: ['products']
});
@ -41,11 +42,32 @@ class StripeProductsImporter extends BaseImporter {
objectInFile.product_id = importedObject.id;
return;
}
const existingObject = _.find(this.requiredExistingData.products, {id: objectInFile.product_id});
const existingObjectById = _.find(this.requiredExistingData.products, {id: objectInFile.product_id});
// CASE: the product exists in the db already
if (existingObject) {
if (existingObjectById) {
return;
}
// CASE: we skipped product import because a product with the same name and slug exists in the DB
debug('lookup product by name and slug');
const productFromFile = _.find(
this.requiredFromFile.products,
{id: objectInFile.product_id}
);
if (productFromFile) {
// look for the existing product with the same name and slug
const existingObjectByNameAndSlug = _.find(
this.requiredExistingData.products,
{name: productFromFile.name, slug: productFromFile.slug}
);
if (existingObjectByNameAndSlug) {
debug(`resolved ${objectInFile.product_id} to ${existingObjectByNameAndSlug.name}`);
objectInFile.product_id = existingObjectByNameAndSlug.id;
return;
}
}
// CASE: we don't know what product this is for
debug(`ignoring stripe product ${objectInFile.stripe_product_id}`);
invalidProducts.push(objectInFile.id);

View File

@ -378,3 +378,101 @@ describe('DB API (canary)', function () {
yearlyPrice.get('stripe_product_id').should.equal('prod_d2c1708c21');
});
});
// The following tests will create a new clean database for every test
describe('DB API (cleaned)', function () {
let backupKey;
let schedulerKey;
beforeEach(async function () {
await testUtils.stopGhost();
await localUtils.startGhost();
request = supertest.agent(config.get('url'));
await localUtils.doAuth(request);
backupKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}});
schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}});
});
afterEach(function () {
sinon.restore();
});
it('Can import a JSON database with products for an existing product', async function () {
// Create a product with existing slug
const existingProduct = await models.Product.forge({
slug: 'ghost-inc',
name: 'Ghost Inc.',
description: 'Our daily newsletter',
type: 'paid',
active: 1,
visibility: 'public'
}).save();
const res = await request.post(localUtils.API.getApiQuery('db/'))
.set('Origin', config.get('url'))
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.attach('importfile', path.join(__dirname, '/../../../utils/fixtures/export/products_export.json'))
.expect(200);
// Check if we ignored the import of the product
const productDuplicate = await models.Product.findOne({slug: 'ghost-inc-2'});
should.not.exist(productDuplicate);
// Check if we have a product
const product = await models.Product.findOne({slug: 'ghost-inc'});
should.exist(product);
product.id.should.equal(existingProduct.id);
product.get('slug').should.equal('ghost-inc');
product.get('name').should.equal('Ghost Inc.');
product.get('description').should.equal('Our daily newsletter');
// Check settings
const portalProducts = await models.Settings.findOne({key: 'portal_products'});
should.exist(portalProducts);
JSON.parse(portalProducts.get('value')).should.deepEqual([]);
// Check stripe products
const stripeProduct = await models.StripeProduct.findOne({product_id: product.id});
should.exist(stripeProduct);
stripeProduct.get('stripe_product_id').should.equal('prod_d2c1708c21');
stripeProduct.id.should.not.equal('60be1fc9bd3af33564cfb337');
// Check newsletters
const newsletter = await models.Newsletter.findOne({slug: 'test'});
should.exist(newsletter);
newsletter.get('name').should.equal('Ghost Inc.');
// Make sure sender_email is not set
should(newsletter.get('sender_email')).equal(null);
// Check posts
const post = await models.Post.findOne({slug: 'test-newsletter'}, {withRelated: ['tiers']});
should.exist(post);
post.get('newsletter_id').should.equal(newsletter.id);
post.get('visibility').should.equal('public');
post.get('email_recipient_filter').should.equal('status:-free');
// Check this post is connected to the imported product
post.relations.tiers.models.map(m => m.id).should.match([product.id]);
// Check stripe prices
const monthlyPrice = await models.StripePrice.findOne({stripe_price_id: 'price_a425520db0'});
should.exist(monthlyPrice);
const yearlyPrice = await models.StripePrice.findOne({stripe_price_id: 'price_d04baebb73'});
should.exist(yearlyPrice);
monthlyPrice.get('amount').should.equal(500);
monthlyPrice.get('currency').should.equal('usd');
monthlyPrice.get('interval').should.equal('month');
monthlyPrice.get('stripe_price_id').should.equal('price_a425520db0');
monthlyPrice.get('stripe_product_id').should.equal('prod_d2c1708c21');
yearlyPrice.get('amount').should.equal(4800);
yearlyPrice.get('currency').should.equal('usd');
yearlyPrice.get('interval').should.equal('year');
yearlyPrice.get('stripe_price_id').should.equal('price_d04baebb73');
yearlyPrice.get('stripe_product_id').should.equal('prod_d2c1708c21');
});
});