adSets.js•15.3 kB
/**
* Ad Sets routes
* Handles operations related to Facebook ad sets
*/
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 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/ad-sets
* @desc Get all ad sets 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 campaign if provided
if (req.query.campaignId) {
query.campaignId = req.query.campaignId;
}
// Filter by status if provided
if (req.query.status) {
query.status = req.query.status;
}
// Get ad sets from database
const adSets = await AdSet.find(query)
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit);
// Get total count
const total = await AdSet.countDocuments(query);
return sendPaginated(res, adSets, page, limit, total, 'Ad sets retrieved successfully');
} catch (error) {
logger.error(`Error retrieving ad sets: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/ad-sets/sync
* @desc Sync ad sets from Facebook
* @access Private
*/
router.get('/sync', protect, checkFacebookToken, async (req, res, next) => {
try {
// Get campaign ID from query params
const { campaignId } = req.query;
if (!campaignId) {
return next(new ValidationError('Campaign ID is required'));
}
// Check if campaign exists
const campaign = await Campaign.findOne({
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);
// Get ad sets from Facebook
const fbAdSets = await fbApi.getAdSets(campaignId);
// Process and save ad sets
const savedAdSets = [];
for (const fbAdSet of fbAdSets) {
// Find existing ad set or create new one
let adSet = await AdSet.findOne({
adSetId: fbAdSet.id,
userId: req.user._id
});
if (!adSet) {
adSet = new AdSet({
adSetId: fbAdSet.id,
campaignId,
userId: req.user._id
});
}
// Update ad set details
adSet.name = fbAdSet.name;
adSet.status = fbAdSet.status;
adSet.targeting = fbAdSet.targeting || {};
adSet.optimization = fbAdSet.optimization_goal;
adSet.bidStrategy = fbAdSet.bid_strategy;
adSet.bidAmount = fbAdSet.bid_amount;
adSet.budget = fbAdSet.daily_budget || fbAdSet.lifetime_budget;
adSet.budgetType = fbAdSet.daily_budget ? 'DAILY' : 'LIFETIME';
adSet.startTime = fbAdSet.start_time ? new Date(fbAdSet.start_time) : null;
adSet.endTime = fbAdSet.end_time ? new Date(fbAdSet.end_time) : null;
adSet.lastSyncedAt = new Date();
// Save ad set
await adSet.save();
savedAdSets.push(adSet);
}
return sendSuccess(res, savedAdSets, 'Ad sets synced successfully');
} catch (error) {
logger.error(`Error syncing ad sets: ${error.message}`);
return next(error);
}
});
/**
* @route POST /api/ad-sets
* @desc Create a new ad set
* @access Private
*/
router.post('/', protect, checkFacebookToken, validate(schemas.adSet.create), async (req, res, next) => {
try {
// Check if campaign exists
const campaign = await Campaign.findOne({
campaignId: req.body.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 set data for Facebook
const adSetData = {
name: req.body.name,
campaign_id: req.body.campaignId,
status: req.body.status || 'PAUSED',
targeting: req.body.targeting,
optimization_goal: req.body.optimization,
bid_strategy: req.body.bidStrategy,
bid_amount: req.body.bidAmount,
start_time: req.body.startTime,
end_time: req.body.endTime
};
// Set budget based on budget type
if (req.body.budgetType === 'DAILY' || !req.body.budgetType) {
adSetData.daily_budget = req.body.budget;
} else {
adSetData.lifetime_budget = req.body.budget;
}
// Create ad set on Facebook
const fbResponse = await fbApi.createAdSet(campaign.adAccountId, adSetData);
// Create ad set in database
const adSet = new AdSet({
adSetId: fbResponse.id,
name: req.body.name,
campaignId: req.body.campaignId,
userId: req.user._id,
status: req.body.status || 'PAUSED',
targeting: req.body.targeting,
optimization: req.body.optimization,
bidStrategy: req.body.bidStrategy,
bidAmount: req.body.bidAmount,
budget: req.body.budget,
budgetType: req.body.budgetType || 'DAILY',
startTime: req.body.startTime,
endTime: req.body.endTime,
lastSyncedAt: new Date()
});
// Save ad set
await adSet.save();
return sendSuccess(res, adSet, 'Ad set created successfully', 201);
} catch (error) {
logger.error(`Error creating ad set: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/ad-sets/:id
* @desc Get ad set by ID
* @access Private
*/
router.get('/:id', protect, validate(schemas.idParam, 'params'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
userId: req.user._id
});
if (!adSet) {
return next(new NotFoundError('Ad set not found'));
}
return sendSuccess(res, adSet, 'Ad set retrieved successfully');
} catch (error) {
logger.error(`Error retrieving ad set: ${error.message}`);
return next(error);
}
});
/**
* @route PUT /api/ad-sets/:id
* @desc Update ad set
* @access Private
*/
router.put('/:id', protect, checkFacebookToken, validate(schemas.idParam, 'params'), validate(schemas.adSet.update), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
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);
// Prepare ad set data for Facebook
const adSetData = {};
if (req.body.name) adSetData.name = req.body.name;
if (req.body.status) adSetData.status = req.body.status;
if (req.body.targeting) adSetData.targeting = req.body.targeting;
if (req.body.optimization) adSetData.optimization_goal = req.body.optimization;
if (req.body.bidStrategy) adSetData.bid_strategy = req.body.bidStrategy;
if (req.body.bidAmount) adSetData.bid_amount = req.body.bidAmount;
if (req.body.startTime) adSetData.start_time = req.body.startTime;
if (req.body.endTime) adSetData.end_time = req.body.endTime;
// Set budget based on budget type
if (req.body.budget) {
if (req.body.budgetType === 'DAILY' || (!req.body.budgetType && adSet.budgetType === 'DAILY')) {
adSetData.daily_budget = req.body.budget;
} else {
adSetData.lifetime_budget = req.body.budget;
}
}
// Update ad set on Facebook
await fbApi.updateAdSet(req.params.id, adSetData);
// Update ad set in database
if (req.body.name) adSet.name = req.body.name;
if (req.body.status) adSet.status = req.body.status;
if (req.body.targeting) adSet.targeting = req.body.targeting;
if (req.body.optimization) adSet.optimization = req.body.optimization;
if (req.body.bidStrategy) adSet.bidStrategy = req.body.bidStrategy;
if (req.body.bidAmount) adSet.bidAmount = req.body.bidAmount;
if (req.body.budget) adSet.budget = req.body.budget;
if (req.body.budgetType) adSet.budgetType = req.body.budgetType;
if (req.body.startTime) adSet.startTime = req.body.startTime;
if (req.body.endTime) adSet.endTime = req.body.endTime;
adSet.lastSyncedAt = new Date();
// Save ad set
await adSet.save();
return sendSuccess(res, adSet, 'Ad set updated successfully');
} catch (error) {
logger.error(`Error updating ad set: ${error.message}`);
return next(error);
}
});
/**
* @route DELETE /api/ad-sets/:id
* @desc Delete ad set
* @access Private
*/
router.delete('/:id', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
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);
// Delete ad set on Facebook (actually sets status to DELETED)
await fbApi.updateAdSet(req.params.id, { status: 'DELETED' });
// Update ad set status in database
adSet.status = 'DELETED';
await adSet.save();
return sendSuccess(res, { id: req.params.id }, 'Ad set deleted successfully');
} catch (error) {
logger.error(`Error deleting ad set: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/ad-sets/:id/insights
* @desc Get insights for an ad set
* @access Private
*/
router.get('/:id/insights', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
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 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: 'adset'
});
return sendSuccess(res, insights, 'Ad set insights retrieved successfully');
} catch (error) {
logger.error(`Error retrieving ad set insights: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/ad-sets/:id/ads
* @desc Get ads for an ad set
* @access Private
*/
router.get('/:id/ads', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
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 ads = await fbApi.getAds(req.params.id);
return sendSuccess(res, ads, 'Ad set ads retrieved successfully');
} catch (error) {
logger.error(`Error retrieving ad set ads: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/ad-sets/:id/analytics
* @desc Get analytics for an ad set
* @access Private
*/
router.get('/:id/analytics', protect, validate(schemas.idParam, 'params'), validate(schemas.analytics.query, 'query'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
userId: req.user._id
});
if (!adSet) {
return next(new NotFoundError('Ad set 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.getAdSetAnalytics(req.params.id, startDate, endDate);
return sendSuccess(res, analyticsData, 'Ad set analytics retrieved successfully');
} catch (error) {
logger.error(`Error retrieving ad set analytics: ${error.message}`);
return next(error);
}
});
/**
* @route POST /api/ad-sets/:id/fetch-analytics
* @desc Fetch and store analytics for an ad set
* @access Private
*/
router.post('/:id/fetch-analytics', protect, checkFacebookToken, validate(schemas.idParam, 'params'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
userId: req.user._id
});
if (!adSet) {
return next(new NotFoundError('Ad set 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.fetchAdSetAnalytics(req.params.id, timeRange);
return sendSuccess(res, analyticsData, 'Ad set analytics fetched successfully');
} catch (error) {
logger.error(`Error fetching ad set analytics: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/ad-sets/:id/targeting-recommendations
* @desc Get targeting recommendations for an ad set
* @access Private
*/
router.get('/:id/targeting-recommendations', protect, validate(schemas.idParam, 'params'), async (req, res, next) => {
try {
// Get ad set from database
const adSet = await AdSet.findOne({
adSetId: req.params.id,
userId: req.user._id
});
if (!adSet) {
return next(new NotFoundError('Ad set not found'));
}
// Create recommendation service
const recommendationService = require('../services/recommendationService').createForUser(req.user);
// Get targeting recommendations
const recommendations = await recommendationService.getTargetingRecommendations(req.params.id);
return sendSuccess(res, recommendations, 'Ad set targeting recommendations retrieved successfully');
} catch (error) {
logger.error(`Error retrieving ad set targeting recommendations: ${error.message}`);
return next(error);
}
});
module.exports = router;