package v1 import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" applogger "github.com/gochat/gochat/pkg/logger" "github.com/gochat/gochat/pkg/response" ) // SummaryReportHandler handles read-only summary report collection endpoints. // Reference: Chatwoot resources :summary_reports, only: [] do // // collection do // get :agent, :team, :inbox, :label, :channel // end // end type SummaryReportHandler struct { svc *service.SummaryReportService } // NewSummaryReportHandler creates a new SummaryReportHandler. func NewSummaryReportHandler(svc *service.SummaryReportService) *SummaryReportHandler { return &SummaryReportHandler{svc: svc} } // Agent returns agent-level summary metrics. // GET /api/v1/accounts/:account_id/summary_reports/agent func (h *SummaryReportHandler) Agent(c *gin.Context) { accountID, ok := parseAccountID(c) if !ok { return } since, until, ok := parseSummaryDateRange(c) if !ok { return } result, err := h.svc.GetAgentSummary(c.Request.Context(), accountID, since, until) if err != nil { applogger.L().Errorf("SummaryReport.Agent error: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate agent summary report") return } c.JSON(http.StatusOK, result) } // Team returns team-level summary metrics. // GET /api/v1/accounts/:account_id/summary_reports/team func (h *SummaryReportHandler) Team(c *gin.Context) { accountID, ok := parseAccountID(c) if !ok { return } since, until, ok := parseSummaryDateRange(c) if !ok { return } result, err := h.svc.GetTeamSummary(c.Request.Context(), accountID, since, until) if err != nil { applogger.L().Errorf("SummaryReport.Team error: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate team summary report") return } c.JSON(http.StatusOK, result) } // Inbox returns inbox-level summary metrics. // GET /api/v1/accounts/:account_id/summary_reports/inbox func (h *SummaryReportHandler) Inbox(c *gin.Context) { accountID, ok := parseAccountID(c) if !ok { return } since, until, ok := parseSummaryDateRange(c) if !ok { return } result, err := h.svc.GetInboxSummary(c.Request.Context(), accountID, since, until) if err != nil { applogger.L().Errorf("SummaryReport.Inbox error: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate inbox summary report") return } c.JSON(http.StatusOK, result) } // Label returns label-level summary metrics. // GET /api/v1/accounts/:account_id/summary_reports/label func (h *SummaryReportHandler) Label(c *gin.Context) { accountID, ok := parseAccountID(c) if !ok { return } since, until, ok := parseSummaryDateRange(c) if !ok { return } result, err := h.svc.GetLabelSummary(c.Request.Context(), accountID, since, until) if err != nil { applogger.L().Errorf("SummaryReport.Label error: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate label summary report") return } c.JSON(http.StatusOK, result) } // Channel returns channel-level summary metrics. // GET /api/v1/accounts/:account_id/summary_reports/channel // Reference: Chatwoot summary_reports#channel — enforces 6-month max date range func (h *SummaryReportHandler) Channel(c *gin.Context) { accountID, ok := parseAccountID(c) if !ok { return } since, until, ok := parseSummaryDateRange(c) if !ok { return } // Chatwoot enforces max 6-month date range for channel summary if !until.IsZero() && !since.IsZero() { maxRange := since.AddDate(0, 6, 0) if until.After(maxRange) { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "date range too long, maximum 6 months") return } } result, err := h.svc.GetChannelSummary(c.Request.Context(), accountID, since, until) if err != nil { applogger.L().Errorf("SummaryReport.Channel error: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to generate channel summary report") return } c.JSON(http.StatusOK, result) } // parseSummaryDateRange extracts since/until from query params for summary reports. func parseSummaryDateRange(c *gin.Context) (since, until time.Time, ok bool) { sinceStr := c.Query("since") untilStr := c.Query("until") if sinceStr == "" || untilStr == "" { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "since and until query params are required") return time.Time{}, time.Time{}, false } since, err := parseChatwootReportTime(sinceStr) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid since date format") return time.Time{}, time.Time{}, false } until, err = parseChatwootReportTime(untilStr) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid until date format") return time.Time{}, time.Time{}, false } return since, until, true } // RegisterSummaryReportRoutes registers summary report routes on the given router group. // Routes are account-scoped: GET /summary_reports/agent, /team, /inbox, /label, /channel func RegisterSummaryReportRoutes(g *gin.RouterGroup, h *SummaryReportHandler) { summaryReports := g.Group("/summary_reports") { summaryReports.GET("/agent", h.Agent) summaryReports.GET("/team", h.Team) summaryReports.GET("/inbox", h.Inbox) summaryReports.GET("/label", h.Label) summaryReports.GET("/channel", h.Channel) } }