// ENVIRONMENT require('dotenv').config(); // DATABASE const { PrismaClient: CMSClient } = require("../../prisma/clients/cms"); const prisma = new CMSClient(); // SERVICES const { getAINotificationAnalytics, updateDailyAnalytics } = require("../services/notification.services.js"); const { getScheduledNotificationsStats, cancelScheduledNotification } = require("../services/notification-scheduler.services.js"); const { manualTriggerScheduledProcessor } = require("../services/scheduled-notification-processor.services.js"); const logger = require("../services/logger.services"); const { localTime } = require("../services/time.services.js"); // CONSTANTS const { badRequestResponse, successResponse, notFoundResponse } = require("../res/responses.js"); // CONTROLLER exports.getAnalytics = async (req, res) => { try { const { startDate, endDate, limit } = req.query; const options = {}; if (startDate) options.startDate = new Date(startDate); if (endDate) options.endDate = new Date(endDate); if (limit) options.limit = parseInt(limit); const analytics = await getAINotificationAnalytics(options); const integrationInfo = { source: "AI notifications are triggered by user activities processed in activity-management", activityTriggerEndpoint: "/activity-management/trigger-notification", activityAnalysisEndpoint: "/activity-management/analyze", activityProcessingEndpoint: "/activity-management/process-all", lastUpdated: new Date().toISOString() }; return successResponse(res, "AI Notification analytics retrieved successfully!", { ...analytics, integration: integrationInfo }); } catch (err) { logger.error(`Error getting AI notification analytics: ${err}`); return badRequestResponse(res, "Error retrieving AI notification analytics", err); } }; exports.updateAnalytics = async (req, res) => { try { const { date } = req.body; const targetDate = date ? new Date(date) : new Date(); const result = await updateDailyAnalytics(targetDate); return successResponse(res, "AI Notification analytics updated successfully!", result); } catch (err) { logger.error(`Error updating AI notification analytics: ${err}`); return badRequestResponse(res, "Error updating AI notification analytics", err); } }; exports.getAllNotifications = async (req, res) => { try { const { page = 1, limit = 20, status, userID, startDate, endDate } = req.query; const skip = (parseInt(page) - 1) * parseInt(limit); const where = {}; if (status) { const statuses = Array.isArray(status) ? status : String(status).split(",").map(s => s.trim()).filter(Boolean); where.SentStatus_AIN = statuses.length > 1 ? { in: statuses } : statuses[0]; } if (userID) { where.UserID_AIN = userID; } if (startDate || endDate) { where.CreatedAt_AIN = {}; if (startDate) where.CreatedAt_AIN.gte = new Date(startDate); if (endDate) where.CreatedAt_AIN.lte = new Date(endDate); } const [notifications, total] = await Promise.all([ prisma.aINotification.findMany({ where, orderBy: { CreatedAt_AIN: 'desc' }, skip, take: parseInt(limit) }), prisma.aINotification.count({ where }) ]); // Add activity correlation info for recent notifications const notificationsWithActivityInfo = notifications.map(notification => { let activityTypes = []; try { activityTypes = JSON.parse(notification.ActivityTypes_AIN || '[]'); } catch (e) { activityTypes = []; } return { ...notification, activityTypes, activityContext: `Triggered by ${notification.AnalyzedActivities_AIN} activities over ${notification.ActivityTimeRange_AIN} minutes`, aiModel: notification.AIModel_AIN }; }); return successResponse(res, "AI Notifications retrieved successfully!", { notifications: notificationsWithActivityInfo, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / parseInt(limit)) }, activityIntegration: { description: "Each notification is generated from user activity analysis", activityManagementEndpoint: "/activity-management/create", aiAnalysisEndpoint: "/activity-management/analyze" } }); } catch (err) { logger.error(`Error getting AI notifications: ${err}`); return badRequestResponse(res, "Error retrieving AI notifications", err); } }; exports.getNotificationDetail = async (req, res) => { try { const { id } = req.params; const notification = await prisma.aINotification.findFirst({ where: { UUID_AIN: id } }); if (!notification) { return notFoundResponse(res, "AI Notification not found", null); } let activityTypes = []; try { activityTypes = JSON.parse(notification.ActivityTypes_AIN || '[]'); } catch (e) { activityTypes = []; } return successResponse(res, "AI Notification detail retrieved successfully!", { ...notification, ActivityTypes_AIN: activityTypes }); } catch (err) { logger.error(`Error getting AI notification detail: ${err}`); return badRequestResponse(res, "Error retrieving AI notification detail", err); } }; exports.getDashboardStats = async (req, res) => { try { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); const last7Days = new Date(today); last7Days.setDate(last7Days.getDate() - 7); const last30Days = new Date(today); last30Days.setDate(last30Days.getDate() - 30); const [ todayStats, yesterdayStats, weeklyStats, monthlyStats, recentFailures, topActivityTypes ] = await Promise.all([ prisma.aINotification.groupBy({ by: ['SentStatus_AIN'], where: { CreatedAt_AIN: { gte: today } }, _count: { SentStatus_AIN: true } }), prisma.aINotification.groupBy({ by: ['SentStatus_AIN'], where: { CreatedAt_AIN: { gte: yesterday, lt: today } }, _count: { SentStatus_AIN: true } }), prisma.aINotification.groupBy({ by: ['SentStatus_AIN'], where: { CreatedAt_AIN: { gte: last7Days } }, _count: { SentStatus_AIN: true } }), prisma.aINotification.groupBy({ by: ['SentStatus_AIN'], where: { CreatedAt_AIN: { gte: last30Days } }, _count: { SentStatus_AIN: true } }), prisma.aINotification.findMany({ where: { SentStatus_AIN: 'failed', CreatedAt_AIN: { gte: last7Days } }, orderBy: { CreatedAt_AIN: 'desc' }, take: 10, select: { UUID_AIN: true, UserID_AIN: true, ErrorMessage_AIN: true, CreatedAt_AIN: true } }), prisma.aINotification.findMany({ where: { CreatedAt_AIN: { gte: last7Days }, ActivityTypes_AIN: { not: null } }, select: { ActivityTypes_AIN: true } }) ]); const processStats = (stats) => { const result = { total: 0, sent: 0, failed: 0, delivered: 0 }; stats.forEach(stat => { const count = stat._count.SentStatus_AIN; result.total += count; if (stat.SentStatus_AIN === 'sent') result.sent += count; if (stat.SentStatus_AIN === 'failed') result.failed += count; if (stat.SentStatus_AIN === 'delivered') result.delivered += count; }); return result; }; const activityTypeCount = {}; topActivityTypes.forEach(record => { try { const types = JSON.parse(record.ActivityTypes_AIN || '[]'); types.forEach(type => { activityTypeCount[type] = (activityTypeCount[type] || 0) + 1; }); } catch (e) { } }); const topActivityTypesResult = Object.entries(activityTypeCount) .sort(([,a], [,b]) => b - a) .slice(0, 10) .map(([type, count]) => ({ type, count })); return successResponse(res, "Dashboard stats retrieved successfully!", { today: processStats(todayStats), yesterday: processStats(yesterdayStats), last7Days: processStats(weeklyStats), last30Days: processStats(monthlyStats), recentFailures, topActivityTypes: topActivityTypesResult }); } catch (err) { logger.error(`Error getting dashboard stats: ${err}`); return badRequestResponse(res, "Error retrieving dashboard stats", err); } }; // NEW: Get scheduled notifications statistics exports.getScheduledStats = async (req, res) => { try { const stats = await getScheduledNotificationsStats(); return successResponse(res, "Scheduled notifications stats retrieved successfully!", { ...stats, info: { description: "AI-powered predictive timing schedules notifications at optimal times", processingInterval: "Every 5 minutes", features: [ "Analyzes user activity patterns", "Predicts optimal delivery time", "Confidence scoring", "Engagement pattern detection" ] } }); } catch (err) { logger.error(`Error getting scheduled stats: ${err}`); return badRequestResponse(res, "Error retrieving scheduled stats", err); } }; // NEW: Manually trigger scheduled notification processor exports.triggerScheduledProcessor = async (req, res) => { try { logger.info("Manual trigger for scheduled notification processor"); const results = await manualTriggerScheduledProcessor(); return successResponse(res, "Scheduled notifications processed!", { ...results, message: `Processed ${results.processed} notifications, sent ${results.sent}, failed ${results.failed}` }); } catch (err) { logger.error(`Error triggering scheduled processor: ${err}`); return badRequestResponse(res, "Error processing scheduled notifications", err); } }; // NEW: Cancel a scheduled notification exports.cancelScheduled = async (req, res) => { try { const { id } = req.params; if (!id) { throw new Error("Notification ID is required"); } const result = await cancelScheduledNotification(id); return successResponse(res, "Scheduled notification cancelled successfully!", result); } catch (err) { logger.error(`Error cancelling scheduled notification: ${err}`); return badRequestResponse(res, "Error cancelling scheduled notification", err); } };