/** * Analytics Controller * API endpoints for analytics dashboard */ const { PageView, Session } = require('../models/Analytics.model'); /** * Get analytics overview */ async function getOverview(req, res) { try { const { period = '24h' } = req.query; // Calculate date range let startDate; const endDate = new Date(); switch (period) { case '1h': startDate = new Date(Date.now() - 60 * 60 * 1000); break; case '24h': startDate = new Date(Date.now() - 24 * 60 * 60 * 1000); break; case '7d': startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); break; case '30d': startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); break; default: startDate = new Date(Date.now() - 24 * 60 * 60 * 1000); } // Get real-time visitors const realTimeVisitors = await Session.getRealTimeVisitors(); // Get total page views const totalPageViews = await PageView.getPageViews(startDate, endDate); // Get unique visitors const uniqueVisitors = await PageView.getUniqueVisitors(startDate, endDate); // Get bounce rate const bounceRate = await Session.getBounceRate(startDate, endDate); // Get average session duration const avgDuration = await Session.getAverageDuration(startDate, endDate); // Get top pages const topPages = await PageView.getTopPages(startDate, endDate, 10); // Get top referrers const topReferrers = await PageView.getTopReferrers(startDate, endDate, 10); res.json({ success: true, period, data: { realTimeVisitors, totalPageViews, uniqueVisitors, bounceRate: Math.round(bounceRate * 10) / 10, avgDuration, topPages, topReferrers } }); } catch (error) { console.error('[Analytics] Overview error:', error); res.status(500).json({ success: false, error: 'Failed to fetch analytics overview' }); } } /** * Get hourly trend data */ async function getHourlyTrend(req, res) { try { const { hours = 24 } = req.query; const startDate = new Date(Date.now() - parseInt(hours, 10) * 60 * 60 * 1000); const endDate = new Date(); const hourlyData = await PageView.getHourlyViews(startDate, endDate); res.json({ success: true, data: hourlyData }); } catch (error) { console.error('[Analytics] Hourly trend error:', error); res.status(500).json({ success: false, error: 'Failed to fetch hourly trend' }); } } /** * Get live visitors (for real-time dashboard) */ async function getLiveVisitors(req, res) { try { const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); const activeSessions = await Session.find({ isActive: true, endTime: { $gte: fiveMinutesAgo } }).select('entryPage exitPage pages startTime endTime language'); const currentPages = await PageView.find({ timestamp: { $gte: fiveMinutesAgo } }) .sort({ timestamp: -1 }) .limit(20) .select('path timestamp visitorId sessionId'); res.json({ success: true, data: { count: activeSessions.length, sessions: activeSessions, recentPages: currentPages } }); } catch (error) { console.error('[Analytics] Live visitors error:', error); res.status(500).json({ success: false, error: 'Failed to fetch live visitors' }); } } /** * Get page-specific analytics */ async function getPageAnalytics(req, res) { try { const { path, period = '7d' } = req.query; if (!path) { return res.status(400).json({ success: false, error: 'Page path required' }); } // Calculate date range let startDate; const endDate = new Date(); switch (period) { case '24h': startDate = new Date(Date.now() - 24 * 60 * 60 * 1000); break; case '7d': startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); break; case '30d': startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); break; default: startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); } const pageViews = await PageView.countDocuments({ path, timestamp: { $gte: startDate, $lte: endDate } }); const uniqueVisitors = await PageView.distinct('visitorId', { path, timestamp: { $gte: startDate, $lte: endDate } }); const avgDuration = await PageView.aggregate([ { $match: { path, timestamp: { $gte: startDate, $lte: endDate }, duration: { $exists: true, $gt: 0 } } }, { $group: { _id: null, avgDuration: { $avg: '$duration' } } } ]); res.json({ success: true, path, period, data: { pageViews, uniqueVisitors: uniqueVisitors.length, avgDuration: avgDuration.length > 0 ? Math.round(avgDuration[0].avgDuration) : 0 } }); } catch (error) { console.error('[Analytics] Page analytics error:', error); res.status(500).json({ success: false, error: 'Failed to fetch page analytics' }); } } /** * Get UTM campaign performance */ async function getCampaignAnalytics(req, res) { try { const { period = '30d' } = req.query; let startDate; const endDate = new Date(); switch (period) { case '7d': startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); break; case '30d': startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); break; case '90d': startDate = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); break; default: startDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); } const campaigns = await PageView.aggregate([ { $match: { timestamp: { $gte: startDate, $lte: endDate }, utmCampaign: { $exists: true, $ne: null } } }, { $group: { _id: { campaign: '$utmCampaign', source: '$utmSource', medium: '$utmMedium' }, visits: { $sum: 1 }, uniqueVisitors: { $addToSet: '$visitorId' } } }, { $project: { campaign: '$_id.campaign', source: '$_id.source', medium: '$_id.medium', visits: 1, uniqueVisitors: { $size: '$uniqueVisitors' } } }, { $sort: { visits: -1 } }, { $limit: 20 } ]); res.json({ success: true, period, data: campaigns }); } catch (error) { console.error('[Analytics] Campaign analytics error:', error); res.status(500).json({ success: false, error: 'Failed to fetch campaign analytics' }); } } module.exports = { getOverview, getHourlyTrend, getLiveVisitors, getPageAnalytics, getCampaignAnalytics };