Add meta component and services
This commit is contained in:
259
examples DO NOT USE OR EDIT/EXAMPLE ONLY metaserver.js
Normal file
259
examples DO NOT USE OR EDIT/EXAMPLE ONLY metaserver.js
Normal file
@@ -0,0 +1,259 @@
|
||||
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,
|
||||
},
|
||||
}],
|
||||
};
|
||||
Reference in New Issue
Block a user