mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
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:
parent
9586d1ce56
commit
c1d66f0b01
4 changed files with 81 additions and 5 deletions
|
@ -13,6 +13,7 @@ const {i18n} = require('../../lib/common');
|
||||||
const db = require('../../data/db');
|
const db = require('../../data/db');
|
||||||
|
|
||||||
const ghostMailer = new GhostMailer();
|
const ghostMailer = new GhostMailer();
|
||||||
|
const allowedIncludes = ['email_recipients'];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
docName: 'members',
|
docName: 'members',
|
||||||
|
@ -51,15 +52,35 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
read: {
|
read: {
|
||||||
|
options: [
|
||||||
|
'include'
|
||||||
|
],
|
||||||
headers: {},
|
headers: {},
|
||||||
data: [
|
data: [
|
||||||
'id',
|
'id',
|
||||||
'email'
|
'email'
|
||||||
],
|
],
|
||||||
validation: {},
|
validation: {
|
||||||
|
options: {
|
||||||
|
include: {
|
||||||
|
values: allowedIncludes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
permissions: true,
|
permissions: true,
|
||||||
async query(frame) {
|
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);
|
let model = await membersService.api.members.get(frame.data, frame.options);
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
|
|
|
@ -103,7 +103,8 @@ function serializeMember(member, options) {
|
||||||
comped: comped,
|
comped: comped,
|
||||||
email_count: json.email_count,
|
email_count: json.email_count,
|
||||||
email_opened_count: json.email_opened_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_count
|
||||||
* @prop {number} email_opened_count
|
* @prop {number} email_opened_count
|
||||||
* @prop {number} email_open_rate
|
* @prop {number} email_open_rate
|
||||||
|
* @prop {null|SerializedEmailRecipient[]} email_recipients
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,6 +182,48 @@ function createSerializer(debugString, serialize) {
|
||||||
* @prop {string} plan.currency_symbol
|
* @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
|
* @typedef {Object} APIConfig
|
||||||
* @prop {string} docName
|
* @prop {string} docName
|
||||||
|
|
|
@ -4,6 +4,12 @@ const EmailRecipient = ghostBookshelf.Model.extend({
|
||||||
tableName: 'email_recipients',
|
tableName: 'email_recipients',
|
||||||
hasTimestamps: false,
|
hasTimestamps: false,
|
||||||
|
|
||||||
|
relationships: ['email'],
|
||||||
|
|
||||||
|
relationshipBelongsTo: {
|
||||||
|
email: 'emails'
|
||||||
|
},
|
||||||
|
|
||||||
email() {
|
email() {
|
||||||
return this.belongsTo('Email', 'email_id');
|
return this.belongsTo('Email', 'email_id');
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,11 +17,12 @@ const Member = ghostBookshelf.Model.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
relationships: ['labels', 'stripeCustomers'],
|
relationships: ['labels', 'stripeCustomers', 'email_recipients'],
|
||||||
|
|
||||||
relationshipBelongsTo: {
|
relationshipBelongsTo: {
|
||||||
labels: 'labels',
|
labels: 'labels',
|
||||||
stripeCustomers: 'members_stripe_customers'
|
stripeCustomers: 'members_stripe_customers',
|
||||||
|
email_recipients: 'email_recipients'
|
||||||
},
|
},
|
||||||
|
|
||||||
labels: function labels() {
|
labels: function labels() {
|
||||||
|
@ -50,6 +51,10 @@ const Member = ghostBookshelf.Model.extend({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
email_recipients() {
|
||||||
|
return this.hasMany('EmailRecipient', 'member_id', 'id');
|
||||||
|
},
|
||||||
|
|
||||||
serialize(options) {
|
serialize(options) {
|
||||||
const defaultSerializedObject = ghostBookshelf.Model.prototype.serialize.call(this, options);
|
const defaultSerializedObject = ghostBookshelf.Model.prototype.serialize.call(this, options);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue