recommendations.js•10.7 kB
/**
* Recommendations routes
* Handles operations related to Facebook ad recommendations
*/
const express = require('express');
const router = express.Router();
const { protect } = require('../middleware/auth');
const { validate, schemas } = require('../middleware/validator');
const { sendSuccess, sendError } = require('../utils/responseFormatter');
const { NotFoundError } = require('../utils/errorTypes');
const AdAccount = require('../models/adAccount');
const recommendationService = require('../services/recommendationService');
const logger = require('../utils/logger');
/**
* @route GET /api/recommendations/budget
* @desc Get budget optimization recommendations
* @access Private
*/
router.get('/budget', protect, async (req, res, next) => {
try {
// Get ad account ID from query params
const { adAccountId } = req.query;
if (!adAccountId) {
return next(new NotFoundError('Ad account ID is required'));
}
// Check if ad account exists
const adAccount = await AdAccount.findOne({
accountId: adAccountId,
userId: req.user._id
});
if (!adAccount) {
return next(new NotFoundError('Ad account not found'));
}
// Create recommendation service
const recommendations = recommendationService.createForUser(req.user);
// Get budget recommendations
const budgetRecommendations = await recommendations.getBudgetRecommendations(adAccountId);
return sendSuccess(res, budgetRecommendations, 'Budget recommendations retrieved successfully');
} catch (error) {
logger.error(`Error retrieving budget recommendations: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/recommendations/targeting
* @desc Get targeting recommendations for an ad set
* @access Private
*/
router.get('/targeting', protect, async (req, res, next) => {
try {
// Get ad set ID from query params
const { adSetId } = req.query;
if (!adSetId) {
return next(new NotFoundError('Ad set ID is required'));
}
// Create recommendation service
const recommendations = recommendationService.createForUser(req.user);
// Get targeting recommendations
const targetingRecommendations = await recommendations.getTargetingRecommendations(adSetId);
return sendSuccess(res, targetingRecommendations, 'Targeting recommendations retrieved successfully');
} catch (error) {
logger.error(`Error retrieving targeting recommendations: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/recommendations/creative
* @desc Get creative performance recommendations
* @access Private
*/
router.get('/creative', protect, async (req, res, next) => {
try {
// Get ad account ID from query params
const { adAccountId } = req.query;
if (!adAccountId) {
return next(new NotFoundError('Ad account ID is required'));
}
// Check if ad account exists
const adAccount = await AdAccount.findOne({
accountId: adAccountId,
userId: req.user._id
});
if (!adAccount) {
return next(new NotFoundError('Ad account not found'));
}
// Create recommendation service
const recommendations = recommendationService.createForUser(req.user);
// Get creative recommendations
const creativeRecommendations = await recommendations.getCreativeRecommendations(adAccountId);
return sendSuccess(res, creativeRecommendations, 'Creative recommendations retrieved successfully');
} catch (error) {
logger.error(`Error retrieving creative recommendations: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/recommendations/all
* @desc Get all recommendations for an ad account
* @access Private
*/
router.get('/all', protect, async (req, res, next) => {
try {
// Get ad account ID from query params
const { adAccountId } = req.query;
if (!adAccountId) {
return next(new NotFoundError('Ad account ID is required'));
}
// Check if ad account exists
const adAccount = await AdAccount.findOne({
accountId: adAccountId,
userId: req.user._id
});
if (!adAccount) {
return next(new NotFoundError('Ad account not found'));
}
// Create recommendation service
const recommendations = recommendationService.createForUser(req.user);
// Get all recommendations
const [budgetRecommendations, creativeRecommendations] = await Promise.all([
recommendations.getBudgetRecommendations(adAccountId),
recommendations.getCreativeRecommendations(adAccountId)
]);
// Combine recommendations
const allRecommendations = {
budget: budgetRecommendations,
creative: creativeRecommendations
};
return sendSuccess(res, allRecommendations, 'All recommendations retrieved successfully');
} catch (error) {
logger.error(`Error retrieving all recommendations: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/recommendations/summary
* @desc Get recommendations summary for an ad account
* @access Private
*/
router.get('/summary', protect, async (req, res, next) => {
try {
// Get ad account ID from query params
const { adAccountId } = req.query;
if (!adAccountId) {
return next(new NotFoundError('Ad account ID is required'));
}
// Check if ad account exists
const adAccount = await AdAccount.findOne({
accountId: adAccountId,
userId: req.user._id
});
if (!adAccount) {
return next(new NotFoundError('Ad account not found'));
}
// Create recommendation service
const recommendations = recommendationService.createForUser(req.user);
// Get all recommendations
const [budgetRecommendations, creativeRecommendations] = await Promise.all([
recommendations.getBudgetRecommendations(adAccountId),
recommendations.getCreativeRecommendations(adAccountId)
]);
// Create summary
const summary = {
totalRecommendations: 0,
budgetRecommendations: {
count: budgetRecommendations.length,
increaseBudget: 0,
decreaseBudget: 0,
other: 0
},
creativeRecommendations: {
count: creativeRecommendations.length,
lowCtr: 0,
lowConversionRate: 0,
other: 0
}
};
// Count budget recommendations by type
budgetRecommendations.forEach(campaign => {
campaign.recommendations.forEach(rec => {
summary.totalRecommendations++;
if (rec.type === 'increase_budget') {
summary.budgetRecommendations.increaseBudget++;
} else if (rec.type === 'decrease_budget') {
summary.budgetRecommendations.decreaseBudget++;
} else {
summary.budgetRecommendations.other++;
}
});
});
// Count creative recommendations by type
creativeRecommendations.forEach(ad => {
ad.recommendations.forEach(rec => {
summary.totalRecommendations++;
if (rec.type === 'low_ctr') {
summary.creativeRecommendations.lowCtr++;
} else if (rec.type === 'low_conversion_rate') {
summary.creativeRecommendations.lowConversionRate++;
} else {
summary.creativeRecommendations.other++;
}
});
});
return sendSuccess(res, summary, 'Recommendations summary retrieved successfully');
} catch (error) {
logger.error(`Error retrieving recommendations summary: ${error.message}`);
return next(error);
}
});
/**
* @route GET /api/recommendations/best-practices
* @desc Get best practices recommendations
* @access Private
*/
router.get('/best-practices', protect, (req, res) => {
const bestPractices = [
{
category: 'Campaign Structure',
recommendations: [
{
title: 'Use campaign budget optimization',
description: 'Let Facebook automatically distribute your budget across ad sets to get more results',
impact: 'high'
},
{
title: 'Group similar ad sets in the same campaign',
description: 'Organize ad sets with similar objectives and target audiences in the same campaign',
impact: 'medium'
},
{
title: 'Use different campaigns for different objectives',
description: 'Separate campaigns by objective (e.g., awareness, consideration, conversion)',
impact: 'high'
}
]
},
{
category: 'Targeting',
recommendations: [
{
title: 'Use lookalike audiences',
description: 'Create lookalike audiences based on your existing customers or high-value users',
impact: 'high'
},
{
title: 'Test different audience sizes',
description: 'Experiment with both broad and narrow audiences to find what works best',
impact: 'medium'
},
{
title: 'Use detailed targeting expansion',
description: 'Allow Facebook to reach people beyond your defined targeting when it\'s likely to improve performance',
impact: 'medium'
}
]
},
{
category: 'Creative',
recommendations: [
{
title: 'Test multiple ad formats',
description: 'Use a mix of image, video, carousel, and collection ads to see what performs best',
impact: 'high'
},
{
title: 'Keep ad copy concise',
description: 'Use short, compelling headlines and clear calls to action',
impact: 'medium'
},
{
title: 'Refresh creative regularly',
description: 'Update your creative every few weeks to prevent ad fatigue',
impact: 'high'
}
]
},
{
category: 'Optimization',
recommendations: [
{
title: 'Use automatic placements',
description: 'Let Facebook show your ads across all placements to reach more people at a lower cost',
impact: 'high'
},
{
title: 'Optimize for the right event',
description: 'Choose an optimization event that aligns with your campaign objective',
impact: 'high'
},
{
title: 'Use A/B testing',
description: 'Regularly test different elements of your ads to improve performance',
impact: 'medium'
}
]
}
];
return sendSuccess(res, bestPractices, 'Best practices recommendations retrieved successfully');
});
module.exports = router;