/** * 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} [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; } }