Added email_recipients include option to members API read endpoint (#12471)

refs https://github.com/TryGhost/Ghost-Admin/pull/1796

We want to be able to display an email activity timeline in Ghost-Admin for each member. The quickest way to achieve that right now is to provide access to the `email_recipient` data for the member when fetching, this will allow clients to build up a timeline based on the event timestamps included with each email_recipient/email pair.

- sets up `email_recipients` relationship in `Member` model
- updates members API read endpoint to accept an `email_recipients` include parameter
  - appends `email_recipients.email` to the `withRelated` array when `email_recipients` is included so that we have data available for email subject and html/plaintext for previews
- updates members API output serializer to include the email_recipients object in the output
This commit is contained in:
Kevin Ansfield 2020-12-10 10:04:05 +00:00 committed by GitHub
parent 9586d1ce56
commit c1d66f0b01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 5 deletions

View File

@ -13,6 +13,7 @@ const {i18n} = require('../../lib/common');
const db = require('../../data/db');
const ghostMailer = new GhostMailer();
const allowedIncludes = ['email_recipients'];
module.exports = {
docName: 'members',
@ -51,15 +52,35 @@ module.exports = {
},
read: {
options: [
'include'
],
headers: {},
data: [
'id',
'email'
],
validation: {},
validation: {
options: {
include: {
values: allowedIncludes
}
}
},
permissions: true,
async query(frame) {
frame.options.withRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer'];
const defaultWithRelated = ['labels', 'stripeSubscriptions', 'stripeSubscriptions.customer'];
if (!frame.options.withRelated) {
frame.options.withRelated = defaultWithRelated;
} else {
frame.options.withRelated = frame.options.withRelated.concat(defaultWithRelated);
}
if (frame.options.withRelated.includes('email_recipients')) {
frame.options.withRelated.push('email_recipients.email');
}
let model = await membersService.api.members.get(frame.data, frame.options);
if (!model) {

View File

@ -103,7 +103,8 @@ function serializeMember(member, options) {
comped: comped,
email_count: json.email_count,
email_opened_count: json.email_opened_count,
email_open_rate: json.email_open_rate
email_open_rate: json.email_open_rate,
email_recipients: json.email_recipients
};
}
@ -150,6 +151,7 @@ function createSerializer(debugString, serialize) {
* @prop {number} email_count
* @prop {number} email_opened_count
* @prop {number} email_open_rate
* @prop {null|SerializedEmailRecipient[]} email_recipients
*/
/**
@ -180,6 +182,48 @@ function createSerializer(debugString, serialize) {
* @prop {string} plan.currency_symbol
*/
/**
* @typedef {Object} SerializedEmailRecipient
*
* @prop {string} id
* @prop {string} email_id
* @prop {string} batch_id
* @prop {string} processed_at
* @prop {string} delivered_at
* @prop {string} opened_at
* @prop {string} failed_at
* @prop {string} member_uuid
* @prop {string} member_email
* @prop {string} member_name
* @prop {SerializedEmail[]} email
*/
/**
* @typedef {Object} SerializedEmail
*
* @prop {string} id
* @prop {string} post_id
* @prop {string} uuid
* @prop {string} status
* @prop {string} recipient_filter
* @prop {null|string} error
* @prop {string} error_data
* @prop {number} email_count
* @prop {number} delivered_count
* @prop {number} opened_count
* @prop {number} failed_count
* @prop {string} subject
* @prop {string} from
* @prop {string} reply_to
* @prop {string} html
* @prop {string} plaintext
* @prop {boolean} track_opens
* @prop {string} created_at
* @prop {string} created_by
* @prop {string} updated_at
* @prop {string} updated_by
*/
/**
* @typedef {Object} APIConfig
* @prop {string} docName

View File

@ -4,6 +4,12 @@ const EmailRecipient = ghostBookshelf.Model.extend({
tableName: 'email_recipients',
hasTimestamps: false,
relationships: ['email'],
relationshipBelongsTo: {
email: 'emails'
},
email() {
return this.belongsTo('Email', 'email_id');
},

View File

@ -17,11 +17,12 @@ const Member = ghostBookshelf.Model.extend({
};
},
relationships: ['labels', 'stripeCustomers'],
relationships: ['labels', 'stripeCustomers', 'email_recipients'],
relationshipBelongsTo: {
labels: 'labels',
stripeCustomers: 'members_stripe_customers'
stripeCustomers: 'members_stripe_customers',
email_recipients: 'email_recipients'
},
labels: function labels() {
@ -50,6 +51,10 @@ const Member = ghostBookshelf.Model.extend({
);
},
email_recipients() {
return this.hasMany('EmailRecipient', 'member_id', 'id');
},
serialize(options) {
const defaultSerializedObject = ghostBookshelf.Model.prototype.serialize.call(this, options);