package handler import ( "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/google/uuid" "go.uber.org/zap" "github.com/dbiz/cdp/data-layer/api/internal/apperr" "github.com/dbiz/cdp/data-layer/api/internal/middleware" "github.com/dbiz/cdp/data-layer/api/internal/service" ) type ProfileHandler struct { svc *service.ProfileService log *zap.Logger } func NewProfileHandler(svc *service.ProfileService, log *zap.Logger) *ProfileHandler { return &ProfileHandler{svc: svc, log: log} } // Get handles GET /profiles/:id. func (h *ProfileHandler) Get(w http.ResponseWriter, r *http.Request) { id, err := parseProfileID(r) if err != nil { writeError(w, err) return } ws := middleware.WorkspaceFromCtx(r.Context()) p, err := h.svc.Get(r.Context(), ws, id) if err != nil { writeError(w, err) return } writeJSON(w, http.StatusOK, p) } // Timeline handles GET /profiles/:id/events. func (h *ProfileHandler) Timeline(w http.ResponseWriter, r *http.Request) { id, err := parseProfileID(r) if err != nil { writeError(w, err) return } limit, offset := parsePagination(r, 100, 1000) ws := middleware.WorkspaceFromCtx(r.Context()) res, err := h.svc.Timeline(r.Context(), ws, id, limit, offset) if err != nil { writeError(w, err) return } writeJSON(w, http.StatusOK, res) } func parseProfileID(r *http.Request) (string, error) { raw := chi.URLParam(r, "id") if raw == "" { return "", apperr.BadRequest("missing profile id", "id", nil) } if _, err := uuid.Parse(raw); err != nil { return "", apperr.BadRequest("profile id must be uuid", "id", err) } return raw, nil } // parsePagination reads ?limit & ?offset with bounds. Invalid values fall back // to the defaults rather than erroring -- the endpoints are GET, not strict. func parsePagination(r *http.Request, def, max int) (limit, offset int) { limit = def if v := r.URL.Query().Get("limit"); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= max { limit = n } } if v := r.URL.Query().Get("offset"); v != "" { if n, err := strconv.Atoi(v); err == nil && n >= 0 { offset = n } } return }