package postgres import ( "context" "encoding/json" "time" "botpontos/internal/domain" ) func (s *Store) InsertAudit(ctx context.Context, e domain.AuditEntry) error { details := e.Details if details == nil { details = map[string]any{} } raw, err := json.Marshal(details) if err != nil { return err } _, err = s.pool.Exec(ctx, ` INSERT INTO audit_logs (source, actor_user_id, actor_account, actor_account_name, target_user_id, event_type, success, details) VALUES ($1::text::audit_source, $2, $3, (SELECT username::text FROM web_accounts WHERE id = $3), $4, $5, $6, $7::text::jsonb)`, string(e.Source), e.ActorUserID, e.ActorAccount, e.TargetUserID, e.EventType, e.Success, string(raw)) return err } type AuditFilter struct { From *time.Time To *time.Time Source *string EventType *string Success *bool TargetUserID *int64 ActorAccount *int64 Before *int64 // cursor: id do último item da página anterior Limit int } func (s *Store) ListAudit(ctx context.Context, f AuditFilter) ([]domain.AuditLog, error) { if f.Limit <= 0 || f.Limit > 200 { f.Limit = 50 } rows, err := s.pool.Query(ctx, ` SELECT a.id, a.occurred_at, a.source, a.actor_user_id, au.display_name, a.actor_account, COALESCE(wa.username::text, a.actor_account_name), a.target_user_id, tu.display_name, a.event_type, a.success, a.details::text FROM audit_logs a LEFT JOIN users au ON au.id = a.actor_user_id LEFT JOIN web_accounts wa ON wa.id = a.actor_account LEFT JOIN users tu ON tu.id = a.target_user_id WHERE ($1::timestamptz IS NULL OR a.occurred_at >= $1) AND ($2::timestamptz IS NULL OR a.occurred_at < $2) AND ($3::text IS NULL OR a.source::text = $3) AND ($4::text IS NULL OR a.event_type = $4) AND ($5::boolean IS NULL OR a.success = $5) AND ($6::bigint IS NULL OR a.target_user_id = $6) AND ($7::bigint IS NULL OR a.actor_account = $7) AND ($8::bigint IS NULL OR a.id < $8) ORDER BY a.id DESC LIMIT $9`, f.From, f.To, f.Source, f.EventType, f.Success, f.TargetUserID, f.ActorAccount, f.Before, f.Limit) return collect(rows, err, func(row scanner) (domain.AuditLog, error) { var l domain.AuditLog var source, details string if err := row.Scan(&l.ID, &l.OccurredAt, &source, &l.ActorUserID, &l.ActorUserName, &l.ActorAccount, &l.ActorUsername, &l.TargetUserID, &l.TargetUserName, &l.EventType, &l.Success, &details); err != nil { return l, err } l.Source = domain.AuditSource(source) l.Details = map[string]any{} _ = json.Unmarshal([]byte(details), &l.Details) return l, nil }) } func (s *Store) DeleteAuditBefore(ctx context.Context, before time.Time) (int64, error) { tag, err := s.pool.Exec(ctx, `DELETE FROM audit_logs WHERE occurred_at < $1`, before) return tag.RowsAffected(), err }