// Meta (Facebook Ads) service — ESM conversion of meta-server/services/meta.service.js. // No Redis caching (matches the original — Meta calls are cheap-enough; reach/spend // rolls over once per request). Uses axios. import axios from 'axios'; function getConfig() { const version = process.env.META_API_VERSION || 'v21.0'; return { baseUrl: `https://graph.facebook.com/${version}`, accessToken: process.env.META_ACCESS_TOKEN, adAccountId: process.env.META_AD_ACCOUNT_ID, }; } async function metaApiRequest(endpoint, params = {}) { const { baseUrl, accessToken } = getConfig(); try { const response = await axios.get(`${baseUrl}/${endpoint}`, { params: { access_token: accessToken, 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; } } export async function fetchCampaigns(since, until) { const { adAccountId } = getConfig(); const campaigns = await metaApiRequest(`act_${adAccountId}/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, }); return campaigns.data.filter((c) => c.insights?.data?.[0]?.spend > 0); } export async function fetchAccountInsights(since, until) { const { adAccountId } = getConfig(); const accountInsights = await metaApiRequest(`act_${adAccountId}/insights`, { fields: 'reach,spend,impressions,clicks,ctr,cpm,actions,action_values', time_range: JSON.stringify({ since, until }), }); return accountInsights.data[0] || null; } export async function updateCampaignBudget(campaignId, budget) { const { baseUrl, accessToken } = getConfig(); try { const response = await axios.post(`${baseUrl}/${campaignId}`, { access_token: accessToken, daily_budget: budget * 100, // dollars → cents }); return response.data; } catch (error) { console.error('Update campaign budget error:', error); throw error; } } export async function updateCampaignStatus(campaignId, action) { const { baseUrl, accessToken } = getConfig(); try { const status = action === 'pause' ? 'PAUSED' : 'ACTIVE'; const response = await axios.post(`${baseUrl}/${campaignId}`, { access_token: accessToken, status, }); return response.data; } catch (error) { console.error('Update campaign status error:', error); throw error; } }