319 lines
8.1 KiB
JavaScript
319 lines
8.1 KiB
JavaScript
/**
|
|
* Notification types - standardized notification types
|
|
*/
|
|
export const NOTIFICATION_TYPES = {
|
|
// Task notifications
|
|
TASK_ASSIGNED: "task_assigned",
|
|
TASK_STATUS_CHANGED: "task_status_changed",
|
|
|
|
// Project notifications
|
|
PROJECT_UPDATED: "project_updated",
|
|
|
|
// System notifications
|
|
DUE_DATE_REMINDER: "due_date_reminder",
|
|
SYSTEM_ANNOUNCEMENT: "system_announcement",
|
|
MENTION: "mention",
|
|
};
|
|
|
|
/**
|
|
* Notification priorities
|
|
*/
|
|
export const NOTIFICATION_PRIORITIES = {
|
|
LOW: "low",
|
|
NORMAL: "normal",
|
|
HIGH: "high",
|
|
URGENT: "urgent",
|
|
};
|
|
|
|
/**
|
|
* Create a notification
|
|
* @param {Object} params - Notification parameters
|
|
* @param {string} params.userId - User to receive the notification
|
|
* @param {string} params.type - Notification type (use NOTIFICATION_TYPES constants)
|
|
* @param {string} params.title - Notification title
|
|
* @param {string} params.message - Notification message
|
|
* @param {string} [params.resourceType] - Type of related resource
|
|
* @param {string} [params.resourceId] - ID of the related resource
|
|
* @param {string} [params.priority] - Priority level (default: normal)
|
|
* @param {string} [params.actionUrl] - URL to navigate to when clicked
|
|
* @param {string} [params.expiresAt] - When the notification expires
|
|
*/
|
|
export async function createNotification({
|
|
userId,
|
|
type,
|
|
title,
|
|
message,
|
|
resourceType = null,
|
|
resourceId = null,
|
|
priority = NOTIFICATION_PRIORITIES.NORMAL,
|
|
actionUrl = null,
|
|
expiresAt = null,
|
|
}) {
|
|
try {
|
|
// Check if we're in Edge Runtime - if so, skip database operations
|
|
if (
|
|
typeof EdgeRuntime !== "undefined" ||
|
|
process.env.NEXT_RUNTIME === "edge"
|
|
) {
|
|
console.log(
|
|
`[Notification - Edge Runtime] ${type} notification for user ${userId}: ${title}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Dynamic import to avoid Edge Runtime issues
|
|
const { default: db } = await import("./db.js");
|
|
|
|
const stmt = db.prepare(`
|
|
INSERT INTO notifications (
|
|
user_id, type, title, message, resource_type, resource_id,
|
|
priority, action_url, expires_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`);
|
|
|
|
const result = stmt.run(
|
|
userId,
|
|
type,
|
|
title,
|
|
message,
|
|
resourceType,
|
|
resourceId,
|
|
priority,
|
|
actionUrl,
|
|
expiresAt
|
|
);
|
|
|
|
console.log(
|
|
`Notification created: ${type} for user ${userId} - ${title}`
|
|
);
|
|
|
|
return result.lastInsertRowid;
|
|
} catch (error) {
|
|
console.error("Failed to create notification:", error);
|
|
// Don't throw error to avoid breaking the main application flow
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get notifications for a user
|
|
* @param {string} userId - User ID
|
|
* @param {Object} options - Query options
|
|
* @param {boolean} [options.includeRead] - Include read notifications (default: false)
|
|
* @param {number} [options.limit] - Maximum number of notifications to return
|
|
* @param {number} [options.offset] - Number of notifications to skip
|
|
* @returns {Array} Array of notifications
|
|
*/
|
|
export async function getUserNotifications(
|
|
userId,
|
|
{ includeRead = false, limit = 50, offset = 0 } = {}
|
|
) {
|
|
try {
|
|
// Check if we're in Edge Runtime - if so, return empty array
|
|
if (
|
|
typeof EdgeRuntime !== "undefined" ||
|
|
process.env.NEXT_RUNTIME === "edge"
|
|
) {
|
|
console.log(
|
|
"[Notification - Edge Runtime] Cannot query notifications in Edge Runtime"
|
|
);
|
|
return [];
|
|
}
|
|
|
|
// Dynamic import to avoid Edge Runtime issues
|
|
const { default: db } = await import("./db.js");
|
|
|
|
let query = `
|
|
SELECT * FROM notifications
|
|
WHERE user_id = ?
|
|
`;
|
|
|
|
const params = [userId];
|
|
|
|
if (!includeRead) {
|
|
query += " AND is_read = 0";
|
|
}
|
|
|
|
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
params.push(limit, offset);
|
|
|
|
const stmt = db.prepare(query);
|
|
const notifications = stmt.all(...params);
|
|
|
|
return notifications;
|
|
} catch (error) {
|
|
console.error("Failed to get user notifications:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark notifications as read
|
|
* @param {string} userId - User ID
|
|
* @param {Array<number>} [notificationIds] - Specific notification IDs to mark as read (if not provided, marks all as read)
|
|
*/
|
|
export async function markNotificationsAsRead(userId, notificationIds = null) {
|
|
try {
|
|
// Check if we're in Edge Runtime - if so, skip database operations
|
|
if (
|
|
typeof EdgeRuntime !== "undefined" ||
|
|
process.env.NEXT_RUNTIME === "edge"
|
|
) {
|
|
console.log(
|
|
`[Notification - Edge Runtime] Cannot mark notifications as read in Edge Runtime`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Dynamic import to avoid Edge Runtime issues
|
|
const { default: db } = await import("./db.js");
|
|
|
|
let query;
|
|
let params;
|
|
|
|
if (notificationIds && notificationIds.length > 0) {
|
|
// Mark specific notifications as read
|
|
const placeholders = notificationIds.map(() => "?").join(",");
|
|
query = `
|
|
UPDATE notifications
|
|
SET is_read = 1
|
|
WHERE user_id = ? AND id IN (${placeholders})
|
|
`;
|
|
params = [userId, ...notificationIds];
|
|
} else {
|
|
// Mark all notifications as read
|
|
query = `
|
|
UPDATE notifications
|
|
SET is_read = 1
|
|
WHERE user_id = ?
|
|
`;
|
|
params = [userId];
|
|
}
|
|
|
|
const stmt = db.prepare(query);
|
|
stmt.run(...params);
|
|
|
|
console.log(`Marked notifications as read for user ${userId}`);
|
|
} catch (error) {
|
|
console.error("Failed to mark notifications as read:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get unread notification count for a user
|
|
* @param {string} userId - User ID
|
|
* @returns {number} Number of unread notifications
|
|
*/
|
|
export async function getUnreadNotificationCount(userId) {
|
|
try {
|
|
// Check if we're in Edge Runtime - if so, return 0
|
|
if (
|
|
typeof EdgeRuntime !== "undefined" ||
|
|
process.env.NEXT_RUNTIME === "edge"
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
// Dynamic import to avoid Edge Runtime issues
|
|
const { default: db } = await import("./db.js");
|
|
|
|
const stmt = db.prepare(`
|
|
SELECT COUNT(*) as count
|
|
FROM notifications
|
|
WHERE user_id = ? AND is_read = 0
|
|
`);
|
|
|
|
const result = stmt.get(userId);
|
|
return result.count || 0;
|
|
} catch (error) {
|
|
console.error("Failed to get unread notification count:", error);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete old notifications (cleanup function)
|
|
* @param {number} daysOld - Delete notifications older than this many days
|
|
*/
|
|
export async function cleanupOldNotifications(daysOld = 30) {
|
|
try {
|
|
// Check if we're in Edge Runtime - if so, skip database operations
|
|
if (
|
|
typeof EdgeRuntime !== "undefined" ||
|
|
process.env.NEXT_RUNTIME === "edge"
|
|
) {
|
|
console.log(
|
|
`[Notification - Edge Runtime] Cannot cleanup notifications in Edge Runtime`
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Dynamic import to avoid Edge Runtime issues
|
|
const { default: db } = await import("./db.js");
|
|
|
|
const cutoffDate = new Date();
|
|
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
|
|
const cutoffIso = cutoffDate.toISOString();
|
|
|
|
const stmt = db.prepare(`
|
|
DELETE FROM notifications
|
|
WHERE created_at < ? AND is_read = 1
|
|
`);
|
|
|
|
const result = stmt.run(cutoffIso);
|
|
console.log(`Cleaned up ${result.changes} old notifications`);
|
|
} catch (error) {
|
|
console.error("Failed to cleanup old notifications:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create notification from audit event (helper function)
|
|
* @param {Object} auditEvent - Audit event data
|
|
* @param {string} targetUserId - User to notify
|
|
* @param {string} notificationType - Type of notification
|
|
* @param {string} title - Notification title
|
|
* @param {string} message - Notification message
|
|
*/
|
|
export async function createNotificationFromAuditEvent(
|
|
auditEvent,
|
|
targetUserId,
|
|
notificationType,
|
|
title,
|
|
message
|
|
) {
|
|
// Don't notify the user who performed the action
|
|
if (auditEvent.userId === targetUserId) {
|
|
return;
|
|
}
|
|
|
|
await createNotification({
|
|
userId: targetUserId,
|
|
type: notificationType,
|
|
title,
|
|
message,
|
|
resourceType: auditEvent.resourceType,
|
|
resourceId: auditEvent.resourceId,
|
|
actionUrl: getActionUrl(auditEvent.resourceType, auditEvent.resourceId),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Generate action URL for notification
|
|
* @param {string} resourceType - Type of resource
|
|
* @param {string} resourceId - Resource ID
|
|
* @returns {string} Action URL
|
|
*/
|
|
function getActionUrl(resourceType, resourceId) {
|
|
switch (resourceType) {
|
|
case "project":
|
|
return `/projects/${resourceId}`;
|
|
case "project_task":
|
|
return `/project-tasks/${resourceId}`;
|
|
case "task":
|
|
return `/tasks/${resourceId}`;
|
|
case "contract":
|
|
return `/contracts/${resourceId}`;
|
|
default:
|
|
return null;
|
|
}
|
|
} |