Skip to main content
Glama

Facebook Ads Management Control Panel

by codprocess
analyticsService.js17.4 kB
/** * Analytics Service * Handles analytics data processing and insights generation */ const Analytics = require('../models/analytics'); const Campaign = require('../models/campaign'); const AdSet = require('../models/adSet'); const Ad = require('../models/ad'); const facebookApiService = require('./facebookApiService'); const logger = require('../utils/logger'); /** * Analytics Service class */ class AnalyticsService { /** * Create a new Analytics Service instance * @param {Object} user - User object */ constructor(user) { this.user = user; this.userId = user._id; } /** * Initialize Facebook API service * @returns {Promise<void>} * @private */ async _initFacebookApi() { if (!this.fbApi) { this.fbApi = await facebookApiService.createForUser(this.user); } } /** * Fetch and store analytics data for a campaign * @param {string} campaignId - Campaign ID * @param {string} timeRange - Time range (e.g., 'today', 'yesterday', 'last_7_days') * @returns {Promise<Object>} - Analytics data */ async fetchCampaignAnalytics(campaignId, timeRange = 'last_7_days') { try { await this._initFacebookApi(); // Fetch insights from Facebook const insights = await this.fbApi.getInsights(campaignId, timeRange, [], { level: 'campaign', time_increment: 1 // Daily breakdown }); // Process and store insights const analyticsData = []; for (const insight of insights) { const date = new Date(insight.date_start); // Format metrics const metrics = { impressions: parseInt(insight.impressions || 0), reach: parseInt(insight.reach || 0), clicks: parseInt(insight.clicks || 0), ctr: parseFloat(insight.ctr || 0), cpc: parseFloat(insight.cpc || 0), cpm: parseFloat(insight.cpm || 0), spend: parseFloat(insight.spend || 0), frequency: parseFloat(insight.frequency || 0), conversions: parseInt(insight.conversions || 0), costPerConversion: parseFloat(insight.cost_per_conversion || 0), conversionRate: parseFloat((insight.conversions / insight.clicks) || 0), roas: parseFloat(insight.roas || 0) }; // Store analytics data const analytics = await Analytics.findOrCreate( campaignId, 'campaign', this.userId, date, metrics ); analyticsData.push(analytics); } // Update campaign metrics with latest data if (analyticsData.length > 0) { const latestData = analyticsData[analyticsData.length - 1]; const campaign = await Campaign.findOne({ campaignId }); if (campaign) { campaign.metrics = { impressions: latestData.impressions, clicks: latestData.clicks, spend: latestData.spend, conversions: latestData.conversions, ctr: latestData.ctr, cpc: latestData.cpc, cpm: latestData.cpm, reach: latestData.reach, frequency: latestData.frequency, costPerConversion: latestData.costPerConversion, conversionRate: latestData.conversionRate, roas: latestData.roas }; await campaign.save(); } } return analyticsData; } catch (error) { logger.error(`Error fetching campaign analytics: ${error.message}`); throw error; } } /** * Fetch and store analytics data for an ad set * @param {string} adSetId - Ad set ID * @param {string} timeRange - Time range (e.g., 'today', 'yesterday', 'last_7_days') * @returns {Promise<Object>} - Analytics data */ async fetchAdSetAnalytics(adSetId, timeRange = 'last_7_days') { try { await this._initFacebookApi(); // Fetch insights from Facebook const insights = await this.fbApi.getInsights(adSetId, timeRange, [], { level: 'adset', time_increment: 1 // Daily breakdown }); // Process and store insights const analyticsData = []; for (const insight of insights) { const date = new Date(insight.date_start); // Format metrics const metrics = { impressions: parseInt(insight.impressions || 0), reach: parseInt(insight.reach || 0), clicks: parseInt(insight.clicks || 0), ctr: parseFloat(insight.ctr || 0), cpc: parseFloat(insight.cpc || 0), cpm: parseFloat(insight.cpm || 0), spend: parseFloat(insight.spend || 0), frequency: parseFloat(insight.frequency || 0), conversions: parseInt(insight.conversions || 0), costPerConversion: parseFloat(insight.cost_per_conversion || 0), conversionRate: parseFloat((insight.conversions / insight.clicks) || 0), roas: parseFloat(insight.roas || 0) }; // Store analytics data const analytics = await Analytics.findOrCreate( adSetId, 'adset', this.userId, date, metrics ); analyticsData.push(analytics); } // Update ad set metrics with latest data if (analyticsData.length > 0) { const latestData = analyticsData[analyticsData.length - 1]; const adSet = await AdSet.findOne({ adSetId }); if (adSet) { adSet.metrics = { impressions: latestData.impressions, clicks: latestData.clicks, spend: latestData.spend, conversions: latestData.conversions, ctr: latestData.ctr, cpc: latestData.cpc, cpm: latestData.cpm, reach: latestData.reach, frequency: latestData.frequency, costPerConversion: latestData.costPerConversion, conversionRate: latestData.conversionRate, roas: latestData.roas }; await adSet.save(); } } return analyticsData; } catch (error) { logger.error(`Error fetching ad set analytics: ${error.message}`); throw error; } } /** * Fetch and store analytics data for an ad * @param {string} adId - Ad ID * @param {string} timeRange - Time range (e.g., 'today', 'yesterday', 'last_7_days') * @returns {Promise<Object>} - Analytics data */ async fetchAdAnalytics(adId, timeRange = 'last_7_days') { try { await this._initFacebookApi(); // Fetch insights from Facebook const insights = await this.fbApi.getInsights(adId, timeRange, [], { level: 'ad', time_increment: 1 // Daily breakdown }); // Process and store insights const analyticsData = []; for (const insight of insights) { const date = new Date(insight.date_start); // Format metrics const metrics = { impressions: parseInt(insight.impressions || 0), reach: parseInt(insight.reach || 0), clicks: parseInt(insight.clicks || 0), ctr: parseFloat(insight.ctr || 0), cpc: parseFloat(insight.cpc || 0), cpm: parseFloat(insight.cpm || 0), spend: parseFloat(insight.spend || 0), frequency: parseFloat(insight.frequency || 0), conversions: parseInt(insight.conversions || 0), costPerConversion: parseFloat(insight.cost_per_conversion || 0), conversionRate: parseFloat((insight.conversions / insight.clicks) || 0), roas: parseFloat(insight.roas || 0), engagement: { postEngagement: parseInt(insight.post_engagement || 0), pageLikes: parseInt(insight.page_likes || 0), postComments: parseInt(insight.post_comments || 0), postShares: parseInt(insight.post_shares || 0), linkClicks: parseInt(insight.link_clicks || 0), videoViews: parseInt(insight.video_views || 0), videoWatchTime: parseInt(insight.video_play_actions || 0) } }; // Store analytics data const analytics = await Analytics.findOrCreate( adId, 'ad', this.userId, date, metrics ); analyticsData.push(analytics); } // Update ad metrics with latest data if (analyticsData.length > 0) { const latestData = analyticsData[analyticsData.length - 1]; const ad = await Ad.findOne({ adId }); if (ad) { ad.metrics = { impressions: latestData.impressions, clicks: latestData.clicks, spend: latestData.spend, conversions: latestData.conversions, ctr: latestData.ctr, cpc: latestData.cpc, cpm: latestData.cpm, reach: latestData.reach, frequency: latestData.frequency, costPerConversion: latestData.costPerConversion, conversionRate: latestData.conversionRate, roas: latestData.roas, engagement: latestData.engagement.postEngagement, videoViews: latestData.engagement.videoViews, videoWatchTime: latestData.engagement.videoWatchTime }; await ad.save(); } } return analyticsData; } catch (error) { logger.error(`Error fetching ad analytics: ${error.message}`); throw error; } } /** * Get analytics data for a campaign * @param {string} campaignId - Campaign ID * @param {Date} startDate - Start date * @param {Date} endDate - End date * @returns {Promise<Array>} - Analytics data */ async getCampaignAnalytics(campaignId, startDate, endDate) { try { return await Analytics.findByEntityId(campaignId, startDate, endDate); } catch (error) { logger.error(`Error getting campaign analytics: ${error.message}`); throw error; } } /** * Get analytics data for an ad set * @param {string} adSetId - Ad set ID * @param {Date} startDate - Start date * @param {Date} endDate - End date * @returns {Promise<Array>} - Analytics data */ async getAdSetAnalytics(adSetId, startDate, endDate) { try { return await Analytics.findByEntityId(adSetId, startDate, endDate); } catch (error) { logger.error(`Error getting ad set analytics: ${error.message}`); throw error; } } /** * Get analytics data for an ad * @param {string} adId - Ad ID * @param {Date} startDate - Start date * @param {Date} endDate - End date * @returns {Promise<Array>} - Analytics data */ async getAdAnalytics(adId, startDate, endDate) { try { return await Analytics.findByEntityId(adId, startDate, endDate); } catch (error) { logger.error(`Error getting ad analytics: ${error.message}`); throw error; } } /** * Get account overview analytics * @param {Date} startDate - Start date * @param {Date} endDate - End date * @returns {Promise<Object>} - Account overview */ async getAccountOverview(startDate, endDate) { try { // Get all analytics data for the user const campaignAnalytics = await Analytics.find({ userId: this.userId, entityType: 'campaign', date: { $gte: startDate, $lte: endDate } }); // Calculate totals const overview = { impressions: 0, reach: 0, clicks: 0, spend: 0, conversions: 0, ctr: 0, cpc: 0, cpm: 0, costPerConversion: 0, conversionRate: 0, roas: 0, campaigns: { total: 0, active: 0 }, adSets: { total: 0, active: 0 }, ads: { total: 0, active: 0 }, performance: { daily: [] } }; // Process campaign analytics const dailyData = {}; campaignAnalytics.forEach(analytics => { // Add to totals overview.impressions += analytics.impressions; overview.reach += analytics.reach; overview.clicks += analytics.clicks; overview.spend += analytics.spend; overview.conversions += analytics.conversions; // Track daily data const dateStr = analytics.date.toISOString().split('T')[0]; if (!dailyData[dateStr]) { dailyData[dateStr] = { date: dateStr, impressions: 0, clicks: 0, spend: 0, conversions: 0 }; } dailyData[dateStr].impressions += analytics.impressions; dailyData[dateStr].clicks += analytics.clicks; dailyData[dateStr].spend += analytics.spend; dailyData[dateStr].conversions += analytics.conversions; }); // Calculate averages if (campaignAnalytics.length > 0) { overview.ctr = overview.clicks / overview.impressions * 100; overview.cpc = overview.spend / overview.clicks; overview.cpm = overview.spend / overview.impressions * 1000; overview.costPerConversion = overview.spend / overview.conversions; overview.conversionRate = overview.conversions / overview.clicks * 100; overview.roas = overview.conversions * 10 / overview.spend; // Assuming $10 per conversion } // Get campaign, ad set, and ad counts overview.campaigns.total = await Campaign.countDocuments({ userId: this.userId }); overview.campaigns.active = await Campaign.countDocuments({ userId: this.userId, status: 'ACTIVE' }); overview.adSets.total = await AdSet.countDocuments({ userId: this.userId }); overview.adSets.active = await AdSet.countDocuments({ userId: this.userId, status: 'ACTIVE' }); overview.ads.total = await Ad.countDocuments({ userId: this.userId }); overview.ads.active = await Ad.countDocuments({ userId: this.userId, status: 'ACTIVE' }); // Format daily data overview.performance.daily = Object.values(dailyData).sort((a, b) => a.date.localeCompare(b.date)); return overview; } catch (error) { logger.error(`Error getting account overview: ${error.message}`); throw error; } } /** * Get performance comparison * @param {string} entityId - Entity ID * @param {string} entityType - Entity type (campaign, adset, ad) * @param {Date} currentStartDate - Current period start date * @param {Date} currentEndDate - Current period end date * @param {Date} previousStartDate - Previous period start date * @param {Date} previousEndDate - Previous period end date * @returns {Promise<Object>} - Performance comparison */ async getPerformanceComparison(entityId, entityType, currentStartDate, currentEndDate, previousStartDate, previousEndDate) { try { // Get current period data const currentPeriod = await Analytics.calculateAggregateMetrics(entityId, currentStartDate, currentEndDate); // Get previous period data const previousPeriod = await Analytics.calculateAggregateMetrics(entityId, previousStartDate, previousEndDate); // Calculate changes const comparison = { current: currentPeriod || { impressions: 0, reach: 0, clicks: 0, spend: 0, conversions: 0, ctr: 0, cpc: 0, cpm: 0, frequency: 0, conversionRate: 0, costPerConversion: 0, roas: 0 }, previous: previousPeriod || { impressions: 0, reach: 0, clicks: 0, spend: 0, conversions: 0, ctr: 0, cpc: 0, cpm: 0, frequency: 0, conversionRate: 0, costPerConversion: 0, roas: 0 }, changes: {} }; // Calculate percentage changes if (previousPeriod) { for (const key in currentPeriod) { if (key === '_id') continue; const current = currentPeriod[key] || 0; const previous = previousPeriod[key] || 0; if (previous === 0) { comparison.changes[key] = current > 0 ? 100 : 0; } else { comparison.changes[key] = ((current - previous) / previous) * 100; } } } else { // No previous data, set all changes to 100% for (const key in currentPeriod) { if (key === '_id') continue; comparison.changes[key] = currentPeriod[key] > 0 ? 100 : 0; } } return comparison; } catch (error) { logger.error(`Error getting performance comparison: ${error.message}`); throw error; } } } /** * Create an Analytics Service instance for a user * @param {Object} user - User object * @returns {AnalyticsService} - Analytics Service instance */ const createForUser = (user) => { return new AnalyticsService(user); }; module.exports = { AnalyticsService, createForUser };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/codprocess/facebook-ads-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server