package handlers import ( "fmt" "strconv" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "github.com/trackeep/backend/config" "github.com/trackeep/backend/models" ) // GetAuditLogs retrieves audit logs with filtering and pagination func GetAuditLogs(c *gin.Context) { user, exists := c.Get("user") if !exists { c.JSON(401, gin.H{"error": "User not authenticated"}) return } currentUser := user.(models.User) db := config.GetDB() // Parse query parameters page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) action := c.Query("action") resource := c.Query("resource") userID := c.Query("user_id") startDate := c.Query("start_date") endDate := c.Query("end_date") riskLevel := c.Query("risk_level") success := c.Query("success") // Build query query := db.Model(&models.AuditLog{}) // Non-admin users can only see their own logs if currentUser.Role != "admin" { query = query.Where("user_id = ?", currentUser.ID) } else if userID != "" { // Admin can filter by specific user if uid, err := strconv.ParseUint(userID, 10, 32); err == nil { query = query.Where("user_id = ?", uid) } } // Apply filters if action != "" { query = query.Where("action = ?", action) } if resource != "" { query = query.Where("resource = ?", resource) } if riskLevel != "" { query = query.Where("risk_level = ?", riskLevel) } if success != "" { query = query.Where("success = ?", success == "true") } if startDate != "" { if start, err := time.Parse("2006-01-02", startDate); err == nil { query = query.Where("created_at >= ?", start) } } if endDate != "" { if end, err := time.Parse("2006-01-02", endDate); err == nil { query = query.Where("created_at <= ?", end.Add(24*time.Hour)) } } // Count total records var total int64 query.Count(&total) // Get paginated results offset := (page - 1) * limit var logs []models.AuditLog query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&logs) c.JSON(200, gin.H{ "logs": logs, "pagination": gin.H{ "page": page, "limit": limit, "total": total, "pages": (total + int64(limit) - 1) / int64(limit), }, }) } // GetAuditLogStats retrieves audit log statistics func GetAuditLogStats(c *gin.Context) { user, exists := c.Get("user") if !exists { c.JSON(401, gin.H{"error": "User not authenticated"}) return } currentUser := user.(models.User) db := config.GetDB() // Parse date range startDate := c.DefaultQuery("start_date", time.Now().AddDate(0, -1, 0).Format("2006-01-02")) endDate := c.DefaultQuery("end_date", time.Now().Format("2006-01-02")) start, _ := time.Parse("2006-01-02", startDate) end, _ := time.Parse("2006-01-02", endDate) end = end.Add(24 * time.Hour) // Include the entire end date // Base query baseQuery := db.Model(&models.AuditLog{}).Where("created_at >= ? AND created_at <= ?", start, end) // Non-admin users can only see their own stats if currentUser.Role != "admin" { baseQuery = baseQuery.Where("user_id = ?", currentUser.ID) } // Get overall stats var totalLogs, successLogs, failedLogs, suspiciousLogs int64 baseQuery.Count(&totalLogs) baseQuery.Where("success = ?", true).Count(&successLogs) baseQuery.Where("success = ?", false).Count(&failedLogs) baseQuery.Where("suspicious = ?", true).Count(&suspiciousLogs) // Get action breakdown type ActionStat struct { Action string `json:"action"` Count int64 `json:"count"` } var actionStats []ActionStat baseQuery.Select("action, COUNT(*) as count").Group("action").Order("count DESC").Scan(&actionStats) // Get resource breakdown type ResourceStat struct { Resource string `json:"resource"` Count int64 `json:"count"` } var resourceStats []ResourceStat baseQuery.Select("resource, COUNT(*) as count").Group("resource").Order("count DESC").Scan(&resourceStats) // Get risk level breakdown type RiskStat struct { RiskLevel string `json:"risk_level"` Count int64 `json:"count"` } var riskStats []RiskStat baseQuery.Select("risk_level, COUNT(*) as count").Group("risk_level").Order("count DESC").Scan(&riskStats) // Get daily activity for the last 30 days type DailyStat struct { Date string `json:"date"` Count int64 `json:"count"` } var dailyStats []DailyStat dailyQuery := db.Model(&models.AuditLog{}). Select("DATE(created_at) as date, COUNT(*) as count"). Where("created_at >= ? AND created_at <= ?", start, end). Group("DATE(created_at)"). Order("date ASC") if currentUser.Role != "admin" { dailyQuery = dailyQuery.Where("user_id = ?", currentUser.ID) } dailyQuery.Scan(&dailyStats) // Get top users (admin only) var topUsers []struct { UserEmail string `json:"user_email"` Count int64 `json:"count"` } if currentUser.Role == "admin" { baseQuery.Select("user_email, COUNT(*) as count"). Group("user_email"). Order("count DESC"). Limit(10). Scan(&topUsers) } // Get recent security events var securityEvents []models.AuditLog securityQuery := db.Model(&models.AuditLog{}). Where("resource = ? AND created_at >= ? AND created_at <= ?", models.AuditResourceSecurity, start, end). Order("created_at DESC"). Limit(20) if currentUser.Role != "admin" { securityQuery = securityQuery.Where("user_id = ?", currentUser.ID) } securityQuery.Find(&securityEvents) stats := gin.H{ "period": gin.H{ "start_date": startDate, "end_date": endDate, }, "overview": gin.H{ "total_logs": totalLogs, "success_logs": successLogs, "failed_logs": failedLogs, "suspicious_logs": suspiciousLogs, "success_rate": float64(successLogs) / float64(totalLogs) * 100, }, "actions": actionStats, "resources": resourceStats, "risk_levels": riskStats, "daily_activity": dailyStats, "security_events": securityEvents, } if currentUser.Role == "admin" { stats["top_users"] = topUsers } c.JSON(200, stats) } // GetAuditLog retrieves a specific audit log entry func GetAuditLog(c *gin.Context) { user, exists := c.Get("user") if !exists { c.JSON(401, gin.H{"error": "User not authenticated"}) return } currentUser := user.(models.User) logID := c.Param("id") db := config.GetDB() var log models.AuditLog query := db.Where("id = ?", logID) // Non-admin users can only see their own logs if currentUser.Role != "admin" { query = query.Where("user_id = ?", currentUser.ID) } if err := query.First(&log).Error; err != nil { if err == gorm.ErrRecordNotFound { c.JSON(404, gin.H{"error": "Audit log not found"}) return } c.JSON(500, gin.H{"error": "Database error"}) return } c.JSON(200, gin.H{"log": log}) } // ExportAuditLogs exports audit logs in various formats func ExportAuditLogs(c *gin.Context) { user, exists := c.Get("user") if !exists { c.JSON(401, gin.H{"error": "User not authenticated"}) return } currentUser := user.(models.User) format := c.DefaultQuery("format", "json") // json, csv, xlsx // Only admin can export logs if currentUser.Role != "admin" { c.JSON(403, gin.H{"error": "Admin access required"}) return } db := config.GetDB() // Parse query parameters (same as GetAuditLogs) startDate := c.DefaultQuery("start_date", time.Now().AddDate(0, -1, 0).Format("2006-01-02")) endDate := c.DefaultQuery("end_date", time.Now().Format("2006-01-02")) action := c.Query("action") resource := c.Query("resource") userID := c.Query("user_id") riskLevel := c.Query("risk_level") // Build query query := db.Model(&models.AuditLog{}) if startDate != "" { if start, err := time.Parse("2006-01-02", startDate); err == nil { query = query.Where("created_at >= ?", start) } } if endDate != "" { if end, err := time.Parse("2006-01-02", endDate); err == nil { query = query.Where("created_at <= ?", end.Add(24*time.Hour)) } } if action != "" { query = query.Where("action = ?", action) } if resource != "" { query = query.Where("resource = ?", resource) } if userID != "" { if uid, err := strconv.ParseUint(userID, 10, 32); err == nil { query = query.Where("user_id = ?", uid) } } if riskLevel != "" { query = query.Where("risk_level = ?", riskLevel) } var logs []models.AuditLog query.Order("created_at DESC").Find(&logs) switch format { case "csv": c.Header("Content-Type", "text/csv") c.Header("Content-Disposition", "attachment; filename=audit_logs.csv") // Generate CSV (simplified) c.String(200, generateCSV(logs)) case "xlsx": // For Excel export, you'd need a library like excelize c.JSON(501, gin.H{"error": "Excel export not implemented yet"}) default: c.Header("Content-Type", "application/json") c.Header("Content-Disposition", "attachment; filename=audit_logs.json") c.JSON(200, logs) } } // CleanupAuditLogs removes old audit logs based on retention policy func CleanupAuditLogs(c *gin.Context) { user, exists := c.Get("user") if !exists { c.JSON(401, gin.H{"error": "User not authenticated"}) return } currentUser := user.(models.User) // Only admin can cleanup logs if currentUser.Role != "admin" { c.JSON(403, gin.H{"error": "Admin access required"}) return } // Parse retention period (default 90 days) retentionDays, _ := strconv.Atoi(c.DefaultQuery("retention_days", "90")) cutoffDate := time.Now().AddDate(0, 0, -retentionDays) db := config.GetDB() // Delete old logs result := db.Where("created_at < ?", cutoffDate).Delete(&models.AuditLog{}) c.JSON(200, gin.H{ "message": "Audit logs cleanup completed", "deleted_count": result.RowsAffected, "retention_days": retentionDays, "cutoff_date": cutoffDate, }) } // Helper function to generate CSV (simplified implementation) func generateCSV(logs []models.AuditLog) string { var csv string csv = "ID,User Email,Action,Resource,Resource ID,Description,Success,Risk Level,Created At\n" for _, log := range logs { csv += fmt.Sprintf("%d,%s,%s,%s,%v,%s,%v,%s,%s\n", log.ID, log.UserEmail, log.Action, log.Resource, log.ResourceID, log.Description, log.Success, log.RiskLevel, log.CreatedAt.Format("2006-01-02 15:04:05"), ) } return csv }