Files
gochat/backend/internal/handler/api/v1/bot_rule_handler.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

252 lines
7.3 KiB
Go

package v1
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/gochat/gochat/internal/automation"
"github.com/gochat/gochat/pkg/response"
)
// BotRuleHandler handles bot rule API endpoints.
// Reference: Chatwoot AutomationRulesController pattern adapted for AgentBot rules.
type BotRuleHandler struct {
svc *automation.BotRuleService
}
// NewBotRuleHandler creates a new BotRuleHandler.
func NewBotRuleHandler(svc *automation.BotRuleService) *BotRuleHandler {
return &BotRuleHandler{svc: svc}
}
// List retrieves all bot rules for an account.
// GET /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules_configs
func (h *BotRuleHandler) List(c *gin.Context) {
accountID, err := parseUintParam(c, "account_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
return
}
rules, svcErr := h.svc.ListByAccount(c.Request.Context(), accountID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, gin.H{
"bot_rules": rules,
"meta": gin.H{"count": len(rules)},
})
}
// ListByBot retrieves all bot rules for a specific AgentBot.
// GET /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules
func (h *BotRuleHandler) ListByBot(c *gin.Context) {
accountID, err := parseUintParam(c, "account_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
return
}
agentBotID, err := parseUintParam(c, "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid agent_bot_id")
return
}
rules, svcErr := h.svc.ListByAgentBot(c.Request.Context(), accountID, agentBotID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, gin.H{
"bot_rules": rules,
"meta": gin.H{"count": len(rules)},
})
}
// Get retrieves a single bot rule by ID.
// GET /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules/:id
func (h *BotRuleHandler) Get(c *gin.Context) {
id, err := parseUintAnyParam(c, "rule_id", "id", "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
return
}
rule, svcErr := h.svc.GetByID(c.Request.Context(), id)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.OK(c, rule)
}
// Create creates a new bot rule for an AgentBot.
// POST /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules
func (h *BotRuleHandler) Create(c *gin.Context) {
accountID, err := parseUintParam(c, "account_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
return
}
agentBotID, err := parseUintParam(c, "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid agent_bot_id")
return
}
var req struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
EventName automation.BotRuleEventType `json:"event_name" binding:"required"`
Conditions automation.Conditions `json:"conditions"`
Actions automation.Actions `json:"actions"`
Status automation.BotRuleStatus `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
rule := &automation.BotRule{
AccountID: accountID,
AgentBotID: agentBotID,
Name: req.Name,
Description: req.Description,
EventName: req.EventName,
Conditions: req.Conditions,
Actions: req.Actions,
Status: req.Status,
}
// Default to active if not specified
if rule.Status == "" {
rule.Status = automation.BotRuleStatusActive
}
if svcErr := h.svc.Create(c.Request.Context(), rule); svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.Created(c, rule)
}
// Update updates an existing bot rule.
// PUT /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules/:id
func (h *BotRuleHandler) Update(c *gin.Context) {
id, err := parseUintAnyParam(c, "rule_id", "id", "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
return
}
existing, svcErr := h.svc.GetByID(c.Request.Context(), id)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
var req struct {
Name string `json:"name"`
Description string `json:"description"`
EventName automation.BotRuleEventType `json:"event_name"`
Conditions automation.Conditions `json:"conditions"`
Actions automation.Actions `json:"actions"`
Status automation.BotRuleStatus `json:"status"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
// Apply updates
if req.Name != "" {
existing.Name = req.Name
}
if req.Description != "" {
existing.Description = req.Description
}
if req.EventName != "" {
existing.EventName = req.EventName
}
if req.Conditions != nil {
existing.Conditions = req.Conditions
}
if req.Actions != nil {
existing.Actions = req.Actions
}
if req.Status != "" {
existing.Status = req.Status
}
if svcErr := h.svc.Update(c.Request.Context(), existing); svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.OK(c, existing)
}
// Delete soft-deletes a bot rule.
// DELETE /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules/:id
func (h *BotRuleHandler) Delete(c *gin.Context) {
id, err := parseUintAnyParam(c, "rule_id", "id", "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
return
}
if svcErr := h.svc.Delete(c.Request.Context(), id); svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.NoContent(c)
}
// ToggleStatus toggles the active/inactive status of a bot rule.
// PATCH /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules/:id/status
func (h *BotRuleHandler) ToggleStatus(c *gin.Context) {
id, err := parseUintAnyParam(c, "rule_id", "id", "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
return
}
var req struct {
Status automation.BotRuleStatus `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
return
}
if svcErr := h.svc.ToggleStatus(c.Request.Context(), id, req.Status); svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.OK(c, gin.H{"id": id, "status": req.Status})
}
// Clone duplicates a bot rule.
// POST /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules/:id/clone
func (h *BotRuleHandler) Clone(c *gin.Context) {
id, err := parseUintAnyParam(c, "rule_id", "id", "agent_bot_id")
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
return
}
cloned, svcErr := h.svc.Clone(c.Request.Context(), id)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
response.Created(c, cloned)
}