115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import logger from '@utils/logger';
|
|
|
|
// Special UUID for system events (login, etc.) - well-known UUID: 00000000-0000-0000-0000-000000000001
|
|
export const SYSTEM_EVENT_REQUEST_ID = '00000000-0000-0000-0000-000000000001';
|
|
|
|
export type ActivityEntry = {
|
|
requestId: string;
|
|
type: 'created' | 'submitted' | 'assignment' | 'approval' | 'rejection' | 'status_change' | 'comment' | 'reminder' | 'document_added' | 'sla_warning' | 'ai_conclusion_generated' | 'summary_generated' | 'closed' | 'login' | 'paused' | 'resumed' | 'pause_retriggered';
|
|
user?: { userId: string; name?: string; email?: string };
|
|
timestamp: string;
|
|
action: string;
|
|
details: string;
|
|
metadata?: any;
|
|
ipAddress?: string;
|
|
userAgent?: string;
|
|
category?: string;
|
|
severity?: string;
|
|
};
|
|
|
|
class ActivityService {
|
|
private byRequest: Map<string, ActivityEntry[]> = new Map();
|
|
|
|
private inferCategory(type: string): string {
|
|
const categoryMap: Record<string, string> = {
|
|
'created': 'WORKFLOW',
|
|
'submitted': 'WORKFLOW',
|
|
'approval': 'WORKFLOW',
|
|
'rejection': 'WORKFLOW',
|
|
'status_change': 'WORKFLOW',
|
|
'assignment': 'WORKFLOW',
|
|
'comment': 'COLLABORATION',
|
|
'document_added': 'DOCUMENT',
|
|
'sla_warning': 'SYSTEM',
|
|
'reminder': 'SYSTEM',
|
|
'ai_conclusion_generated': 'SYSTEM',
|
|
'closed': 'WORKFLOW',
|
|
'login': 'AUTHENTICATION',
|
|
'paused': 'WORKFLOW',
|
|
'resumed': 'WORKFLOW',
|
|
'pause_retriggered': 'WORKFLOW'
|
|
};
|
|
return categoryMap[type] || 'OTHER';
|
|
}
|
|
|
|
private inferSeverity(type: string): string {
|
|
const severityMap: Record<string, string> = {
|
|
'rejection': 'WARNING',
|
|
'sla_warning': 'WARNING',
|
|
'approval': 'INFO',
|
|
'closed': 'INFO',
|
|
'status_change': 'INFO',
|
|
'login': 'INFO',
|
|
'created': 'INFO',
|
|
'submitted': 'INFO',
|
|
'comment': 'INFO',
|
|
'document_added': 'INFO',
|
|
'assignment': 'INFO',
|
|
'reminder': 'INFO',
|
|
'ai_conclusion_generated': 'INFO',
|
|
'paused': 'WARNING',
|
|
'resumed': 'INFO',
|
|
'pause_retriggered': 'INFO'
|
|
};
|
|
return severityMap[type] || 'INFO';
|
|
}
|
|
|
|
async log(entry: ActivityEntry) {
|
|
const list = this.byRequest.get(entry.requestId) || [];
|
|
list.push(entry);
|
|
this.byRequest.set(entry.requestId, list);
|
|
|
|
// Persist to database
|
|
try {
|
|
const { Activity } = require('@models/Activity');
|
|
const userName = entry.user?.name || entry.user?.email || null;
|
|
|
|
const activityData = {
|
|
requestId: entry.requestId,
|
|
userId: entry.user?.userId || null,
|
|
userName: userName,
|
|
activityType: entry.type,
|
|
activityDescription: entry.details,
|
|
activityCategory: entry.category || this.inferCategory(entry.type),
|
|
severity: entry.severity || this.inferSeverity(entry.type),
|
|
metadata: entry.metadata || null,
|
|
isSystemEvent: !entry.user,
|
|
ipAddress: entry.ipAddress || null, // Database accepts null
|
|
userAgent: entry.userAgent || null, // Database accepts null
|
|
};
|
|
|
|
logger.info(`[Activity] Creating activity:`, {
|
|
requestId: entry.requestId,
|
|
userName,
|
|
userId: entry.user?.userId,
|
|
type: entry.type,
|
|
ipAddress: entry.ipAddress ? '***' : null
|
|
});
|
|
|
|
await Activity.create(activityData);
|
|
|
|
logger.info(`[Activity] Successfully logged activity for request ${entry.requestId} by user: ${userName}`);
|
|
} catch (error) {
|
|
logger.error('[Activity] Failed to persist activity:', error);
|
|
}
|
|
}
|
|
|
|
get(requestId: string): ActivityEntry[] {
|
|
return this.byRequest.get(requestId) || [];
|
|
}
|
|
}
|
|
|
|
export const activityService = new ActivityService();
|
|
|
|
|