Files
ITWorx-Pulse-Public/internal/eventapi/handler.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

87 lines
2.8 KiB
Go

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})
}