This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user