Spaces:
Running
Running
| const Visit = require('../models/visitModel'); | |
| // Track a visit | |
| exports.trackVisit = async (req, res) => { | |
| try { | |
| const { sessionId, page, userId } = req.body; | |
| if (!sessionId) { | |
| return res.status(400).json({ | |
| status: 'fail', | |
| message: 'Session ID is required', | |
| }); | |
| } | |
| // Get IP address from request | |
| const xForwardedFor = req.headers['x-forwarded-for']; | |
| const ipAddress = | |
| (xForwardedFor ? xForwardedFor.split(',')[0] : null) || | |
| req.connection.remoteAddress || | |
| req.socket.remoteAddress || | |
| req.ip; | |
| // Get user agent | |
| const userAgent = req.headers['user-agent'] || ''; | |
| // Grouping priority: | |
| // 1. If we have a userId, we find the ONE global record for this user | |
| // 2. If no userId (guest), we find a recent record for this sessionId | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| const query = { createdAt: { $gte: today } }; | |
| if (userId) { | |
| query.user = userId; | |
| } else { | |
| query.sessionId = sessionId; | |
| } | |
| let visit = await Visit.findOne(query).populate('user', 'name email'); | |
| if (visit) { | |
| // Grouping: Increment count and update last active time | |
| visit.count += 1; | |
| visit.lastVisitedAt = new Date(); | |
| // Update user ID if it was null before (guest just logged in) | |
| if (!visit.user && userId) { | |
| visit.user = userId; | |
| } | |
| // Update IP and User Agent to latest | |
| visit.ipAddress = ipAddress; | |
| visit.userAgent = userAgent; | |
| visit.page = page || '/'; | |
| await visit.save(); | |
| } else { | |
| // Create new visit document | |
| visit = await Visit.create({ | |
| user: userId || null, | |
| ipAddress, | |
| userAgent, | |
| page: page || '/', | |
| sessionId, | |
| count: 1, | |
| lastVisitedAt: new Date(), | |
| }); | |
| // Populate if it was a user | |
| if (userId) { | |
| await visit.populate('user', 'name email'); | |
| } | |
| } | |
| res.status(200).json({ | |
| status: 'success', | |
| data: { visit }, | |
| }); | |
| } catch (err) { | |
| res.status(500).json({ | |
| status: 'fail', | |
| message: err.message, | |
| }); | |
| } | |
| }; | |
| // Get visit statistics | |
| exports.getVisitStats = async (req, res) => { | |
| try { | |
| const now = new Date(); | |
| // Today's start | |
| const todayStart = new Date( | |
| now.getFullYear(), | |
| now.getMonth(), | |
| now.getDate(), | |
| ); | |
| // Current month boundaries | |
| const currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1); | |
| const currentMonthEnd = new Date( | |
| now.getFullYear(), | |
| now.getMonth() + 1, | |
| 0, | |
| 23, | |
| 59, | |
| 59, | |
| ); | |
| // Previous month boundaries | |
| const previousMonthStart = new Date( | |
| now.getFullYear(), | |
| now.getMonth() - 1, | |
| 1, | |
| ); | |
| const previousMonthEnd = new Date( | |
| now.getFullYear(), | |
| now.getMonth(), | |
| 0, | |
| 23, | |
| 59, | |
| 59, | |
| ); | |
| // Helper for summing counts and counting documents | |
| const getStats = async (filter = {}) => { | |
| const uniqueVisits = await Visit.countDocuments(filter); | |
| const totalResult = await Visit.aggregate([ | |
| { $match: filter }, | |
| { $group: { _id: null, total: { $sum: '$count' } } }, | |
| ]); | |
| const totalVisits = totalResult.length > 0 ? totalResult[0].total : 0; | |
| return { uniqueVisits, totalVisits }; | |
| }; | |
| // Get all time stats | |
| const allTime = await getStats(); | |
| // Get today's stats | |
| const today = await getStats({ createdAt: { $gte: todayStart } }); | |
| // Get monthly stats for growth calculation | |
| const currentMonth = await getStats({ | |
| createdAt: { $gte: currentMonthStart, $lte: currentMonthEnd }, | |
| }); | |
| const previousMonth = await getStats({ | |
| createdAt: { $gte: previousMonthStart, $lte: previousMonthEnd }, | |
| }); | |
| // Calculate monthly growth percentage based on TOTAL visits | |
| let monthlyGrowth = 0; | |
| if (previousMonth.totalVisits > 0) { | |
| monthlyGrowth = | |
| ((currentMonth.totalVisits - previousMonth.totalVisits) / | |
| previousMonth.totalVisits) * | |
| 100; | |
| } else if (currentMonth.totalVisits > 0) { | |
| monthlyGrowth = 100; | |
| } | |
| res.status(200).json({ | |
| status: 'success', | |
| data: { | |
| totalVisits: allTime.totalVisits, | |
| uniqueVisits: allTime.uniqueVisits, | |
| todayVisits: today.totalVisits, | |
| todayUnique: today.uniqueVisits, | |
| currentMonthVisits: currentMonth.totalVisits, | |
| previousMonthVisits: previousMonth.totalVisits, | |
| monthlyGrowth: parseFloat(monthlyGrowth.toFixed(1)), | |
| }, | |
| }); | |
| } catch (err) { | |
| res.status(500).json({ | |
| status: 'fail', | |
| message: err.message, | |
| }); | |
| } | |
| }; | |
| // Get all visits (admin only) | |
| exports.getAllVisits = async (req, res) => { | |
| try { | |
| const { page = 1, limit = 50, startDate, endDate } = req.query; | |
| // Build filter | |
| const filter = {}; | |
| if (startDate || endDate) { | |
| filter.createdAt = {}; | |
| if (startDate) filter.createdAt.$gte = new Date(startDate); | |
| if (endDate) filter.createdAt.$lte = new Date(endDate); | |
| } | |
| const visits = await Visit.find(filter) | |
| .populate('user', 'name email') | |
| .sort({ createdAt: -1 }) | |
| .limit(limit * 1) | |
| .skip((page - 1) * limit); | |
| const count = await Visit.countDocuments(filter); | |
| res.status(200).json({ | |
| status: 'success', | |
| results: visits.length, | |
| totalPages: Math.ceil(count / limit), | |
| currentPage: page, | |
| data: { visits }, | |
| }); | |
| } catch (err) { | |
| res.status(500).json({ | |
| status: 'fail', | |
| message: err.message, | |
| }); | |
| } | |
| }; | |