package eventapi import ( "context" "encoding/json" "errors" "net/http" "strconv" "time" "github.com/itworx/pulse/internal/auth" "github.com/itworx/pulse/internal/problem" "github.com/jackc/pgx/v5/pgxpool" ) type Event struct { ID string `json:"id"` Type string `json:"type"` Severity string `json:"severity"` EntityID *string `json:"entityId,omitempty"` SourceID *string `json:"sourceId,omitempty"` OccurredAt time.Time `json:"occurredAt"` ReceivedAt time.Time `json:"receivedAt"` Summary string `json:"summary"` Attributes json.RawMessage `json:"attributes"` CorrelationID *string `json:"correlationId,omitempty"` } type Store interface { List(context.Context, int) ([]Event, error) } type PostgresStore struct{ Pool *pgxpool.Pool } func (s PostgresStore) List(ctx context.Context, limit int) ([]Event, error) { if s.Pool == nil { return nil, errors.New("event database pool is nil") } rows, err := s.Pool.Query(ctx, `SELECT id::text,event_type,severity,entity_id::text,source_id::text,occurred_at,received_at,summary,attributes,correlation_id FROM events ORDER BY occurred_at DESC,id DESC LIMIT $1`, limit) if err != nil { return nil, err } defer rows.Close() items := make([]Event, 0, limit) for rows.Next() { var item Event if err := rows.Scan(&item.ID, &item.Type, &item.Severity, &item.EntityID, &item.SourceID, &item.OccurredAt, &item.ReceivedAt, &item.Summary, &item.Attributes, &item.CorrelationID); err != nil { return nil, err } items = append(items, item) } return items, rows.Err() } type Handler struct{ Store Store } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet || r.URL.Path != "/api/v1/events" { http.NotFound(w, r) return } if _, ok := auth.PrincipalFromContext(r.Context()); !ok { problem.Write(w, r, http.StatusUnauthorized, "UNAUTHORIZED", "Authentication required", "Authentication is required to read events.", nil) return } limit := 100 if raw := r.URL.Query().Get("limit"); raw != "" { value, err := strconv.Atoi(raw) if err != nil || value < 1 || value > 100 { problem.Write(w, r, http.StatusBadRequest, "INVALID_LIMIT", "Invalid limit", "The event limit must be between 1 and 100.", nil) return } limit = value } items, err := h.Store.List(r.Context(), limit) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return } problem.Write(w, r, http.StatusServiceUnavailable, "EVENTS_UNAVAILABLE", "Events unavailable", "Gebeurtenissen konden niet worden gelezen.", nil) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "private, max-age=5") _ = json.NewEncoder(w).Encode(map[string]any{"items": items}) }