csa-backend-test/app/controllers/ai-notification.controller.js

380 lines
12 KiB
JavaScript

// 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);
}
};