package mysql import ( "context" "fmt" "strings" "gorm.io/gorm" "wucher/internal/domain/audit" ) type AuditRepository struct { db *gorm.DB } func NewAuditRepository(db *gorm.DB) *AuditRepository { return &AuditRepository{db: db} } func (r *AuditRepository) Create(ctx context.Context, entry *audit.AuditLog) error { return r.db.WithContext(ctx).Create(entry).Error } func (r *AuditRepository) List(ctx context.Context, filter string, methods []string, modules []string, actions []string, sort string, limit, offset int) ([]audit.AuditLog, int64, error) { q := r.db.WithContext(ctx).Model(&audit.AuditLog{}) filter = strings.TrimSpace(filter) if filter != "" { like := "%" + strings.ToLower(filter) + "%" q = q.Where( `LOWER(request_id) LIKE ? OR LOWER(layer) LIKE ? OR LOWER(action) LIKE ? OR LOWER(method) LIKE ? OR LOWER(path) LIKE ? OR LOWER(message) LIKE ?`, like, like, like, like, like, like, ) } if len(methods) > 0 { q = q.Where("UPPER(method) IN ?", methods) } if len(modules) > 0 { pathPrefixes := modulePathPrefixes(modules) if len(pathPrefixes) == 0 { q = q.Where("LOWER(module) IN ?", modules) } else { pathConds := make([]string, 0, len(pathPrefixes)) pathArgs := make([]any, 0, len(pathPrefixes)) for _, prefix := range pathPrefixes { pathConds = append(pathConds, "LOWER(path) LIKE ?") pathArgs = append(pathArgs, strings.ToLower(prefix)+"%") } cond := fmt.Sprintf("(LOWER(module) IN ? OR ((module IS NULL OR module = '') AND (%s)))", strings.Join(pathConds, " OR ")) args := make([]any, 0, 2+len(pathArgs)) args = append(args, modules) args = append(args, pathArgs...) q = q.Where(cond, args...) } } if len(actions) > 0 { q = q.Where("LOWER(action) IN ?", actions) } var total int64 if err := q.Count(&total).Error; err != nil { return nil, 0, err } if strings.TrimSpace(sort) == "" { sort = "created_at DESC" } if limit < 0 { limit = 0 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } var out []audit.AuditLog query := q.Order(sort) if limit > 0 { query = query.Limit(limit).Offset(offset) } if err := query.Find(&out).Error; err != nil { return nil, 0, err } return out, total, nil } func modulePathPrefixes(modules []string) []string { seen := map[string]struct{}{} out := make([]string, 0, len(modules)) for _, module := range modules { var prefix string switch strings.ToLower(strings.TrimSpace(module)) { case "air_rescuer_checklist": prefix = "/api/v1/air-rescuer-checklist/" case "audit_log": prefix = "/api/v1/audit-logs/" case "auth": prefix = "/api/v1/auth/" case "base": prefix = "/api/v1/bases/" case "branding": prefix = "/api/v1/branding/" case "contact": prefix = "/api/v1/contacts/" case "duty_roster": prefix = "/api/v1/duty-roster/" case "dul": prefix = "/api/v1/dul/" case "facility": prefix = "/api/v1/facilities/" case "federal_state": prefix = "/api/v1/federal-state/" case "file_manager": prefix = "/api/v1/file-manager/" case "flight_data": prefix = "/api/v1/flight-data/" case "flight": prefix = "/api/v1/flights/" case "forces_present": prefix = "/api/v1/forces-present/" case "health_insurance_company": prefix = "/api/v1/health-insurance-companies/" case "helicopter_file": prefix = "/api/v1/helicopter-files/" case "helicopter": prefix = "/api/v1/helicopters/" case "hospital": prefix = "/api/v1/hospital/" case "icao": prefix = "/api/v1/icao/" case "insurance_patient_data": prefix = "/api/v1/insurance-patient-data/" case "land": prefix = "/api/v1/land/" case "master_setting": prefix = "/api/v1/master-settings/" case "medicine": prefix = "/api/v1/medicine/" case "mission": prefix = "/api/v1/mission/" case "opc": prefix = "/api/v1/opc/" case "patient_data": prefix = "/api/v1/patient-data/" case "reserve_ac": prefix = "/api/v1/reserve-acs/" case "role": prefix = "/api/v1/roles/" case "user": prefix = "/api/v1/users/" case "vocation": prefix = "/api/v1/vocation/" } if prefix == "" { continue } if _, ok := seen[prefix]; ok { continue } seen[prefix] = struct{}{} out = append(out, prefix) } return out }