package handler import ( "errors" "net/http" "github.com/gin-gonic/gin" "github.com/yuxingu/digital-psychology/apps/api/internal/middleware" "github.com/yuxingu/digital-psychology/apps/api/internal/service/analytics" "github.com/yuxingu/digital-psychology/apps/api/pkg/response" ) // AnalyticsHandler serves POST /api/v1/analytics/events (DeviceAuth). type AnalyticsHandler struct { Svc *analytics.Service } // Register mounts analytics routes on a DeviceAuth group. func (h *AnalyticsHandler) Register(api *gin.RouterGroup) { g := api.Group("/analytics") g.POST("/events", h.Ingest) } func (h *AnalyticsHandler) Ingest(c *gin.Context) { userID, ok := middleware.UserIDFromContext(c) if !ok { response.Fail(c, http.StatusUnauthorized, 40100, "unauthorized") return } deviceKey := c.GetHeader(middleware.DeviceKeyHeader) if deviceKey == "" { response.Fail(c, http.StatusBadRequest, 40020, "device key required") return } var body struct { Items []analytics.EventIn `json:"items"` } if err := c.ShouldBindJSON(&body); err != nil { response.Fail(c, http.StatusBadRequest, 40021, "invalid body") return } res, err := h.Svc.Ingest(c.Request.Context(), userID, deviceKey, body.Items) if err != nil { if errors.Is(err, analytics.ErrTooManyItems) { response.Fail(c, http.StatusBadRequest, 40022, "too many items") return } if errors.Is(err, analytics.ErrInvalidBatch) { response.Fail(c, http.StatusBadRequest, 40023, "invalid batch") return } response.Fail(c, http.StatusInternalServerError, 50020, "ingest failed") return } response.OK(c, res) }