import { logApiAction, AUDIT_ACTIONS, RESOURCE_TYPES } from "@/lib/auditLog.js"; /** * Higher-order function to add audit logging to API routes * @param {Function} handler - The original API route handler * @param {Object} auditConfig - Audit logging configuration * @param {string} auditConfig.action - The audit action to log * @param {string} auditConfig.resourceType - The resource type being accessed * @param {Function} [auditConfig.getResourceId] - Function to extract resource ID from request/params * @param {Function} [auditConfig.getAdditionalDetails] - Function to get additional details to log * @returns {Function} Wrapped handler with audit logging */ export function withAuditLog(handler, auditConfig) { return async (request, context) => { try { // Execute the original handler first const response = await handler(request, context); // Extract resource ID if function provided let resourceId = null; if (auditConfig.getResourceId) { resourceId = auditConfig.getResourceId(request, context, response); } else if (context?.params?.id) { resourceId = context.params.id; } // Get additional details if function provided let additionalDetails = {}; if (auditConfig.getAdditionalDetails) { additionalDetails = auditConfig.getAdditionalDetails( request, context, response ); } // Log the action logApiAction( request, auditConfig.action, auditConfig.resourceType, resourceId, request.session, additionalDetails ); return response; } catch (error) { // Log failed actions const resourceId = auditConfig.getResourceId ? auditConfig.getResourceId(request, context, null) : context?.params?.id || null; logApiAction( request, `${auditConfig.action}_failed`, auditConfig.resourceType, resourceId, request.session, { error: error.message, ...(auditConfig.getAdditionalDetails ? auditConfig.getAdditionalDetails(request, context, null) : {}), } ); // Re-throw the error throw error; } }; } /** * Predefined audit configurations for common actions */ export const AUDIT_CONFIGS = { // Project actions PROJECT_VIEW: { action: AUDIT_ACTIONS.PROJECT_VIEW, resourceType: RESOURCE_TYPES.PROJECT, }, PROJECT_CREATE: { action: AUDIT_ACTIONS.PROJECT_CREATE, resourceType: RESOURCE_TYPES.PROJECT, getResourceId: (req, ctx, res) => res?.json?.projectId?.toString(), getAdditionalDetails: async (req) => { const data = await req.json(); return { projectData: data }; }, }, PROJECT_UPDATE: { action: AUDIT_ACTIONS.PROJECT_UPDATE, resourceType: RESOURCE_TYPES.PROJECT, getAdditionalDetails: async (req) => { const data = await req.json(); return { updatedData: data }; }, }, PROJECT_DELETE: { action: AUDIT_ACTIONS.PROJECT_DELETE, resourceType: RESOURCE_TYPES.PROJECT, }, // Task actions TASK_VIEW: { action: AUDIT_ACTIONS.TASK_VIEW, resourceType: RESOURCE_TYPES.TASK, }, TASK_CREATE: { action: AUDIT_ACTIONS.TASK_CREATE, resourceType: RESOURCE_TYPES.TASK, getAdditionalDetails: async (req) => { const data = await req.json(); return { taskData: data }; }, }, TASK_UPDATE: { action: AUDIT_ACTIONS.TASK_UPDATE, resourceType: RESOURCE_TYPES.TASK, getAdditionalDetails: async (req) => { const data = await req.json(); return { updatedData: data }; }, }, TASK_DELETE: { action: AUDIT_ACTIONS.TASK_DELETE, resourceType: RESOURCE_TYPES.TASK, }, // Project Task actions PROJECT_TASK_VIEW: { action: AUDIT_ACTIONS.PROJECT_TASK_VIEW, resourceType: RESOURCE_TYPES.PROJECT_TASK, }, PROJECT_TASK_CREATE: { action: AUDIT_ACTIONS.PROJECT_TASK_CREATE, resourceType: RESOURCE_TYPES.PROJECT_TASK, getAdditionalDetails: async (req) => { const data = await req.json(); return { taskData: data }; }, }, PROJECT_TASK_UPDATE: { action: AUDIT_ACTIONS.PROJECT_TASK_UPDATE, resourceType: RESOURCE_TYPES.PROJECT_TASK, getAdditionalDetails: async (req) => { const data = await req.json(); return { updatedData: data }; }, }, PROJECT_TASK_DELETE: { action: AUDIT_ACTIONS.PROJECT_TASK_DELETE, resourceType: RESOURCE_TYPES.PROJECT_TASK, }, // Contract actions CONTRACT_VIEW: { action: AUDIT_ACTIONS.CONTRACT_VIEW, resourceType: RESOURCE_TYPES.CONTRACT, }, CONTRACT_CREATE: { action: AUDIT_ACTIONS.CONTRACT_CREATE, resourceType: RESOURCE_TYPES.CONTRACT, getAdditionalDetails: async (req) => { const data = await req.json(); return { contractData: data }; }, }, CONTRACT_UPDATE: { action: AUDIT_ACTIONS.CONTRACT_UPDATE, resourceType: RESOURCE_TYPES.CONTRACT, getAdditionalDetails: async (req) => { const data = await req.json(); return { updatedData: data }; }, }, CONTRACT_DELETE: { action: AUDIT_ACTIONS.CONTRACT_DELETE, resourceType: RESOURCE_TYPES.CONTRACT, }, // Note actions NOTE_VIEW: { action: AUDIT_ACTIONS.NOTE_VIEW, resourceType: RESOURCE_TYPES.NOTE, }, NOTE_CREATE: { action: AUDIT_ACTIONS.NOTE_CREATE, resourceType: RESOURCE_TYPES.NOTE, getAdditionalDetails: async (req) => { const data = await req.json(); return { noteData: data }; }, }, NOTE_UPDATE: { action: AUDIT_ACTIONS.NOTE_UPDATE, resourceType: RESOURCE_TYPES.NOTE, getAdditionalDetails: async (req) => { const data = await req.json(); return { updatedData: data }; }, }, NOTE_DELETE: { action: AUDIT_ACTIONS.NOTE_DELETE, resourceType: RESOURCE_TYPES.NOTE, }, }; /** * Utility function to create audit-logged API handlers * @param {Object} handlers - Object with HTTP method handlers * @param {Object} auditConfig - Audit configuration for this route * @returns {Object} Object with audit-logged handlers */ export function createAuditedHandlers(handlers, auditConfig) { const auditedHandlers = {}; Object.entries(handlers).forEach(([method, handler]) => { // Get method-specific audit config or use default const config = auditConfig[method] || auditConfig.default || auditConfig; auditedHandlers[method] = withAuditLog(handler, config); }); return auditedHandlers; } const auditLogMiddleware = { withAuditLog, AUDIT_CONFIGS, createAuditedHandlers, }; export default auditLogMiddleware;