package useCases import ( "RenewCMS/internal/domain/audit" "encoding/csv" "io" "strings" "time" ) const exportBatchSize = 500 const formulaGuard = "'" const formulaStarts = "=+-@" var exportHeader = []string{"when", "actor", "action", "target", "outcome", "source", "ip"} type ExportActivityUseCase struct { events audit.Repository now func() time.Time } type ActivityExport struct { events audit.Repository filter audit.Filter } func NewExportActivityUseCase(events audit.Repository, now func() time.Time) *ExportActivityUseCase { return &ExportActivityUseCase{events, now} } func (e *ExportActivityUseCase) Prepare(query ActivityQuery) (*ActivityExport, error) { filter, err := buildActivityFilter(query, e.now()) if err != nil { return nil, err } return &ActivityExport{events: e.events, filter: filter}, nil } func (a *ActivityExport) WriteCSV(w io.Writer) error { writer := csv.NewWriter(w) if err := writer.Write(exportHeader); err != nil { return err } for page := 1; ; page++ { filter := a.filter filter.Page, filter.Size = page, exportBatchSize found, err := a.events.List(filter) if err != nil { return err } for _, event := range found.Items { if err := writer.Write(exportRow(event)); err != nil { return err } } writer.Flush() if err := writer.Error(); err != nil { return err } if len(found.Items) < exportBatchSize || page*exportBatchSize >= found.Total { return nil } } } func exportRow(event audit.Event) []string { ip := "" if event.IP != nil { ip = *event.IP } return []string{ event.CreatedAt.UTC().Format(time.RFC3339), guardCell(event.Actor.Name), guardCell(event.Action), guardCell(event.Target.Name), string(event.Outcome), string(event.Source), guardCell(ip), } } func guardCell(value string) string { if value != "" && strings.ContainsRune(formulaStarts, rune(value[0])) { return formulaGuard + value } return value }