Ghost/ghost/milestone-emails/lib/Milestone.js
Aileen Booker 3b6759ca6d
Added initial basic milestone emails in-memory repository (#16216)
refs
https://www.notion.so/ghost/Marketing-Milestone-email-campaigns-1d2c9dee3cfa4029863edb16092ad5c4

This adds a milestone entity and in-memory repository in a new
`milestone-emails` package. This also adds a first initial definition of
milestones and their types which is held in the default config to avoid
DB changes when, e. g. values change.

This should get everything in place to begin with the service
implementation.
2023-02-07 12:47:35 +02:00

227 lines
4.6 KiB
JavaScript

const ObjectID = require('bson-objectid').default;
const {ValidationError} = require('@tryghost/errors');
module.exports = class Milestone {
/**
* @type {ObjectID}
*/
#id;
get id() {
return this.#id;
}
/**
* @type {'arr'|'members'}
*/
#type;
get type() {
return this.#type;
}
/** @type {number} */
#value;
get value() {
return this.#value;
}
/** @type {string} */
#currency;
get currency() {
return this.#currency;
}
/** @type {Date} */
#createdAt;
get createdAt() {
return this.#createdAt;
}
/** @type {Date|null} */
#emailSentAt;
get emailSentAt() {
return this.#emailSentAt;
}
toJSON() {
return {
id: this.id,
name: this.name,
type: this.type,
value: this.value,
currency: this.currency,
createdAt: this.createdAt,
emailSentAt: this.emailSentAt
};
}
/** @private */
constructor(data) {
this.#id = data.id;
this.#type = data.type;
this.#value = data.value;
this.#currency = data.currency;
this.#createdAt = data.createdAt;
this.#emailSentAt = data.emailSentAt;
}
/**
* @returns {string}
*/
get name() {
if (this.type === 'arr') {
return `arr-${this.value}-${this.currency}`;
}
return `members-${this.value}`;
}
/**
* @param {any} data
* @returns {Promise<Milestone>}
*/
static async create(data) {
// order of validation matters!
const id = validateId(data.id);
const type = validateType(data.type);
const currency = validateCurrency(type, data?.currency);
const value = validateValue(data.value);
const name = validateName(data.name, value, type, currency);
const emailSentAt = validateEmailSentAt(data);
/** @type Date */
let createdAt;
if (data.createdAt instanceof Date) {
createdAt = data.createdAt;
} else if (data.createdAt) {
createdAt = new Date(data.createdAt);
if (isNaN(createdAt.valueOf())) {
throw new ValidationError({
message: 'Invalid Date'
});
}
} else {
createdAt = new Date();
}
return new Milestone({
id,
name,
type,
value,
currency,
createdAt,
emailSentAt
});
}
};
/**
*
* @param {ObjectID|string|null} id
*
* @returns {ObjectID}
*/
function validateId(id) {
if (!id) {
return new ObjectID();
}
if (typeof id === 'string') {
return ObjectID.createFromHexString(id);
}
if (id instanceof ObjectID) {
return id;
}
return new ObjectID();
}
/**
*
* @param {number|null} value
*
* @returns {number}
*/
function validateValue(value) {
if (!value || typeof value !== 'number' || value === 0) {
throw new ValidationError({
message: 'Invalid value'
});
}
return value;
}
/**
*
* @param {'arr'|'members'} type
*
* @returns {string}
*/
function validateType(type) {
if (type === 'arr') {
return 'arr';
}
return 'members';
}
/**
*
* @param {'arr'|'members'} type
* @param {string|null} currency
*
* @returns {string}
*/
function validateCurrency(type, currency) {
if (type === 'members') {
return null;
}
if (!currency || (currency && typeof currency !== 'string' || currency.length > 3)) {
return 'usd';
}
return currency;
}
/**
*
* @param {string} name
* @param {number} value
* @param {'arr'|'members'} type
* @param {string|null} currency
*
* @returns {string}
*/
function validateName(name, value, type, currency) {
if (!name || !name.match(/(arr|members)-\d*/i)) {
return type === 'arr' ? `${type}-${value}-${currency}` : `${type}-${value}`;
}
return name;
}
/**
*
* @param {Object} data
* @param {Date|null} data.emailSentAt
*
* @returns {Date|null}
*/
function validateEmailSentAt(data) {
let emailSentAt;
if (data.emailSentAt instanceof Date) {
emailSentAt = data.emailSentAt;
} else if (data.emailSentAt) {
emailSentAt = new Date(data.emailSentAt);
if (isNaN(emailSentAt.valueOf())) {
throw new ValidationError({
message: 'Invalid Date'
});
}
} else {
emailSentAt = null;
}
return emailSentAt;
}