/** * Audit Log Controller * * Provides endpoints for querying audit logs */ import { Controller, Get, Param, Query, UseGuards, ParseIntPipe, DefaultValuePipe, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; import { AuditService } from '../services/audit.service'; import { JwtAuthGuard } from '../guards/jwt-auth.guard'; import { RolesGuard } from '../guards/roles.guard'; import { Roles } from '../decorators/roles.decorator'; import { CurrentUser, UserPayload } from '../decorators/current-user.decorator'; import { AuditLog, AuditAction, AuditStatus } from '../../domain/entities/audit-log.entity'; class AuditLogResponseDto { id: string; action: string; status: string; userId: string; userEmail: string; organizationId: string; resourceType?: string; resourceId?: string; resourceName?: string; metadata?: Record; ipAddress?: string; userAgent?: string; errorMessage?: string; timestamp: string; } class AuditLogQueryDto { userId?: string; action?: AuditAction[]; status?: AuditStatus[]; resourceType?: string; resourceId?: string; startDate?: string; endDate?: string; page?: number; limit?: number; } @ApiTags('Audit Logs') @ApiBearerAuth() @Controller('api/v1/audit-logs') @UseGuards(JwtAuthGuard, RolesGuard) export class AuditController { constructor(private readonly auditService: AuditService) {} /** * Get audit logs with filters * Only admins and managers can view audit logs */ @Get() @Roles('admin', 'manager') @ApiOperation({ summary: 'Get audit logs with filters' }) @ApiResponse({ status: 200, description: 'Audit logs retrieved successfully' }) @ApiQuery({ name: 'userId', required: false, description: 'Filter by user ID' }) @ApiQuery({ name: 'action', required: false, description: 'Filter by action (comma-separated)', isArray: true }) @ApiQuery({ name: 'status', required: false, description: 'Filter by status (comma-separated)', isArray: true }) @ApiQuery({ name: 'resourceType', required: false, description: 'Filter by resource type' }) @ApiQuery({ name: 'resourceId', required: false, description: 'Filter by resource ID' }) @ApiQuery({ name: 'startDate', required: false, description: 'Filter by start date (ISO 8601)' }) @ApiQuery({ name: 'endDate', required: false, description: 'Filter by end date (ISO 8601)' }) @ApiQuery({ name: 'page', required: false, description: 'Page number (default: 1)' }) @ApiQuery({ name: 'limit', required: false, description: 'Items per page (default: 50)' }) async getAuditLogs( @CurrentUser() user: UserPayload, @Query('userId') userId?: string, @Query('action') action?: string, @Query('status') status?: string, @Query('resourceType') resourceType?: string, @Query('resourceId') resourceId?: string, @Query('startDate') startDate?: string, @Query('endDate') endDate?: string, @Query('page', new DefaultValuePipe(1), ParseIntPipe) page?: number, @Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit?: number, ): Promise<{ logs: AuditLogResponseDto[]; total: number; page: number; pageSize: number }> { page = page || 1; limit = limit || 50; const filters: any = { organizationId: user.organizationId, userId, action: action ? action.split(',') : undefined, status: status ? status.split(',') : undefined, resourceType, resourceId, startDate: startDate ? new Date(startDate) : undefined, endDate: endDate ? new Date(endDate) : undefined, offset: (page - 1) * limit, limit, }; const { logs, total } = await this.auditService.getAuditLogs(filters); return { logs: logs.map((log) => this.mapToDto(log)), total, page, pageSize: limit, }; } /** * Get specific audit log by ID */ @Get(':id') @Roles('admin', 'manager') @ApiOperation({ summary: 'Get audit log by ID' }) @ApiResponse({ status: 200, description: 'Audit log retrieved successfully' }) @ApiResponse({ status: 404, description: 'Audit log not found' }) async getAuditLogById( @Param('id') id: string, @CurrentUser() user: UserPayload, ): Promise { const log = await this.auditService.getAuditLogs({ organizationId: user.organizationId, limit: 1, }); if (!log.logs.length) { throw new Error('Audit log not found'); } return this.mapToDto(log.logs[0]); } /** * Get audit trail for a specific resource */ @Get('resource/:type/:id') @Roles('admin', 'manager', 'user') @ApiOperation({ summary: 'Get audit trail for a specific resource' }) @ApiResponse({ status: 200, description: 'Audit trail retrieved successfully' }) async getResourceAuditTrail( @Param('type') resourceType: string, @Param('id') resourceId: string, @CurrentUser() user: UserPayload, ): Promise { const logs = await this.auditService.getResourceAuditTrail(resourceType, resourceId); // Filter by organization for security const filteredLogs = logs.filter((log) => log.organizationId === user.organizationId); return filteredLogs.map((log) => this.mapToDto(log)); } /** * Get recent activity for current organization */ @Get('organization/activity') @Roles('admin', 'manager') @ApiOperation({ summary: 'Get recent organization activity' }) @ApiResponse({ status: 200, description: 'Organization activity retrieved successfully' }) @ApiQuery({ name: 'limit', required: false, description: 'Number of recent logs (default: 50)' }) async getOrganizationActivity( @CurrentUser() user: UserPayload, @Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit?: number, ): Promise { limit = limit || 50; const logs = await this.auditService.getOrganizationActivity(user.organizationId, limit); return logs.map((log) => this.mapToDto(log)); } /** * Get user activity history */ @Get('user/:userId/activity') @Roles('admin', 'manager') @ApiOperation({ summary: 'Get user activity history' }) @ApiResponse({ status: 200, description: 'User activity retrieved successfully' }) @ApiQuery({ name: 'limit', required: false, description: 'Number of recent logs (default: 50)' }) async getUserActivity( @CurrentUser() user: UserPayload, @Param('userId') userId: string, @Query('limit', new DefaultValuePipe(50), ParseIntPipe) limit?: number, ): Promise { limit = limit || 50; const logs = await this.auditService.getUserActivity(userId, limit); // Filter by organization for security const filteredLogs = logs.filter((log) => log.organizationId === user.organizationId); return filteredLogs.map((log) => this.mapToDto(log)); } /** * Map domain entity to DTO */ private mapToDto(log: AuditLog): AuditLogResponseDto { return { id: log.id, action: log.action, status: log.status, userId: log.userId, userEmail: log.userEmail, organizationId: log.organizationId, resourceType: log.resourceType, resourceId: log.resourceId, resourceName: log.resourceName, metadata: log.metadata, ipAddress: log.ipAddress, userAgent: log.userAgent, errorMessage: log.errorMessage, timestamp: log.timestamp.toISOString(), }; } }