Skip to main content
Glama

Facebook Ads Management Control Panel

by codprocess
ads.js15.7 kB
/** * Ads routes * Handles operations related to Facebook ads */ const express = require('express'); const router = express.Router(); const { protect, checkFacebookToken } = require('../middleware/auth'); const { validate, schemas } = require('../middleware/validator'); const { sendSuccess, sendError, sendPaginated } = require('../utils/responseFormatter'); const { NotFoundError, ValidationError } = require('../utils/errorTypes'); const Ad = require('../models/ad'); const AdSet = require('../models/adSet'); const Campaign = require('../models/campaign'); const facebookApiService = require('../services/facebookApiService'); const analyticsService = require('../services/analyticsService'); const logger = require('../utils/logger'); /** * @route GET /api/ads * @desc Get all ads for the current user * @access Private */ router.get('/', protect, async (req, res, next) => { try { // Get pagination parameters const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 20; const skip = (page - 1) * limit; // Build query const query = { userId: req.user._id }; // Filter by ad set if provided if (req.query.adSetId) { query.adSetId = req.query.adSetId; } // Filter by status if provided if (req.query.status) { query.status = req.query.status; } // Filter by creative type if provided if (req.query.creativeType) { query['creative.type'] = req.query.creativeType; } // Get ads from database const ads = await Ad.find(query) .sort({ createdAt: -1 }) .skip(skip) .limit(limit); // Get total count const total = await Ad.countDocuments(query); return sendPaginated(res, ads, page, limit, total, 'Ads retrieved successfully'); } catch (error) { logger.error(`Error retrieving ads: ${error.message}`); return next(error); } }); /** * @route GET /api/ads/sync * @desc Sync ads from Facebook * @access Private */ router.get('/sync', protect, checkFacebookToken, async (req, res, next) => { try { // Get ad set ID from query params const { adSetId } = req.query; if (!adSetId) { return next(new ValidationError('Ad set ID is required')); } // Check if ad set exists const adSet = await AdSet.findOne({ adSetId, userId: req.user._id }); if (!adSet) { return next(new NotFoundError('Ad set not found')); } // Create Facebook API service const fbApi = await facebookApiService.createForUser(req.user); // Get ads from Facebook const fbAds = await fbApi.getAds(adSetId); // Process and save ads const savedAds = []; for (const fbAd of fbAds) { // Find existing ad or create new one let ad = await Ad.findOne({ adId: fbAd.id, userId: req.user._id }); if (!ad) { ad = new Ad({ adId: fbAd.id, adSetId, userId: req.user._id }); } // Update ad details ad.name = fbAd.name; ad.status = fbAd.status; ad.creativeId = fbAd.creative?.id; // Process creative details if (fbAd.creative) { // Determine creative type let creativeType = 'IMAGE'; if (fbAd.creative.video_id) { creativeType = 'VIDEO'; } else if (fbAd.creative.product_set_id) { creativeType = 'CAROUSEL'; } ad.creative = { type: creativeType, title: fbAd.creative.title, body: fbAd.creative.body, callToAction: fbAd.creative.call_to_action_type, url: fbAd.creative.url_tags, imageUrl: fbAd.creative.image_url, videoUrl: fbAd.creative.video_id ? `https://www.facebook.com/ads/archive/render_ad/?id=${fbAd.creative.video_id}` : null }; // Process carousel items if available if (fbAd.creative.product_set_id) { ad.creative.carouselItems = fbAd.creative.child_attachments || []; } } ad.previewUrl = fbAd.preview_url; ad.lastSyncedAt = new Date(); // Save ad await ad.save(); savedAds.push(ad); } return sendSuccess(res, savedAds, 'Ads synced successfully'); } catch (error) { logger.error(`Error syncing ads: ${error.message}`); return next(error); } }); /** * @route POST /api/ads * @desc Create a new ad * @access Private */ router.post('/', protect, checkFacebookToken, validate(schemas.ad.create), async (req, res, next) => { try { // Check if ad set exists const adSet = await AdSet.findOne({ adSetId: req.body.adSetId, userId: req.user._id }); if (!adSet) { return next(new NotFoundError('Ad set not found')); } // Get campaign for ad account ID const campaign = await Campaign.findOne({ campaignId: adSet.campaignId, userId: req.user._id }); if (!campaign) { return next(new NotFoundError('Campaign not found')); } // Create Facebook API service const fbApi = await facebookApiService.createForUser(req.user); // Prepare ad data for Facebook const adData = { name: req.body.name, adset_id: req.body.adSetId, status: req.body.status || 'PAUSED' }; // Handle creative if (req.body.creativeId) { adData.creative_id = req.body.creativeId; } else if (req.body.creative) { // Create creative based on type const creative = req.body.creative; // Prepare creative data const creativeData = { name: `Creative for ${req.body.name}`, title: creative.title, body: creative.body, call_to_action_type: creative.callToAction, url_tags: creative.url, object_story_spec: {} }; // Add type-specific data switch (creative.type) { case 'IMAGE': creativeData.image_url = creative.imageUrl; break; case 'VIDEO': creativeData.video_id = creative.videoUrl; break; case 'CAROUSEL': creativeData.product_set_id = creative.productSetId; creativeData.child_attachments = creative.carouselItems; break; } adData.creative = creativeData; } // Create ad on Facebook const fbResponse = await fbApi.createAd(campaign.adAccountId, adData); // Create ad in database const ad = new Ad({ adId: fbResponse.id, name: req.body.name, adSetId: req.body.adSetId, userId: req.user._id, status: req.body.status || 'PAUSED', creativeId: req.body.creativeId || fbResponse.creative_id, creative: req.body.creative, lastSyncedAt: new Date() }); // Save ad await ad.save(); return sendSuccess(res, ad, 'Ad created successfully', 201); } catch (error) { logger.error(`Error creating ad: ${error.message}`); return next(error); } }); /** * @route GET /api/ads/:id * @desc Get ad by ID * @access Private */ router.get('/:id', protect, validate(schemas.idParam, 'params'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } return sendSuccess(res, ad, 'Ad retrieved successfully'); } catch (error) { logger.error(`Error retrieving ad: ${error.message}`); return next(error); } }); /** * @route PUT /api/ads/:id * @desc Update ad * @access Private */ router.put('/:id', protect, checkFacebookToken, validate(schemas.idParam, 'params'), validate(schemas.ad.update), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Create Facebook API service const fbApi = await facebookApiService.createForUser(req.user); // Prepare ad data for Facebook const adData = {}; if (req.body.name) adData.name = req.body.name; if (req.body.status) adData.status = req.body.status; if (req.body.creativeId) adData.creative_id = req.body.creativeId; // Update ad on Facebook await fbApi.updateAd(req.params.id, adData); // Update ad in database if (req.body.name) ad.name = req.body.name; if (req.body.status) ad.status = req.body.status; if (req.body.creativeId) ad.creativeId = req.body.creativeId; if (req.body.creative) ad.creative = req.body.creative; ad.lastSyncedAt = new Date(); // Save ad await ad.save(); return sendSuccess(res, ad, 'Ad updated successfully'); } catch (error) { logger.error(`Error updating ad: ${error.message}`); return next(error); } }); /** * @route DELETE /api/ads/:id * @desc Delete ad * @access Private */ router.delete('/:id', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Create Facebook API service const fbApi = await facebookApiService.createForUser(req.user); // Delete ad on Facebook (actually sets status to DELETED) await fbApi.updateAd(req.params.id, { status: 'DELETED' }); // Update ad status in database ad.status = 'DELETED'; await ad.save(); return sendSuccess(res, { id: req.params.id }, 'Ad deleted successfully'); } catch (error) { logger.error(`Error deleting ad: ${error.message}`); return next(error); } }); /** * @route GET /api/ads/:id/insights * @desc Get insights for an ad * @access Private */ router.get('/:id/insights', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Create Facebook API service const fbApi = await facebookApiService.createForUser(req.user); // Get time range from query params const timeRange = req.query.timeRange || 'last_30_days'; // Get insights from Facebook const insights = await fbApi.getInsights(req.params.id, timeRange, [], { level: 'ad' }); return sendSuccess(res, insights, 'Ad insights retrieved successfully'); } catch (error) { logger.error(`Error retrieving ad insights: ${error.message}`); return next(error); } }); /** * @route GET /api/ads/:id/analytics * @desc Get analytics for an ad * @access Private */ router.get('/:id/analytics', protect, validate(schemas.idParam, 'params'), validate(schemas.analytics.query, 'query'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Create analytics service const analytics = analyticsService.createForUser(req.user); // Get start and end dates from query params const startDate = new Date(req.query.startDate); const endDate = new Date(req.query.endDate); // Get analytics data const analyticsData = await analytics.getAdAnalytics(req.params.id, startDate, endDate); return sendSuccess(res, analyticsData, 'Ad analytics retrieved successfully'); } catch (error) { logger.error(`Error retrieving ad analytics: ${error.message}`); return next(error); } }); /** * @route POST /api/ads/:id/fetch-analytics * @desc Fetch and store analytics for an ad * @access Private */ router.post('/:id/fetch-analytics', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Create analytics service const analytics = analyticsService.createForUser(req.user); // Get time range from query params const timeRange = req.query.timeRange || 'last_30_days'; // Fetch and store analytics data const analyticsData = await analytics.fetchAdAnalytics(req.params.id, timeRange); return sendSuccess(res, analyticsData, 'Ad analytics fetched successfully'); } catch (error) { logger.error(`Error fetching ad analytics: ${error.message}`); return next(error); } }); /** * @route GET /api/ads/:id/creative-recommendations * @desc Get creative recommendations for an ad * @access Private */ router.get('/:id/creative-recommendations', protect, validate(schemas.idParam, 'params'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Get ad set to get ad account ID const adSet = await AdSet.findOne({ adSetId: ad.adSetId, userId: req.user._id }); if (!adSet) { return next(new NotFoundError('Ad set not found')); } // Get campaign to get ad account ID const campaign = await Campaign.findOne({ campaignId: adSet.campaignId, userId: req.user._id }); if (!campaign) { return next(new NotFoundError('Campaign not found')); } // Create recommendation service const recommendationService = require('../services/recommendationService').createForUser(req.user); // Get creative recommendations const recommendations = await recommendationService.getCreativeRecommendations(campaign.adAccountId); // Filter recommendations for this ad const adRecommendations = recommendations.find(rec => rec.adId === ad.adId); if (!adRecommendations) { return sendSuccess(res, { adId: ad.adId, adName: ad.name, recommendations: [] }, 'No creative recommendations available for this ad'); } return sendSuccess(res, adRecommendations, 'Ad creative recommendations retrieved successfully'); } catch (error) { logger.error(`Error retrieving ad creative recommendations: ${error.message}`); return next(error); } }); /** * @route GET /api/ads/:id/preview * @desc Get preview URL for an ad * @access Private */ router.get('/:id/preview', protect, validate(schemas.idParam, 'params'), async (req, res, next) => { try { // Get ad from database const ad = await Ad.findOne({ adId: req.params.id, userId: req.user._id }); if (!ad) { return next(new NotFoundError('Ad not found')); } // Check if preview URL exists if (!ad.previewUrl) { return next(new NotFoundError('Preview URL not available for this ad')); } return sendSuccess(res, { previewUrl: ad.previewUrl }, 'Ad preview URL retrieved successfully'); } catch (error) { logger.error(`Error retrieving ad preview URL: ${error.message}`); return next(error); } }); module.exports = router;

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