import prisma from '~/server/utils/prisma' export default defineEventHandler(async (event) => { try { // Check authentication const user = event.context.user; if (!user || !user.userID) { return { statusCode: 401, body: { success: false, message: 'Unauthorized' } } } const query = getQuery(event) const period = query.period || '7d' // Default to last 7 days const channel = query.channel || 'all' // Calculate date range based on selected period const endDate = new Date() let startDate = new Date() switch(period) { case '1d': startDate.setDate(startDate.getDate() - 1) break case '7d': startDate.setDate(startDate.getDate() - 7) break case '30d': startDate.setDate(startDate.getDate() - 30) break case '90d': startDate.setDate(startDate.getDate() - 90) break case '12m': startDate.setMonth(startDate.getMonth() - 12) break default: startDate.setDate(startDate.getDate() - 7) } // Channel filter const channelFilter = channel !== 'all' ? { channel_type: channel } : {} // Get key metrics const totalSent = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Sent', ...channelFilter } }) const previousPeriodEnd = new Date(startDate) const previousPeriodStart = new Date(startDate) // Calculate the same duration for previous period switch(period) { case '1d': previousPeriodStart.setDate(previousPeriodStart.getDate() - 1) break case '7d': previousPeriodStart.setDate(previousPeriodStart.getDate() - 7) break case '30d': previousPeriodStart.setDate(previousPeriodStart.getDate() - 30) break case '90d': previousPeriodStart.setDate(previousPeriodStart.getDate() - 90) break case '12m': previousPeriodStart.setMonth(previousPeriodStart.getMonth() - 12) break } const previousTotalSent = await prisma.notification_logs.count({ where: { created_at: { gte: previousPeriodStart, lt: startDate }, status: 'Sent', ...channelFilter } }) const sentChangePercent = previousTotalSent > 0 ? ((totalSent - previousTotalSent) / previousTotalSent * 100).toFixed(1) : '0.0' // Get success rate const totalAttempted = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, action: { in: ['Notification Sent', 'Delivery Attempted'] }, ...channelFilter } }) const successfulDeliveries = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Sent', ...channelFilter } }) const successRate = totalAttempted > 0 ? ((successfulDeliveries / totalAttempted) * 100).toFixed(1) : '0.0' const previousSuccessRate = await calculateSuccessRate( prisma, previousPeriodStart, startDate, channelFilter ) const successRateChangePercent = previousSuccessRate > 0 ? ((parseFloat(successRate) - previousSuccessRate) / previousSuccessRate * 100).toFixed(1) : '0.0' // Get open rate (if tracking available) const totalOpened = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Opened', ...channelFilter } }) const openRate = totalSent > 0 ? ((totalOpened / totalSent) * 100).toFixed(1) : '0.0' const previousOpenRate = await calculateOpenRate( prisma, previousPeriodStart, startDate, channelFilter ) const openRateChangePercent = previousOpenRate > 0 ? ((parseFloat(openRate) - previousOpenRate) / previousOpenRate * 100).toFixed(1) : '0.0' // Get click rate (if tracking available) const totalClicked = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Clicked', ...channelFilter } }) const clickRate = totalSent > 0 ? ((totalClicked / totalSent) * 100).toFixed(1) : '0.0' const previousClickRate = await calculateClickRate( prisma, previousPeriodStart, startDate, channelFilter ) const clickRateChangePercent = previousClickRate > 0 ? ((parseFloat(clickRate) - previousClickRate) / previousClickRate * 100).toFixed(1) : '0.0' // Get channel performance data const channelPerformance = await getChannelPerformance(prisma, startDate, endDate) // Get recent notable events const recentEvents = await getRecentEvents(prisma) return { statusCode: 200, body: { success: true, data: { keyMetrics: [ { title: "Total Sent", value: totalSent.toLocaleString(), icon: "ic:outline-send", trend: parseFloat(sentChangePercent) >= 0 ? "up" : "down", change: `${parseFloat(sentChangePercent) >= 0 ? '+' : ''}${sentChangePercent}%` }, { title: "Success Rate", value: `${successRate}%`, icon: "ic:outline-check-circle", trend: parseFloat(successRateChangePercent) >= 0 ? "up" : "down", change: `${parseFloat(successRateChangePercent) >= 0 ? '+' : ''}${successRateChangePercent}%` }, { title: "Open Rate", value: `${openRate}%`, icon: "ic:outline-open-in-new", trend: parseFloat(openRateChangePercent) >= 0 ? "up" : "down", change: `${parseFloat(openRateChangePercent) >= 0 ? '+' : ''}${openRateChangePercent}%` }, { title: "Click Rate", value: `${clickRate}%`, icon: "ic:outline-touch-app", trend: parseFloat(clickRateChangePercent) >= 0 ? "up" : "down", change: `${parseFloat(clickRateChangePercent) >= 0 ? '+' : ''}${clickRateChangePercent}%` } ], channelPerformance, recentEvents } } } } catch (error) { console.error('Error fetching analytics data:', error) return { statusCode: 500, body: { success: false, message: 'Failed to fetch analytics data', error: error.message } } } }) // Helper functions async function calculateSuccessRate(prisma, startDate, endDate, channelFilter) { const totalAttempted = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lt: endDate }, action: { in: ['Notification Sent', 'Delivery Attempted'] }, ...channelFilter } }) const successfulDeliveries = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lt: endDate }, status: 'Sent', ...channelFilter } }) return totalAttempted > 0 ? ((successfulDeliveries / totalAttempted) * 100) : 0 } async function calculateOpenRate(prisma, startDate, endDate, channelFilter) { const totalSent = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lt: endDate }, status: 'Sent', ...channelFilter } }) const totalOpened = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lt: endDate }, status: 'Opened', ...channelFilter } }) return totalSent > 0 ? ((totalOpened / totalSent) * 100) : 0 } async function calculateClickRate(prisma, startDate, endDate, channelFilter) { const totalSent = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lt: endDate }, status: 'Sent', ...channelFilter } }) const totalClicked = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lt: endDate }, status: 'Clicked', ...channelFilter } }) return totalSent > 0 ? ((totalClicked / totalSent) * 100) : 0 } async function getChannelPerformance(prisma, startDate, endDate) { // Define the channels we want to analyze const channels = ['Email', 'SMS', 'Push Notification', 'Webhook'] const channelIcons = { 'Email': 'ic:outline-email', 'SMS': 'ic:outline-sms', 'Push Notification': 'ic:outline-notifications', 'Webhook': 'ic:outline-webhook' } const result = [] for (const channel of channels) { // Get total sent for this channel const sent = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Sent', channel_type: channel } }) // Get total failed for this channel const failed = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Failed', channel_type: channel } }) // Get total bounced for this channel const bounced = await prisma.notification_logs.count({ where: { created_at: { gte: startDate, lte: endDate }, status: 'Bounced', channel_type: channel } }) // Calculate total attempted (sent + failed + bounced) const total = sent + failed + bounced // Calculate success rate const successRate = total > 0 ? ((sent / total) * 100).toFixed(1) : '0.0' // Calculate bounce rate const bounceRate = total > 0 ? ((bounced / total) * 100).toFixed(1) : '0.0' // Calculate failure rate const failureRate = total > 0 ? ((failed / total) * 100).toFixed(1) : '0.0' result.push({ name: channel, icon: channelIcons[channel] || 'ic:outline-message', sent: sent.toString(), failed: failed.toString(), bounced: bounced.toString(), total: total.toString(), successRate, bounceRate, failureRate }) } return result } // Function to get recent notable events async function getRecentEvents(prisma) { // Get last 24 hours const now = new Date() const oneDayAgo = new Date(now) oneDayAgo.setDate(oneDayAgo.getDate() - 1) // Find recent logs with interesting events const recentLogs = await prisma.notification_logs.findMany({ where: { created_at: { gte: oneDayAgo, lte: now }, OR: [ { status: 'Failed' }, { status: 'Bounced' }, { AND: [ { status: 'Sent' }, { details: { contains: 'batch' } } ] }, { status: 'Opened' } ] }, orderBy: { created_at: 'desc' }, take: 5 }) // Transform logs into notable events return recentLogs.map(log => { let type = 'info' let title = log.action let description = log.details || '' let value = '' let time = formatTimeAgo(log.created_at) if (log.status === 'Failed') { type = 'error' title = 'Delivery Failure' description = log.error_message || log.details || '' value = log.channel_type || '' } else if (log.status === 'Bounced') { type = 'warning' title = 'Delivery Bounced' value = log.channel_type || '' } else if (log.status === 'Opened') { type = 'success' title = 'Notification Opened' value = 'User Engagement' } else if (log.status === 'Sent' && log.details?.includes('batch')) { type = 'info' title = 'Batch Delivery' // Try to extract batch size from details const match = log.details?.match(/(\d+)\s*notifications?/i) value = match ? `${match[1]} sent` : '' } return { title, description, value, time, type } }) } // Format time ago function formatTimeAgo(timestamp) { const now = new Date() const time = new Date(timestamp) const diffInMinutes = Math.floor((now - time) / (1000 * 60)) if (diffInMinutes < 1) return "Just now" if (diffInMinutes < 60) return `${diffInMinutes} minutes ago` if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)} hours ago` return `${Math.floor(diffInMinutes / 1440)} days ago` }