package useCases import ( "RenewCMS/internal/domain/audit" "time" ) const ( activityRangeToday = "today" activityRange7Days = "7d" activityRange30Days = "30d" activityRangeAll = "all" activityRangeCustom = "custom" activityDateLayout = "2006-01-02" activityDefaultSize = 25 outcomeAll = "all" securityOnly = "true" ) var activityPageSizes = []int{5, 25, 50, 100} type ActivityQuery struct { Q string Actor string Category string Outcome string Security string Range string From string To string Page int Size int } type activityWindow struct { from time.Time to time.Time explicit bool } func resolveActivityWindow(query ActivityQuery, now time.Time) (activityWindow, error) { utc := now.UTC() end := utc.Add(time.Second) switch query.Range { case "", activityRange7Days: return activityWindow{from: end.AddDate(0, 0, -7), to: end}, nil case activityRange30Days: return activityWindow{from: end.AddDate(0, 0, -30), to: end}, nil case activityRangeToday: return activityWindow{from: time.Date(utc.Year(), utc.Month(), utc.Day(), 0, 0, 0, 0, time.UTC), to: end}, nil case activityRangeAll: return activityWindow{from: time.Unix(0, 0).UTC(), to: end}, nil case activityRangeCustom: return resolveCustomWindow(query) default: return activityWindow{}, audit.ErrInvalidFilter } } func resolveCustomWindow(query ActivityQuery) (activityWindow, error) { from, err := time.Parse(activityDateLayout, query.From) if err != nil { return activityWindow{}, audit.ErrInvalidFilter } last, err := time.Parse(activityDateLayout, query.To) if err != nil || last.Before(from) { return activityWindow{}, audit.ErrInvalidFilter } return activityWindow{from: from, to: last.AddDate(0, 0, 1), explicit: true}, nil } func buildActivityFilter(query ActivityQuery, now time.Time) (audit.Filter, error) { window, err := resolveActivityWindow(query, now) if err != nil { return audit.Filter{}, err } filter := audit.Filter{Query: query.Q, From: &window.from} if window.explicit { filter.To = &window.to } if filter.Outcome, err = parseActivityOutcome(query.Outcome); err != nil { return audit.Filter{}, err } if filter.Security, err = parseActivitySecurity(query.Security); err != nil { return audit.Filter{}, err } if query.Category != "" { if filter.Category, err = audit.ParseCategory(query.Category); err != nil { return audit.Filter{}, err } } if query.Actor != "" { actor, err := audit.ParseActor(query.Actor) if err != nil { return audit.Filter{}, err } filter.Actor = &actor } return filter, nil } func parseActivityOutcome(raw string) (audit.Outcome, error) { switch raw { case "", outcomeAll: return "", nil case string(audit.OutcomeSuccess): return audit.OutcomeSuccess, nil case string(audit.OutcomeFailed): return audit.OutcomeFailed, nil default: return "", audit.ErrInvalidFilter } } func parseActivitySecurity(raw string) (bool, error) { switch raw { case "": return false, nil case securityOnly: return true, nil default: return false, audit.ErrInvalidFilter } } func resolveActivityPaging(query ActivityQuery) (int, int, error) { page, size := query.Page, query.Size if page == 0 { page = 1 } if size == 0 { size = activityDefaultSize } if page < 1 || !containsInt(activityPageSizes, size) { return 0, 0, audit.ErrInvalidFilter } return page, size, nil } func containsInt(values []int, wanted int) bool { for _, value := range values { if value == wanted { return true } } return false }