package v1 import ( "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/pkg/response" ) // PlatformAgentBotHandler handles Platform API agent bot endpoints (AccessToken auth). // Reference: Chatwoot Platform::Api::V1::AgentBotsController — AccessToken authenticated // // Distinct from account-level AgentBotHandler: this uses AccessToken authentication // + Permissible system for resource-level access control. // Platform-level bots (account_id == nil) and account-scoped bots are both accessible. type PlatformAgentBotHandler struct { agentBotRepo *repository.AgentBotRepo permissibleRepo *repository.PermissibleRepo } // NewPlatformAgentBotHandler creates a new PlatformAgentBot handler. func NewPlatformAgentBotHandler( agentBotRepo *repository.AgentBotRepo, permissibleRepo *repository.PermissibleRepo, ) *PlatformAgentBotHandler { return &PlatformAgentBotHandler{ agentBotRepo: agentBotRepo, permissibleRepo: permissibleRepo, } } // List returns all agent bots the PlatformApp has permissible access to. // GET /platform/api/v1/agent_bots // Reference: Chatwoot Platform::Api::V1::AgentBotsController#index func (h *PlatformAgentBotHandler) List(c *gin.Context) { platformAppID := getPlatformAppID(c) permissibles, err := h.permissibleRepo.FindByPlatformAppID(c.Request.Context(), platformAppID) if err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } var bots []model.AgentBot for _, perm := range permissibles { if perm.PermissibleType == model.PermissibleTypeAgentBot { bot, err := h.agentBotRepo.FindByID(c.Request.Context(), perm.PermissibleID) if err != nil { continue } bots = append(bots, *bot) } } response.OK(c, bots) } // Show retrieves an agent bot by ID. // GET /platform/api/v1/agent_bots/:id // Reference: Chatwoot Platform::Api::V1::AgentBotsController#show // Requires: Permissible verification func (h *PlatformAgentBotHandler) Show(c *gin.Context) { botID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid bot ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAgentBot, botID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } bot, err := h.agentBotRepo.FindByID(c.Request.Context(), botID) if err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "agent bot not found") return } response.OK(c, bot) } // Create creates a new agent bot and auto-creates Permissible record. // POST /platform/api/v1/agent_bots // Reference: Chatwoot Platform::Api::V1::AgentBotsController#create // Auto-permissible: PlatformApp automatically gets access to the created bot. func (h *PlatformAgentBotHandler) Create(c *gin.Context) { platformAppID := getPlatformAppID(c) var req struct { Name string `json:"name" binding:"required"` Description string `json:"description,omitempty"` AvatarURL string `json:"avatar_url,omitempty"` AccountID *uint `json:"account_id,omitempty"` Config model.AgentBot `json:"config,omitempty"` // Embedded struct for bot config } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } bot := &model.AgentBot{ AccountID: req.AccountID, Name: req.Name, Description: req.Description, AvatarURL: req.AvatarURL, } if err := h.agentBotRepo.Create(c.Request.Context(), bot); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } // Auto-create Permissible perm := &model.Permissible{ PlatformAppID: platformAppID, PermissibleType: model.PermissibleTypeAgentBot, PermissibleID: bot.ID, } if err := h.permissibleRepo.Create(c.Request.Context(), perm); err != nil { // Non-critical } response.Created(c, bot) } // Update updates an agent bot. // PUT /platform/api/v1/agent_bots/:id // Reference: Chatwoot Platform::Api::V1::AgentBotsController#update // Requires: Permissible verification func (h *PlatformAgentBotHandler) Update(c *gin.Context) { botID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid bot ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAgentBot, botID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } bot, err := h.agentBotRepo.FindByID(c.Request.Context(), botID) if err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "agent bot not found") return } var req struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` AvatarURL string `json:"avatar_url,omitempty"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } if req.Name != "" { bot.Name = req.Name } if req.Description != "" { bot.Description = req.Description } if req.AvatarURL != "" { bot.AvatarURL = req.AvatarURL } if err := h.agentBotRepo.Update(c.Request.Context(), bot); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } response.OK(c, bot) } // Destroy deletes an agent bot. // DELETE /platform/api/v1/agent_bots/:id // Reference: Chatwoot Platform::Api::V1::AgentBotsController#destroy // Requires: Permissible verification func (h *PlatformAgentBotHandler) Destroy(c *gin.Context) { botID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid bot ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAgentBot, botID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } if err := h.agentBotRepo.Delete(c.Request.Context(), botID); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } response.NoContent(c) } // DeleteAvatar removes the avatar of an agent bot. // POST /platform/api/v1/agent_bots/:id/delete_avatar // Reference: Chatwoot Platform::Api::V1::AgentBotsController#delete_avatar // Requires: Permissible verification func (h *PlatformAgentBotHandler) DeleteAvatar(c *gin.Context) { botID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid bot ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAgentBot, botID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } bot, err := h.agentBotRepo.FindByID(c.Request.Context(), botID) if err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "agent bot not found") return } bot.AvatarURL = "" if err := h.agentBotRepo.Update(c.Request.Context(), bot); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } response.OK(c, bot) }