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.
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gochat/gochat/internal/automation"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
)
|
||||
|
||||
// BotTriggerConfigHandler handles bot trigger condition config API endpoints.
|
||||
// Reference: Chatwoot AutomationRulesController trigger conditions pattern adapted for AgentBot.
|
||||
type BotTriggerConfigHandler struct {
|
||||
svc *automation.BotTriggerConfigService
|
||||
}
|
||||
|
||||
// NewBotTriggerConfigHandler creates a new BotTriggerConfigHandler.
|
||||
func NewBotTriggerConfigHandler(svc *automation.BotTriggerConfigService) *BotTriggerConfigHandler {
|
||||
return &BotTriggerConfigHandler{svc: svc}
|
||||
}
|
||||
|
||||
// List retrieves all trigger configs for an account.
|
||||
// GET /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs
|
||||
func (h *BotTriggerConfigHandler) List(c *gin.Context) {
|
||||
accountID, err := parseUintParam(c, "account_id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
|
||||
return
|
||||
}
|
||||
|
||||
configs, svcErr := h.svc.ListByAccount(c.Request.Context(), accountID)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"trigger_configs": configs,
|
||||
"meta": gin.H{"count": len(configs)},
|
||||
})
|
||||
}
|
||||
|
||||
// ListByBot retrieves all trigger configs for a specific AgentBot.
|
||||
// GET /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs
|
||||
func (h *BotTriggerConfigHandler) 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
|
||||
}
|
||||
|
||||
configs, svcErr := h.svc.ListByAgentBot(c.Request.Context(), accountID, agentBotID)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"trigger_configs": configs,
|
||||
"meta": gin.H{"count": len(configs)},
|
||||
})
|
||||
}
|
||||
|
||||
// Get retrieves a single trigger config by ID.
|
||||
// GET /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs/:id
|
||||
func (h *BotTriggerConfigHandler) Get(c *gin.Context) {
|
||||
id, err := parseUintAnyParam(c, "trigger_config_id", "id", "agent_bot_id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
|
||||
return
|
||||
}
|
||||
|
||||
config, svcErr := h.svc.GetByID(c.Request.Context(), id)
|
||||
if svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, config)
|
||||
}
|
||||
|
||||
// Create creates a new trigger config for an AgentBot.
|
||||
// POST /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs
|
||||
func (h *BotTriggerConfigHandler) 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.TriggerConditions `json:"conditions"`
|
||||
QueryOperator string `json:"query_operator"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
config := &automation.BotTriggerConfig{
|
||||
AccountID: accountID,
|
||||
AgentBotID: agentBotID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
EventName: req.EventName,
|
||||
Conditions: req.Conditions,
|
||||
QueryOperator: req.QueryOperator,
|
||||
Active: req.Active,
|
||||
}
|
||||
// Default query_operator to "and"
|
||||
if config.QueryOperator == "" {
|
||||
config.QueryOperator = "and"
|
||||
}
|
||||
// Default to active if not explicitly set
|
||||
if !config.Active && req.Active == false {
|
||||
// Only default to active when the field was not provided in JSON
|
||||
// (JSON deserialization defaults bool to false)
|
||||
config.Active = true
|
||||
}
|
||||
|
||||
if svcErr := h.svc.Create(c.Request.Context(), config); svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, config)
|
||||
}
|
||||
|
||||
// Update updates an existing trigger config.
|
||||
// PUT /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs/:id
|
||||
func (h *BotTriggerConfigHandler) Update(c *gin.Context) {
|
||||
id, err := parseUintAnyParam(c, "trigger_config_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.TriggerConditions `json:"conditions"`
|
||||
QueryOperator string `json:"query_operator"`
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
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.QueryOperator != "" {
|
||||
existing.QueryOperator = req.QueryOperator
|
||||
}
|
||||
existing.Active = req.Active
|
||||
|
||||
if svcErr := h.svc.Update(c.Request.Context(), existing); svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, existing)
|
||||
}
|
||||
|
||||
// Delete soft-deletes a trigger config.
|
||||
// DELETE /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs/:id
|
||||
func (h *BotTriggerConfigHandler) Delete(c *gin.Context) {
|
||||
id, err := parseUintAnyParam(c, "trigger_config_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)
|
||||
}
|
||||
|
||||
// ToggleActive toggles the active state of a trigger config.
|
||||
// PATCH /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/trigger_configs/:id/active
|
||||
func (h *BotTriggerConfigHandler) ToggleActive(c *gin.Context) {
|
||||
id, err := parseUintAnyParam(c, "trigger_config_id", "id", "agent_bot_id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid id")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Active bool `json:"active" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if svcErr := h.svc.ToggleActive(c.Request.Context(), id, req.Active); svcErr != nil {
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, gin.H{"id": id, "active": req.Active})
|
||||
}
|
||||
Reference in New Issue
Block a user