mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-02 08:13:34 +03:00
3b6759ca6d
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.
227 lines
4.6 KiB
JavaScript
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;
|
|
}
|
|
|