ghost
Version:
The professional publishing platform
278 lines (245 loc) • 10.1 kB
JavaScript
const MRRService = require('./MrrStatsService');
const MembersService = require('./MembersStatsService');
const SubscriptionStatsService = require('./SubscriptionStatsService');
const ReferrersStatsService = require('./ReferrersStatsService');
const PostsStatsService = require('./PostsStatsService');
const ContentStatsService = require('./ContentStatsService');
class StatsService {
/**
* @param {object} deps
* @param {MRRService} deps.mrr
* @param {MembersService} deps.members
* @param {SubscriptionStatsService} deps.subscriptions
* @param {ReferrersStatsService} deps.referrers
* @param {PostsStatsService} deps.posts
* @param {ContentStatsService} deps.content
**/
constructor(deps) {
this.mrr = deps.mrr;
this.members = deps.members;
this.subscriptions = deps.subscriptions;
this.referrers = deps.referrers;
this.posts = deps.posts;
this.content = deps.content;
}
async getMRRHistory() {
return this.mrr.getHistory();
}
/**
* @param {Object} [options]
* @param {string} [options.dateFrom] - Start date in YYYY-MM-DD format
* @param {string} [options.endDate] - End date in YYYY-MM-DD format
*/
async getMemberCountHistory(options = {}) {
// Map dateFrom to startDate for backwards compatibility
const mappedOptions = {
...options,
startDate: options.dateFrom
};
delete mappedOptions.dateFrom;
return this.members.getCountHistory(mappedOptions);
}
async getSubscriptionCountHistory() {
return this.subscriptions.getSubscriptionHistory();
}
async getReferrersHistory() {
return this.referrers.getReferrersHistory();
}
/**
* @param {string} postId
*/
async getPostReferrers(postId) {
return {
data: await this.referrers.getForPost(postId),
meta: {}
};
}
/**
* @param {string} postId
*/
async getReferrersForPost(postId, options) {
const result = await this.posts.getReferrersForPost(postId, options);
return result;
}
/**
* @param {Object} options
*/
async getTopContent(options = {}) {
return await this.content.getTopContent(options);
}
/**
* Get top posts by attribution metrics (includes all content that drove conversions)
* @param {import('./PostsStatsService').TopPostsOptions} options
* @returns {Promise<{data: import('./PostsStatsService').AttributionResult[]}>}
*/
async getTopPosts(options = {}) {
// Return the original { data: results } structure
const result = await this.posts.getTopPosts(options);
return result;
}
/**
* Get top posts by views
* @param {Object} options
* @param {string} options.date_from - Start date in YYYY-MM-DD format
* @param {string} options.date_to - End date in YYYY-MM-DD format
* @param {string} options.timezone - Timezone to use for date interpretation
* @param {number} [options.limit=5] - Maximum number of posts to return
* @returns {Promise<{data: import('./PostsStatsService').TopPostResult[]}>}
*/
async getTopPostsViews(options) {
const result = await this.posts.getTopPostsViews(options);
return result;
}
/**
* @param {string} postId
*/
async getGrowthStatsForPost(postId) {
return await this.posts.getGrowthStatsForPost(postId);
}
/**
* @param {string[]} postIds
*/
async getPostsMemberCounts(postIds) {
return await this.posts.getPostsMemberCounts(postIds);
}
/**
* Get newsletter stats for sent posts
* @param {Object} options
* @param {string} [options.newsletter_id] - ID of the specific newsletter to get stats for
* @param {string} [options.order='published_at desc'] - Order field and direction
* @param {number} [options.limit=20] - Max number of results to return
* @param {string} [options.date_from] - Start date filter in YYYY-MM-DD format
* @param {string} [options.date_to] - End date filter in YYYY-MM-DD format
* @returns {Promise<{data: import('./PostsStatsService').NewsletterStatResult[]}>}
*/
async getNewsletterStats(options = {}) {
// Extract newsletter_id from options
const {newsletter_id: newsletterId, ...otherOptions} = options;
// If no newsletterId is provided, we can't get specific stats
if (!newsletterId) {
return {data: []};
}
// Return newsletter stats for the specific newsletter
const result = await this.posts.getNewsletterStats(newsletterId, otherOptions);
return result;
}
/**
* Get newsletter subscriber statistics including total count and daily deltas
*
* @param {Object} options
* @param {string} [options.newsletter_id] - ID of the specific newsletter to get stats for
* @param {string} [options.date_from] - Start date filter in YYYY-MM-DD format
* @param {string} [options.date_to] - End date filter in YYYY-MM-DD format
* @returns {Promise<{data: import('./PostsStatsService').NewsletterSubscriberStats[]}>}
*/
async getNewsletterSubscriberStats(options = {}) {
// Extract newsletter_id from options
const {newsletter_id: newsletterId, ...otherOptions} = options;
// If no newsletterId is provided, we can't get specific stats
if (!newsletterId) {
return {data: [{total: 0, deltas: []}]};
}
const result = await this.posts.getNewsletterSubscriberStats(newsletterId, otherOptions);
return result;
}
/**
* Get stats for a specific post by ID
* @param {string} postId - The post ID to get stats for
* @returns {Promise<{data: Object}>}
*/
async getPostStats(postId) {
return await this.posts.getPostStats(postId);
}
/**
* Get visitor counts for multiple posts
* @param {string[]} postUuids - Array of post UUIDs
* @returns {Promise<{data: Object}>} Visitor counts mapped by post UUID
*/
async getPostsVisitorCounts(postUuids) {
const visitorCounts = await this.posts.getPostsVisitorCounts(postUuids);
return {
data: {
visitor_counts: visitorCounts
}
};
}
/**
* Get newsletter basic stats for sent posts (without click data)
* @param {Object} options
* @param {string} [options.newsletter_id] - ID of the specific newsletter to get stats for
* @param {string} [options.order='published_at desc'] - Order field and direction
* @param {number} [options.limit=20] - Max number of results to return
* @param {string} [options.date_from] - Start date filter in YYYY-MM-DD format
* @param {string} [options.date_to] - End date filter in YYYY-MM-DD format
* @returns {Promise<{data: import('./PostsStatsService').NewsletterStatResult[]}>}
*/
async getNewsletterBasicStats(options = {}) {
// Extract newsletter_id from options
const {newsletter_id: newsletterId, ...otherOptions} = options;
// If no newsletterId is provided, we can't get specific stats
if (!newsletterId) {
return {data: []};
}
// Return newsletter basic stats for the specific newsletter
const result = await this.posts.getNewsletterBasicStats(newsletterId, otherOptions);
return result;
}
/**
* Get newsletter click stats for specific posts
* @param {Object} options
* @param {string} [options.newsletter_id] - ID of the specific newsletter to get stats for
* @param {string} [options.post_ids] - Comma-separated string of post IDs to get click data for
* @returns {Promise<{data: Object[]}>}
*/
async getNewsletterClickStats(options = {}) {
// Extract newsletter_id and post_ids from options
const {newsletter_id: newsletterId, post_ids: postIds} = options;
// If no newsletterId is provided, we can't get specific stats
if (!newsletterId) {
return {data: []};
}
// Return newsletter click stats for the specific newsletter and posts
const result = await this.posts.getNewsletterClickStats(newsletterId, postIds);
return result;
}
async getTopSourcesWithRange(startDate, endDate, orderBy, limit) {
return this.referrers.getTopSourcesWithRange(startDate, endDate, orderBy, limit);
}
/**
* @param {object} deps
*
* @returns {StatsService}
**/
static create(deps) {
// Create the Tinybird client if config exists
let tinybirdClient = null;
const config = deps.config || require('../../../shared/config');
const request = deps.request || require('../../lib/request-external');
const settingsCache = deps.settingsCache || require('../../../shared/settings-cache');
if (settingsCache.get('web_analytics_enabled')) {
// TODO: move the tinybird client to the tinybird service
const TinybirdServiceWrapper = require('../tinybird');
TinybirdServiceWrapper.init();
tinybirdClient = require('./utils/tinybird').create({
config,
request,
settingsCache,
tinybirdService: TinybirdServiceWrapper.instance
});
}
// Add the Tinybird client to the dependencies
const depsWithTinybird = {
...deps,
tinybirdClient
};
return new StatsService({
mrr: new MRRService(deps),
members: new MembersService(deps),
subscriptions: new SubscriptionStatsService(deps),
referrers: new ReferrersStatsService(deps),
posts: new PostsStatsService(depsWithTinybird),
content: new ContentStatsService(depsWithTinybird)
});
}
}
module.exports = StatsService;