380 lines
12 KiB
JavaScript
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);
|
|
}
|
|
}; |