Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added products and Stripe data to exports and imports (#14873)
- The migration path from 4.x on SQLite to 5.0 on MySQL requires an export/import - Exports don't include the Stripe info required to map members to tiers correctly on import. This change fixes that. Co-authored-by: Simon Backx <simon@ghost.org> Co-authored-by: Hannah Wolfe <github.erisds@gmail.com>
- Loading branch information
1 parent
d6d6841
commit eae0a6a
Showing
12 changed files
with
506 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
const _ = require('lodash'); | ||
const BaseImporter = require('./base'); | ||
const models = require('../../../../models'); | ||
|
||
class ProductsImporter extends BaseImporter { | ||
constructor(allDataFromFile) { | ||
super(allDataFromFile, { | ||
modelName: 'Product', | ||
dataKeyToImport: 'products', | ||
requiredFromFile: ['stripe_prices'], | ||
requiredExistingData: ['stripe_prices'] | ||
}); | ||
} | ||
|
||
fetchExisting(modelOptions) { | ||
return models.Product.findAll(_.merge({columns: ['products.id as id']}, modelOptions)) | ||
.then((existingData) => { | ||
this.existingData = existingData.toJSON(); | ||
}); | ||
} | ||
|
||
mapImportedData(originalObject, importedObject) { | ||
return { | ||
id: importedObject.id, | ||
originalId: this.originalIdMap[importedObject.id], | ||
monthly_price_id: originalObject.monthly_price_id, | ||
yearly_price_id: originalObject.yearly_price_id | ||
}; | ||
} | ||
|
||
validateStripePrice() { | ||
// the stripe price either needs to exist in the current db, | ||
// or be imported as part of the same import | ||
let invalidProducts = []; | ||
_.each(['monthly_price_id', 'yearly_price_id'], (field) => { | ||
_.each(this.dataToImport, (objectInFile) => { | ||
const importedObject = _.find( | ||
this.requiredFromFile.stripe_prices, | ||
{id: objectInFile[field]} | ||
); | ||
// CASE: we'll import the stripe price later | ||
if (importedObject) { | ||
return; | ||
} | ||
const existingObject = _.find( | ||
this.requiredExistingData.stripe_prices, | ||
{id: objectInFile[field]} | ||
); | ||
// CASE: stripe price already exists in the DB | ||
if (existingObject) { | ||
return; | ||
} | ||
// CASE: we don't know what stripe price this is for | ||
invalidProducts.push(objectInFile.id); | ||
}); | ||
}); | ||
// ignore prices with invalid products | ||
this.dataToImport = this.dataToImport.filter(item => !invalidProducts.includes(item.id)); | ||
} | ||
|
||
replaceIdentifiers() { | ||
// this has to be in replaceIdentifiers because it's after required* fields are set | ||
this.validateStripePrice(); | ||
return super.replaceIdentifiers(); | ||
} | ||
} | ||
|
||
module.exports = ProductsImporter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
const _ = require('lodash'); | ||
const debug = require('@tryghost/debug')('importer:stripeprices'); | ||
const BaseImporter = require('./base'); | ||
const models = require('../../../../models'); | ||
|
||
class StripePricesImporter extends BaseImporter { | ||
constructor(allDataFromFile) { | ||
super(allDataFromFile, { | ||
modelName: 'StripePrice', | ||
dataKeyToImport: 'stripe_prices', | ||
requiredImportedData: ['stripe_products'], | ||
requiredExistingData: ['stripe_products'] | ||
}); | ||
} | ||
|
||
fetchExisting(modelOptions) { | ||
return models.StripePrice.findAll(_.merge({columns: ['id', 'stripe_product_id']}, modelOptions)) | ||
.then((existingData) => { | ||
this.existingData = existingData.toJSON(); | ||
}); | ||
} | ||
|
||
validateStripeProduct() { | ||
// ensure we have a valid stripe_product_id in the stripe_products table | ||
let invalidPrices = []; | ||
_.each(this.dataToImport, (objectInFile) => { | ||
const importedObject = _.find( | ||
this.requiredImportedData.stripe_products, | ||
{stripe_product_id: objectInFile.stripe_product_id} | ||
); | ||
// CASE: we've imported the stripe_product | ||
if (importedObject) { | ||
return; | ||
} | ||
const existingObject = _.find( | ||
this.requiredExistingData.stripe_products, | ||
{stripe_product_id: objectInFile.stripe_product_id} | ||
); | ||
// CASE: stripe product already exists in the DB | ||
if (existingObject) { | ||
return; | ||
} | ||
// CASE: we don't know what stripe product this is for | ||
debug(`ignoring invalid product ${objectInFile.stripe_product_id}`); | ||
invalidPrices.push(objectInFile.id); | ||
}); | ||
// ignore prices with invalid products | ||
debug(`ignoring ${invalidPrices.length} products`); | ||
this.dataToImport = this.dataToImport.filter(item => !invalidPrices.includes(item.id)); | ||
} | ||
|
||
replaceIdentifiers() { | ||
// this has to be in replaceIdentifiers because it's after required* fields are set | ||
this.validateStripeProduct(); | ||
return super.replaceIdentifiers(); | ||
} | ||
} | ||
|
||
module.exports = StripePricesImporter; |
Oops, something went wrong.