package v1 import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/pkg/pagination" "github.com/gochat/gochat/pkg/response" ) // IntegrationHookHandler handles IntegrationHook CRUD + ProcessEvent endpoints. // Reference: Chatwoot Integrations::HooksController + AppsController type IntegrationHookHandler struct { svc *service.IntegrationHookService } // NewIntegrationHookHandler creates a new IntegrationHookHandler. func NewIntegrationHookHandler(svc *service.IntegrationHookService) *IntegrationHookHandler { return &IntegrationHookHandler{svc: svc} } // --- Integration Apps (catalog) --- // ListApps retrieves all available integration apps. // GET /api/v1/accounts/:account_id/integrations/apps func (h *IntegrationHookHandler) ListApps(c *gin.Context) { if !h.svc.Ready() { response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "failed to list integration apps") return } apps, err := h.svc.ListApps(c.Request.Context()) if err != nil { handleServiceError(c, err) return } response.OK(c, apps) } // GetApp retrieves a single integration app by ID. // GET /api/v1/accounts/:account_id/integrations/apps/:id func (h *IntegrationHookHandler) GetApp(c *gin.Context) { id, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id") return } app, svcErr := h.svc.GetApp(c.Request.Context(), id) if svcErr != nil { handleServiceError(c, svcErr) return } response.OK(c, app) } // --- Integration Hooks CRUD --- // ListHooks retrieves all integration hooks for an account, paginated. // GET /api/v1/accounts/:account_id/integrations/hooks func (h *IntegrationHookHandler) ListHooks(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } page := pagination.Parse(c) hooks, total, svcErr := h.svc.List(c.Request.Context(), accountID, page.Offset, page.PerPage) if svcErr != nil { handleServiceError(c, svcErr) return } response.OKWithMeta(c, hooks, page.Page, page.PerPage, total) } // GetHook retrieves a single integration hook by ID. // GET /api/v1/accounts/:account_id/integrations/hooks/:id func (h *IntegrationHookHandler) GetHook(c *gin.Context) { id, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid hook id") return } hook, svcErr := h.svc.Get(c.Request.Context(), id) if svcErr != nil { handleServiceError(c, svcErr) return } response.OK(c, hook) } // CreateHook creates a new integration hook. // POST /api/v1/accounts/:account_id/integrations/hooks func (h *IntegrationHookHandler) CreateHook(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } // Chatwoot: params.require(:hook) → {"hook": {...}} var wrapper struct { Hook service.CreateHookRequest `json:"hook"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } req := wrapper.Hook hook, svcErr := h.svc.Create(c.Request.Context(), accountID, req) if svcErr != nil { handleServiceError(c, svcErr) return } response.OK(c, hook) } // UpdateHook updates an existing integration hook. // PUT /api/v1/accounts/:account_id/integrations/hooks/:id func (h *IntegrationHookHandler) UpdateHook(c *gin.Context) { id, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid hook id") return } // Chatwoot: params.require(:hook) → {"hook": {...}} var wrapper struct { Hook service.UpdateHookRequest `json:"hook"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } req := wrapper.Hook hook, svcErr := h.svc.Update(c.Request.Context(), id, req) if svcErr != nil { handleServiceError(c, svcErr) return } response.OK(c, hook) } // DeleteHook deletes an integration hook. // DELETE /api/v1/accounts/:account_id/integrations/hooks/:id func (h *IntegrationHookHandler) DeleteHook(c *gin.Context) { id, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid hook id") return } if svcErr := h.svc.Delete(c.Request.Context(), id); svcErr != nil { handleServiceError(c, svcErr) return } response.OK(c, gin.H{"id": id}) } // ProcessHookEvent processes an incoming event for a hook. // POST /api/v1/accounts/:account_id/integrations/hooks/:id/process_event func (h *IntegrationHookHandler) ProcessHookEvent(c *gin.Context) { id, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid hook id") return } var eventData map[string]interface{} if err := c.ShouldBindJSON(&eventData); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } if svcErr := h.svc.ProcessEvent(c.Request.Context(), id, eventData); svcErr != nil { if strings.Contains(strings.ToLower(svcErr.Error()), "not found") { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, svcErr.Error()) return } response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, svcErr.Error()) return } response.OK(c, gin.H{"message": "event processed"}) } // RegisterIntegrationHookRoutes registers integration hook routes on a router group. func RegisterIntegrationHookRoutes(g *gin.RouterGroup, h *IntegrationHookHandler) { // Integration apps catalog apps := g.Group("/apps") { apps.GET("/", h.ListApps) apps.GET("/:id", h.GetApp) } // Integration hooks CRUD + process event hooks := g.Group("/hooks") { hooks.GET("/", h.ListHooks) hooks.GET("/:id", h.GetHook) hooks.POST("/", h.CreateHook) hooks.PUT("/:id", h.UpdateHook) hooks.DELETE("/:id", h.DeleteHook) hooks.POST("/:id/process_event", h.ProcessHookEvent) } }