177 lines
5.1 KiB
JavaScript
177 lines
5.1 KiB
JavaScript
import axios from "axios";
|
|
|
|
class MetaAdsService {
|
|
constructor() {
|
|
this.client = axios.create({
|
|
baseURL: "/api/meta-ads",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
}
|
|
|
|
// metaAdsService.js
|
|
|
|
processMetrics(campaign) {
|
|
const insights = campaign.insights?.data?.[0] || {};
|
|
const spend = parseFloat(insights.spend || 0);
|
|
const impressions = parseInt(insights.impressions || 0);
|
|
const clicks = parseInt(insights.clicks || 0);
|
|
const reach = parseInt(insights.reach || 0);
|
|
const cpc = parseFloat(insights.cpc || 0);
|
|
const ctr = parseFloat(insights.ctr || 0);
|
|
const cpm = parseFloat(insights.cpm || 0);
|
|
const frequency = parseFloat(insights.frequency || 0);
|
|
|
|
// Purchase value and total purchases
|
|
const purchaseValue = (insights.action_values || [])
|
|
.filter(({ action_type }) => action_type === "purchase")
|
|
.reduce((sum, { value }) => sum + parseFloat(value || 0), 0);
|
|
|
|
const totalPurchases = (insights.actions || [])
|
|
.filter(({ action_type }) => action_type === "purchase")
|
|
.reduce((sum, { value }) => sum + parseInt(value || 0), 0);
|
|
|
|
// Aggregate unique actions
|
|
const actionMap = new Map();
|
|
(insights.actions || []).forEach(({ action_type, value }) => {
|
|
const currentValue = actionMap.get(action_type) || 0;
|
|
actionMap.set(action_type, currentValue + parseInt(value || 0));
|
|
});
|
|
|
|
const actions = Array.from(actionMap.entries()).map(
|
|
([action_type, value]) => ({
|
|
action_type,
|
|
value,
|
|
})
|
|
);
|
|
|
|
// Map of cost per action
|
|
const costPerActionMap = new Map();
|
|
(insights.cost_per_action_type || []).forEach(({ action_type, value }) => {
|
|
costPerActionMap.set(action_type, parseFloat(value || 0));
|
|
});
|
|
|
|
// Total post engagements
|
|
const totalPostEngagements = actionMap.get("post_engagement") || 0;
|
|
|
|
return {
|
|
spend,
|
|
impressions,
|
|
clicks,
|
|
reach,
|
|
frequency,
|
|
ctr,
|
|
cpm,
|
|
cpc,
|
|
actions,
|
|
costPerActionMap, // Include cost per action map
|
|
purchaseValue,
|
|
totalPurchases,
|
|
totalPostEngagements, // Include total post engagements
|
|
};
|
|
}
|
|
|
|
getObjectiveAction(campaignObjective) {
|
|
const objectiveMap = {
|
|
OUTCOME_AWARENESS: { action_type: "impressions", label: "Impressions" },
|
|
OUTCOME_ENGAGEMENT: {
|
|
action_type: "post_engagement",
|
|
label: "Post Engagements",
|
|
},
|
|
OUTCOME_TRAFFIC: { action_type: "link_click", label: "Link Clicks" },
|
|
OUTCOME_LEADS: { action_type: "lead", label: "Leads" },
|
|
OUTCOME_SALES: { action_type: "purchase", label: "Purchases" },
|
|
MESSAGES: { action_type: "messages", label: "Messages" },
|
|
// Add other objectives as needed
|
|
};
|
|
|
|
return (
|
|
objectiveMap[campaignObjective] || {
|
|
action_type: "link_click",
|
|
label: "Link Clicks",
|
|
}
|
|
);
|
|
}
|
|
calculateBudget(campaign) {
|
|
if (campaign.daily_budget) {
|
|
return { value: campaign.daily_budget / 100, type: "day" };
|
|
}
|
|
if (campaign.lifetime_budget) {
|
|
return { value: campaign.lifetime_budget / 100, type: "lifetime" };
|
|
}
|
|
|
|
const adsets = campaign.adsets?.data || [];
|
|
const dailyTotal = adsets.reduce(
|
|
(sum, adset) => sum + (adset.daily_budget || 0),
|
|
0
|
|
);
|
|
const lifetimeTotal = adsets.reduce(
|
|
(sum, adset) => sum + (adset.lifetime_budget || 0),
|
|
0
|
|
);
|
|
|
|
if (dailyTotal > 0) return { value: dailyTotal / 100, type: "day" };
|
|
if (lifetimeTotal > 0)
|
|
return { value: lifetimeTotal / 100, type: "lifetime" };
|
|
|
|
return { value: 0, type: "day" };
|
|
}
|
|
|
|
processCampaignData(campaign) {
|
|
const metrics = this.processMetrics(campaign);
|
|
const budget = this.calculateBudget(campaign);
|
|
const { action_type, label } = this.getObjectiveAction(campaign.objective);
|
|
|
|
// Get cost per result from costPerActionMap
|
|
const costPerResult = metrics.costPerActionMap.get(action_type) || 0;
|
|
|
|
return {
|
|
id: campaign.id,
|
|
name: campaign.name,
|
|
status: campaign.status,
|
|
objective: label, // User-friendly objective label
|
|
objectiveActionType: action_type, // Action type for metrics
|
|
budget: budget.value,
|
|
budgetType: budget.type,
|
|
metrics: {
|
|
...metrics,
|
|
costPerResult,
|
|
},
|
|
};
|
|
}
|
|
async getCampaigns({ since, until }) {
|
|
try {
|
|
const response = await this.client.get("/campaigns", {
|
|
params: { since, until },
|
|
});
|
|
|
|
return response.data.map((campaign) =>
|
|
this.processCampaignData(campaign)
|
|
);
|
|
} catch (error) {
|
|
console.error(
|
|
"Error fetching campaigns:",
|
|
error.response?.data || error.message
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
async getAccountInsights({ since, until }) {
|
|
try {
|
|
const response = await this.client.get("/account_insights", {
|
|
params: { since, until },
|
|
});
|
|
return response.data; // This will be an object with reach and other fields
|
|
} catch (error) {
|
|
console.error(
|
|
"Error fetching account insights:",
|
|
error.response?.data || error.message
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const metaAdsService = new MetaAdsService();
|