Fixed invalid Offers from throwing when reading

closes https://github.com/TryGhost/Product/issues/3675
refs c98bf80248

As part of our architecture guidelines Repository implementations should protect
against invalid or malformed data in persistence. We do not want read operations
of Entities to throw because of such data. For some fields that bad data can be
fixed or handled in the constructor or static create factory method and replaced
with valid data, others will cause the factory to throw.

This means that Repositories should catch these errors and exclude those
entities from their results. We log the errors in Sentry so that we have
visibility on the state of bad data in DBs
This commit is contained in:
Fabien "egg" O'Carroll 2023-09-01 13:24:53 +07:00 committed by Fabien 'egg' O'Carroll
parent 84e6026408
commit 5e3470a3a1

View File

@ -2,6 +2,8 @@ const {flowRight} = require('lodash');
const {mapKeyValues, mapQuery} = require('@tryghost/mongo-utils'); const {mapKeyValues, mapQuery} = require('@tryghost/mongo-utils');
const DomainEvents = require('@tryghost/domain-events'); const DomainEvents = require('@tryghost/domain-events');
const {Offer} = require('@tryghost/members-offers'); const {Offer} = require('@tryghost/members-offers');
const sentry = require('../../../shared/sentry');
const logger = require('@tryghost/logging');
const statusTransformer = mapKeyValues({ const statusTransformer = mapKeyValues({
key: { key: {
@ -99,25 +101,31 @@ class OfferBookshelfRepository {
const count = await this.OfferRedemptionModel.where({offer_id: json.id}).count('id', { const count = await this.OfferRedemptionModel.where({offer_id: json.id}).count('id', {
transacting: options.transacting transacting: options.transacting
}); });
return Offer.create({ try {
id: json.id, return await Offer.create({
name: json.name, id: json.id,
code: json.code, name: json.name,
display_title: json.portal_title, code: json.code,
display_description: json.portal_description, display_title: json.portal_title,
type: json.discount_type === 'amount' ? 'fixed' : json.discount_type, display_description: json.portal_description,
amount: json.discount_amount, type: json.discount_type === 'amount' ? 'fixed' : json.discount_type,
cadence: json.interval, amount: json.discount_amount,
currency: json.currency, cadence: json.interval,
duration: json.duration, currency: json.currency,
duration_in_months: json.duration_in_months, duration: json.duration,
redemptionCount: count, duration_in_months: json.duration_in_months,
status: json.active ? 'active' : 'archived', redemptionCount: count,
tier: { status: json.active ? 'active' : 'archived',
id: json.product.id, tier: {
name: json.product.name id: json.product.id,
} name: json.product.name
}, null); }
}, null);
} catch (err) {
logger.error(err);
sentry.captureException(err);
return null;
}
} }
/** /**
@ -173,7 +181,7 @@ class OfferBookshelfRepository {
const offers = models.map(model => this.mapToOffer(model, mapOptions)); const offers = models.map(model => this.mapToOffer(model, mapOptions));
return Promise.all(offers); return (await Promise.all(offers)).filter(offer => offer !== null);
} }
/** /**