Files
dashboard/examples DO NOT USE OR EDIT/EXAMPLE ONLY metaserver.js
2024-12-27 13:59:51 -05:00

259 lines
6.8 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const { default: axios } = require('axios');
const path = require('path');
require('dotenv').config({
path: path.resolve(__dirname, '.env')
});
const app = express();
const port = process.env.PORT || 3002;
const META_API_VERSION = 'v21.0';
const META_API_BASE_URL = `https://graph.facebook.com/${META_API_VERSION}`;
const META_ACCESS_TOKEN = process.env.META_ACCESS_TOKEN;
const AD_ACCOUNT_ID = process.env.META_AD_ACCOUNT_ID;
app.use(cors());
app.use(express.json());
const metaApiRequest = async (endpoint, params = {}) => {
try {
const response = await axios.get(`${META_API_BASE_URL}/${endpoint}`, {
params: {
access_token: META_ACCESS_TOKEN,
time_zone: 'America/New_York',
...params,
},
});
return response.data;
} catch (error) {
console.error('Meta API Error:', {
message: error.message,
response: error.response?.data,
endpoint,
});
throw error;
}
};
const fetchCampaigns = async (since, until) => {
const campaigns = await metaApiRequest(`act_${AD_ACCOUNT_ID}/campaigns`, {
fields: `
id,
name,
status,
objective,
daily_budget,
lifetime_budget,
adsets{daily_budget,lifetime_budget},
insights.time_range({"since":"${since}","until":"${until}"}).level(campaign){
spend,
impressions,
clicks,
ctr,
cpc,
reach,
frequency,
cpm,
actions,
action_values,
cost_per_action_type
}
`,
limit: 100,
});
// Filter campaigns with valid spend
const filtered = campaigns.data.filter(c => c.insights?.data?.[0]?.spend > 0);
return filtered;
};
app.get('/campaigns', async (req, res) => {
try {
const { since, until } = req.query;
if (!since || !until) {
return res.status(400).json({ error: 'Date range is required (since, until)' });
}
const campaigns = await metaApiRequest(`act_${AD_ACCOUNT_ID}/campaigns`, {
fields: [
'id',
'name',
'status',
'objective',
'daily_budget',
'lifetime_budget',
'adsets{daily_budget,lifetime_budget}',
`insights.time_range({'since':'${since}','until':'${until}'}).level(campaign){
spend,
impressions,
clicks,
ctr,
reach,
frequency,
cpm,
cpc,
actions,
action_values,
cost_per_action_type
}`,
].join(','),
limit: 100,
time_zone: 'America/New_York', // Specify Eastern Time
});
const filteredCampaigns = campaigns.data.filter(c => c.insights?.data?.[0]?.spend > 0);
res.json(filteredCampaigns);
} catch (error) {
console.error('Campaign fetch error:', error);
res.status(500).json({
error: 'Failed to fetch campaigns',
details: error.response?.data?.error?.message || error.message,
});
}
});
app.get('/insights', async (req, res) => {
try {
const { timeframe = '30' } = req.query;
const since = new Date();
since.setDate(since.getDate() - parseInt(timeframe));
const insights = await metaApiRequest(`act_${AD_ACCOUNT_ID}/insights`, {
fields: 'spend,impressions,clicks,ctr,reach,frequency,cpm,actions,action_values',
time_increment: 1,
time_range: JSON.stringify({
since: since.toISOString().split('T')[0],
until: new Date().toISOString().split('T')[0]
})
});
res.json(insights.data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch insights' });
}
});
app.get('/campaigns', async (req, res) => {
try {
const { since, until } = req.query;
if (!since || !until) {
return res.status(400).json({ error: 'Date range is required (since, until)' });
}
const campaigns = await fetchCampaigns(since, until);
campaigns.forEach((campaign) => {
const insights = campaign.insights?.data?.[0];
});
res.json(campaigns);
} catch (error) {
console.error('Campaign fetch error:', error.message);
res.status(500).json({
error: 'Failed to fetch campaigns',
details: error.response?.data?.error?.message || error.message,
});
}
});
app.get('/account_insights', async (req, res) => {
try {
const { since, until } = req.query;
if (!since || !until) {
return res.status(400).json({ error: 'Date range is required (since, until)' });
}
const accountInsights = await metaApiRequest(`act_${AD_ACCOUNT_ID}/insights`, {
fields: 'reach',
time_range: JSON.stringify({ since, until }),
time_zone: 'America/New_York', // Specify Eastern Time
});
// accountInsights.data[0] contains the aggregated metrics over the date range
res.json(accountInsights.data[0]);
} catch (error) {
console.error('Account insights fetch error:', error);
res.status(500).json({
error: 'Failed to fetch account insights',
details: error.response?.data?.error?.message || error.message,
});
}
});
app.patch('/campaigns/:campaignId/budget', async (req, res) => {
try {
const { campaignId } = req.params;
const { budget } = req.body;
const response = await axios.post(`${META_API_BASE_URL}/${campaignId}`, {
access_token: META_ACCESS_TOKEN,
daily_budget: budget * 100, // Convert to cents
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: 'Failed to update campaign budget' });
}
});
app.post('/campaigns/:campaignId/:action', async (req, res) => {
try {
const { campaignId, action } = req.params;
const status = action === 'pause' ? 'PAUSED' : 'ACTIVE';
const response = await axios.post(`${META_API_BASE_URL}/${campaignId}`, {
access_token: META_ACCESS_TOKEN,
status,
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: `Failed to ${req.params.action} campaign` });
}
});
// Rate limit handling
const handleRateLimit = (headers) => {
const usageInfo = headers['x-business-use-case-usage'];
if (usageInfo) {
try {
const usage = JSON.parse(usageInfo);
const adsApiAccess = Object.values(usage)[0]?.[0];
if (adsApiAccess?.estimated_time_to_regain_access > 0) {
throw new Error(`Rate limit exceeded. Try again in ${adsApiAccess.estimated_time_to_regain_access} seconds`);
}
} catch (e) {
console.error('Error parsing rate limit headers:', e);
}
}
};
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
// Start server
app.listen(port, () => {
});
// PM2 process name
module.exports = {
apps: [{
name: 'meta-ads-server',
script: 'server.js',
env: {
NODE_ENV: 'production',
PORT: 3004,
},
}],
};