mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-07 03:22:21 +03:00
58c9c1c649
refs https://github.com/TryGhost/Team/issues/469 In order to reduce noise, we want to only display newsletter subscription events which are not likely to be the result of a member signup. The approach we've taken is to remove any newsletter subscription (not unsubscription) event, if when sorted in chronological order, it is to reside next to a signup event for the same member. An improvement to this approach might be to add some kind of transaction id to events which would allow us to group together events which should be considered to have happened simultaneously.
267 lines
8.1 KiB
JavaScript
267 lines
8.1 KiB
JavaScript
module.exports = class EventRepository {
|
|
constructor({
|
|
MemberSubscribeEvent,
|
|
MemberPaymentEvent,
|
|
MemberStatusEvent,
|
|
MemberLoginEvent,
|
|
MemberPaidSubscriptionEvent,
|
|
logger
|
|
}) {
|
|
this._MemberSubscribeEvent = MemberSubscribeEvent;
|
|
this._MemberPaidSubscriptionEvent = MemberPaidSubscriptionEvent;
|
|
this._MemberPaymentEvent = MemberPaymentEvent;
|
|
this._MemberStatusEvent = MemberStatusEvent;
|
|
this._MemberLoginEvent = MemberLoginEvent;
|
|
this._logging = logger;
|
|
}
|
|
|
|
async registerPayment(data) {
|
|
await this._MemberPaymentEvent.add({
|
|
...data,
|
|
source: 'stripe'
|
|
});
|
|
}
|
|
|
|
async getNewsletterSubscriptionEvents(options = {}) {
|
|
options.withRelated = ['member'];
|
|
const {data: models, meta} = await this._MemberSubscribeEvent.findPage(options);
|
|
|
|
const data = models.map((data) => {
|
|
return {
|
|
type: 'newsletter_event',
|
|
data: data.toJSON(options)
|
|
};
|
|
});
|
|
|
|
return {
|
|
data,
|
|
meta
|
|
};
|
|
}
|
|
|
|
async getSubscriptionEvents(options = {}) {
|
|
options.withRelated = ['member'];
|
|
const {data: models, meta} = await this._MemberPaidSubscriptionEvent.findPage(options);
|
|
|
|
const data = models.map((data) => {
|
|
return {
|
|
type: 'subscription_event',
|
|
data: data.toJSON(options)
|
|
};
|
|
});
|
|
|
|
return {
|
|
data,
|
|
meta
|
|
};
|
|
}
|
|
|
|
async getPaymentEvents(options = {}) {
|
|
options.withRelated = ['member'];
|
|
const {data: models, meta} = await this._MemberPaymentEvent.findPage(options);
|
|
|
|
const data = models.map((data) => {
|
|
return {
|
|
type: 'payment_event',
|
|
data: data.toJSON(options)
|
|
};
|
|
});
|
|
|
|
return {
|
|
data,
|
|
meta
|
|
};
|
|
}
|
|
|
|
async getLoginEvents(options = {}) {
|
|
options.withRelated = ['member'];
|
|
const {data: models, meta} = await this._MemberLoginEvent.findPage(options);
|
|
|
|
const data = models.map((data) => {
|
|
return {
|
|
type: 'login_event',
|
|
data: data.toJSON(options)
|
|
};
|
|
});
|
|
|
|
return {
|
|
data,
|
|
meta
|
|
};
|
|
}
|
|
|
|
async getSignupEvents(options = {}) {
|
|
options.withRelated = ['member'];
|
|
options.filter = 'from_status:null';
|
|
const {data: models, meta} = await this._MemberStatusEvent.findPage(options);
|
|
|
|
const data = models.map((data) => {
|
|
return {
|
|
type: 'signup_event',
|
|
data: data.toJSON(options)
|
|
};
|
|
});
|
|
|
|
return {
|
|
data,
|
|
meta
|
|
};
|
|
}
|
|
|
|
async getEventTimeline(options = {}) {
|
|
if (!options.limit) {
|
|
options.limit = 10;
|
|
}
|
|
|
|
options.order = 'created_at desc';
|
|
|
|
const allEventPages = await Promise.all([
|
|
this.getNewsletterSubscriptionEvents(options),
|
|
this.getSubscriptionEvents(options),
|
|
this.getLoginEvents(options),
|
|
this.getSignupEvents(options)
|
|
]);
|
|
|
|
const allEvents = allEventPages.reduce((allEvents, page) => allEvents.concat(page.data), []);
|
|
|
|
return allEvents.sort((a, b) => {
|
|
return new Date(b.data.created_at) - new Date(a.data.created_at);
|
|
}).reduce((memo, event, i) => {
|
|
if (event.type === 'newsletter_event' && event.data.subscribed) {
|
|
const previousEvent = allEvents[i - 1];
|
|
const nextEvent = allEvents[i + 1];
|
|
const currentMember = event.data.member_id;
|
|
|
|
if (previousEvent && previousEvent.type === 'signup_event') {
|
|
const previousMember = previousEvent.data.member_id;
|
|
|
|
if (currentMember === previousMember) {
|
|
return memo;
|
|
}
|
|
}
|
|
|
|
if (nextEvent && nextEvent.type === 'signup_event') {
|
|
const nextMember = nextEvent.data.member_id;
|
|
|
|
if (currentMember === nextMember) {
|
|
return memo;
|
|
}
|
|
}
|
|
}
|
|
return memo.concat(event);
|
|
}, []).slice(0, options.limit);
|
|
}
|
|
|
|
async getSubscriptions() {
|
|
const results = await this._MemberSubscribeEvent.findAll({
|
|
aggregateSubscriptionDeltas: true
|
|
});
|
|
|
|
const resultsJSON = results.toJSON();
|
|
|
|
const cumulativeResults = resultsJSON.reduce((cumulativeResults, result, index) => {
|
|
if (index === 0) {
|
|
return [{
|
|
date: result.date,
|
|
subscribed: result.subscribed_delta
|
|
}];
|
|
}
|
|
return cumulativeResults.concat([{
|
|
date: result.date,
|
|
subscribed: result.subscribed_delta + cumulativeResults[index - 1].subscribed
|
|
}]);
|
|
}, []);
|
|
|
|
return cumulativeResults;
|
|
}
|
|
|
|
async getMRR() {
|
|
const results = await this._MemberPaidSubscriptionEvent.findAll({
|
|
aggregateMRRDeltas: true
|
|
});
|
|
|
|
const resultsJSON = results.toJSON();
|
|
|
|
const cumulativeResults = resultsJSON.reduce((cumulativeResults, result) => {
|
|
if (!cumulativeResults[result.currency]) {
|
|
return {
|
|
...cumulativeResults,
|
|
[result.currency]: [{
|
|
date: result.date,
|
|
mrr: result.mrr_delta,
|
|
currency: result.currency
|
|
}]
|
|
};
|
|
}
|
|
return {
|
|
...cumulativeResults,
|
|
[result.currency]: cumulativeResults[result.currency].concat([{
|
|
date: result.date,
|
|
mrr: result.mrr_delta + cumulativeResults[result.currency].slice(-1)[0].mrr,
|
|
currency: result.currency
|
|
}])
|
|
};
|
|
}, {});
|
|
|
|
return cumulativeResults;
|
|
}
|
|
|
|
async getVolume() {
|
|
const results = await this._MemberPaymentEvent.findAll({
|
|
aggregatePaymentVolume: true
|
|
});
|
|
|
|
const resultsJSON = results.toJSON();
|
|
|
|
const cumulativeResults = resultsJSON.reduce((cumulativeResults, result) => {
|
|
if (!cumulativeResults[result.currency]) {
|
|
return {
|
|
...cumulativeResults,
|
|
[result.currency]: [{
|
|
date: result.date,
|
|
volume: result.volume_delta,
|
|
currency: result.currency
|
|
}]
|
|
};
|
|
}
|
|
return {
|
|
...cumulativeResults,
|
|
[result.currency]: cumulativeResults[result.currency].concat([{
|
|
date: result.date,
|
|
volume: result.volume_delta + cumulativeResults[result.currency].slice(-1)[0].volume,
|
|
currency: result.currency
|
|
}])
|
|
};
|
|
}, {});
|
|
|
|
return cumulativeResults;
|
|
}
|
|
|
|
async getStatuses() {
|
|
const results = await this._MemberStatusEvent.findAll({
|
|
aggregateStatusCounts: true
|
|
});
|
|
|
|
const resultsJSON = results.toJSON();
|
|
|
|
const cumulativeResults = resultsJSON.reduce((cumulativeResults, result, index) => {
|
|
if (index === 0) {
|
|
return [{
|
|
date: result.date,
|
|
paid: result.paid_delta,
|
|
comped: result.comped_delta,
|
|
free: result.free_delta
|
|
}];
|
|
}
|
|
return cumulativeResults.concat([{
|
|
date: result.date,
|
|
paid: result.paid_delta + cumulativeResults[index - 1].paid,
|
|
comped: result.comped_delta + cumulativeResults[index - 1].comped,
|
|
free: result.free_delta + cumulativeResults[index - 1].free
|
|
}]);
|
|
}, []);
|
|
|
|
return cumulativeResults;
|
|
}
|
|
};
|