Files
cdp/data-layer/api/internal/handler/analytics_handler.go
2026-05-25 08:38:26 +07:00

133 lines
4.1 KiB
Go

package handler
import (
"net/http"
"time"
"go.uber.org/zap"
"github.com/dbiz/cdp/data-layer/api/internal/middleware"
"github.com/dbiz/cdp/data-layer/api/internal/repo"
"github.com/dbiz/cdp/data-layer/api/internal/service"
)
type AnalyticsHandler struct {
svc *service.QueryService
log *zap.Logger
}
func NewAnalyticsHandler(svc *service.QueryService, log *zap.Logger) *AnalyticsHandler {
return &AnalyticsHandler{svc: svc, log: log}
}
// ---------------------------------------------------------------------------
// Funnel
// ---------------------------------------------------------------------------
type funnelRequest struct {
Steps []string `json:"steps" validate:"required,min=2,max=10,dive,min=1"`
From *time.Time `json:"from" validate:"required"`
To *time.Time `json:"to" validate:"required,gtfield=From"`
WindowSeconds uint32 `json:"window_seconds" validate:"required,min=1,max=2592000"` // up to 30d
}
func (h *AnalyticsHandler) Funnel(w http.ResponseWriter, r *http.Request) {
var req funnelRequest
if err := decodeAndValidate(r, &req); err != nil {
writeError(w, err)
return
}
ws := middleware.WorkspaceFromCtx(r.Context())
res, err := h.svc.Funnel(r.Context(), repo.FunnelQuery{
WorkspaceID: ws,
Steps: req.Steps,
From: *req.From,
To: *req.To,
WindowSeconds: req.WindowSeconds,
})
if err != nil {
h.log.Error("funnel", zap.String("workspace_id", ws), zap.Error(err))
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, res)
}
// ---------------------------------------------------------------------------
// Retention
// ---------------------------------------------------------------------------
type retentionRequest struct {
InitialEvent string `json:"initial_event" validate:"required,min=1"`
ReturnEvent string `json:"return_event" validate:"required,min=1"`
From *time.Time `json:"from" validate:"required"`
To *time.Time `json:"to" validate:"required,gtfield=From"`
Periods int `json:"periods" validate:"omitempty,min=1,max=90"`
}
func (h *AnalyticsHandler) Retention(w http.ResponseWriter, r *http.Request) {
var req retentionRequest
if err := decodeAndValidate(r, &req); err != nil {
writeError(w, err)
return
}
ws := middleware.WorkspaceFromCtx(r.Context())
res, err := h.svc.Retention(r.Context(), repo.RetentionQuery{
WorkspaceID: ws,
InitialEvent: req.InitialEvent,
ReturnEvent: req.ReturnEvent,
From: *req.From,
To: *req.To,
Periods: req.Periods,
})
if err != nil {
h.log.Error("retention", zap.String("workspace_id", ws), zap.Error(err))
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, res)
}
// ---------------------------------------------------------------------------
// Session
// ---------------------------------------------------------------------------
type sessionRequest struct {
From *time.Time `json:"from" validate:"required"`
To *time.Time `json:"to" validate:"required,gtfield=From"`
TimeoutSeconds uint32 `json:"timeout_seconds" validate:"omitempty,min=60,max=86400"`
UserID string `json:"user_id"`
Limit int `json:"limit" validate:"omitempty,min=1,max=1000"`
Offset int `json:"offset" validate:"omitempty,min=0"`
}
func (h *AnalyticsHandler) Session(w http.ResponseWriter, r *http.Request) {
var req sessionRequest
if err := decodeAndValidate(r, &req); err != nil {
writeError(w, err)
return
}
if req.TimeoutSeconds == 0 {
req.TimeoutSeconds = 30 * 60
}
if req.Limit == 0 {
req.Limit = 100
}
ws := middleware.WorkspaceFromCtx(r.Context())
res, err := h.svc.Sessions(r.Context(), repo.SessionQuery{
WorkspaceID: ws,
UserID: req.UserID,
From: *req.From,
To: *req.To,
TimeoutSeconds: req.TimeoutSeconds,
Limit: req.Limit,
Offset: req.Offset,
})
if err != nil {
h.log.Error("session", zap.String("workspace_id", ws), zap.Error(err))
writeError(w, err)
return
}
writeJSON(w, http.StatusOK, res)
}