package transport import ( "strconv" "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/geo-platform/tenant-api/internal/bootstrap" "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/tenant/app" ) type MonitoringHandler struct { svc *app.MonitoringService } type monitoringCollectNowRequest struct { KeywordID *int64 `json:"keyword_id"` PlatformIDs []string `json:"platform_ids"` Preempt *bool `json:"preempt"` WaitForFirstDispatch *bool `json:"wait_for_first_dispatch"` TargetClientID *string `json:"target_client_id"` } func NewMonitoringHandler(a *bootstrap.App) *MonitoringHandler { return &MonitoringHandler{ svc: a.MonitoringService, } } func (h *MonitoringHandler) DashboardComposite(c *gin.Context) { brandID, err := parseOptionalInt64(c.Query("brand_id")) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_brand_id", "brand_id must be a number")) return } keywordID, err := parseOptionalInt64Pointer(c.Query("keyword_id")) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_keyword_id", "keyword_id must be a number")) return } days, err := parseOptionalInt(c.Query("days")) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_days", "days must be a number")) return } aiPlatformID := parseOptionalStringPointer(c.Query("ai_platform_id")) data, svcErr := h.svc.DashboardComposite(c.Request.Context(), brandID, keywordID, days, c.Query("business_date"), aiPlatformID) if svcErr != nil { response.Error(c, svcErr) return } response.Success(c, data) } func (h *MonitoringHandler) CitationSummary(c *gin.Context) { days, err := parseOptionalInt(c.Query("days")) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_days", "days must be a number")) return } brandID, err := parseOptionalInt64(c.Query("brand_id")) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_brand_id", "brand_id must be a number")) return } keywordID, err := parseOptionalInt64Pointer(c.Query("keyword_id")) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_keyword_id", "keyword_id must be a number")) return } aiPlatformID := parseOptionalStringPointer(c.Query("ai_platform_id")) data, svcErr := h.svc.CitationSummary(c.Request.Context(), days, brandID, keywordID, c.Query("business_date"), aiPlatformID) if svcErr != nil { response.Error(c, svcErr) return } response.Success(c, data) } func (h *MonitoringHandler) QuestionDetail(c *gin.Context) { brandID, err := strconv.ParseInt(c.Param("brand_id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_brand_id", "brand_id must be a number")) return } questionID, err := strconv.ParseInt(c.Param("question_id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_question_id", "question_id must be a number")) return } aiPlatformID := parseOptionalStringPointer(c.Query("ai_platform_id")) data, svcErr := h.svc.QuestionDetail( c.Request.Context(), brandID, questionID, c.Query("date_from"), c.Query("date_to"), c.Query("question_hash"), aiPlatformID, ) if svcErr != nil { response.Error(c, svcErr) return } response.Success(c, data) } func (h *MonitoringHandler) CollectNow(c *gin.Context) { brandID, err := strconv.ParseInt(c.Param("brand_id"), 10, 64) if err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_brand_id", "brand_id must be a number")) return } var req monitoringCollectNowRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_collect_now_request", "collect-now request is invalid")) return } var targetClientID *uuid.UUID if req.TargetClientID != nil && strings.TrimSpace(*req.TargetClientID) != "" { parsed, parseErr := uuid.Parse(strings.TrimSpace(*req.TargetClientID)) if parseErr != nil { response.Error(c, response.ErrBadRequest(40031, "invalid_target_client_id", "target_client_id must be a uuid")) return } targetClientID = &parsed } data, svcErr := h.svc.CollectNow(c.Request.Context(), brandID, req.KeywordID, app.MonitoringCollectNowOptions{ PlatformIDs: req.PlatformIDs, Preempt: req.Preempt == nil || *req.Preempt, WaitForFirstDispatch: req.WaitForFirstDispatch != nil && *req.WaitForFirstDispatch, TargetClientID: targetClientID, }) if svcErr != nil { response.Error(c, svcErr) return } response.Success(c, data) } func parseOptionalInt64(raw string) (int64, error) { if raw == "" { return 0, nil } return strconv.ParseInt(raw, 10, 64) } func parseOptionalInt64Pointer(raw string) (*int64, error) { if raw == "" { return nil, nil } value, err := strconv.ParseInt(raw, 10, 64) if err != nil { return nil, err } return &value, nil } func parseOptionalInt(raw string) (int, error) { if raw == "" { return 0, nil } return strconv.Atoi(raw) } func parseOptionalStringPointer(raw string) *string { trimmed := strings.TrimSpace(raw) if trimmed == "" { return nil } value := trimmed return &value }