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,573 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SlaPolicyHandler handles SLA Policy CRUD + applied SLA metrics/download.
|
||||
// Reference: Chatwoot app/controllers/api/v1/sla_policies_controller.rb
|
||||
type SlaPolicyHandler struct {
|
||||
svc *service.SlaPolicyService
|
||||
auditSvc *service.AuditService
|
||||
}
|
||||
|
||||
// NewSlaPolicyHandler creates a new SlaPolicy handler.
|
||||
func NewSlaPolicyHandler(svc *service.SlaPolicyService) *SlaPolicyHandler {
|
||||
return &SlaPolicyHandler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *SlaPolicyHandler) WithAuditService(auditSvc *service.AuditService) *SlaPolicyHandler {
|
||||
h.auditSvc = auditSvc
|
||||
return h
|
||||
}
|
||||
|
||||
// Create creates a new SLA policy.
|
||||
// POST /api/v1/accounts/:account_id/sla_policies
|
||||
func (h *SlaPolicyHandler) Create(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
var wrapper service.SlaPolicyCreateWrapper
|
||||
if err := c.ShouldBindJSON(&wrapper); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
req := wrapper.SlaPolicy
|
||||
|
||||
policy, svcErr := h.svc.Create(c.Request.Context(), accountID, &req)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Create SLA policy for account %d: %v", accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
recordAuditMutation(c, h.auditSvc, auditMutation{
|
||||
AccountID: accountID,
|
||||
AuditableType: "SlaPolicy",
|
||||
AuditableID: policy.ID,
|
||||
Action: "create",
|
||||
AuditedChanges: serializeSlaPolicy(policy),
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"payload": serializeSlaPolicy(policy)})
|
||||
}
|
||||
|
||||
// Get retrieves a SLA policy by ID.
|
||||
// GET /api/v1/accounts/:account_id/sla_policies/:id
|
||||
func (h *SlaPolicyHandler) Get(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid policy id")
|
||||
return
|
||||
}
|
||||
|
||||
policy, svcErr := h.svc.Get(c.Request.Context(), accountID, policyID)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Get SLA policy %d for account %d: %v", policyID, accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"payload": serializeSlaPolicy(policy)})
|
||||
}
|
||||
|
||||
// List retrieves all SLA policies for an account.
|
||||
// GET /api/v1/accounts/:account_id/sla_policies
|
||||
func (h *SlaPolicyHandler) List(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policies, svcErr := h.svc.List(c.Request.Context(), accountID)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("List SLA policies for account %d: %v", accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"payload": serializeSlaPolicies(policies)})
|
||||
}
|
||||
|
||||
// Update updates a SLA policy.
|
||||
// PUT /api/v1/accounts/:account_id/sla_policies/:id
|
||||
func (h *SlaPolicyHandler) Update(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid policy id")
|
||||
return
|
||||
}
|
||||
|
||||
var wrapper service.SlaPolicyUpdateWrapper
|
||||
if err := c.ShouldBindJSON(&wrapper); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
req := wrapper.SlaPolicy
|
||||
|
||||
policy, svcErr := h.svc.Update(c.Request.Context(), accountID, policyID, &req)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Update SLA policy %d for account %d: %v", policyID, accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
recordAuditMutation(c, h.auditSvc, auditMutation{
|
||||
AccountID: accountID,
|
||||
AuditableType: "SlaPolicy",
|
||||
AuditableID: policy.ID,
|
||||
Action: "update",
|
||||
AuditedChanges: serializeSlaPolicy(policy),
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"payload": serializeSlaPolicy(policy)})
|
||||
}
|
||||
|
||||
// Delete deletes a SLA policy.
|
||||
// DELETE /api/v1/accounts/:account_id/sla_policies/:id
|
||||
func (h *SlaPolicyHandler) Delete(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid policy id")
|
||||
return
|
||||
}
|
||||
|
||||
svcErr := h.svc.Delete(c.Request.Context(), accountID, policyID)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Delete SLA policy %d for account %d: %v", policyID, accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
recordAuditMutation(c, h.auditSvc, auditMutation{
|
||||
AccountID: accountID,
|
||||
AuditableType: "SlaPolicy",
|
||||
AuditableID: policyID,
|
||||
Action: "destroy",
|
||||
AuditedChanges: gin.H{"id": policyID},
|
||||
})
|
||||
|
||||
// Chatwoot returns head :ok (200) on destroy.
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func serializeSlaPolicies(policies []model.SlaPolicy) []map[string]any {
|
||||
payload := make([]map[string]any, 0, len(policies))
|
||||
for i := range policies {
|
||||
payload = append(payload, serializeSlaPolicy(&policies[i]))
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeSlaPolicy(policy *model.SlaPolicy) map[string]any {
|
||||
if policy == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
"id": policy.ID,
|
||||
"name": policy.Name,
|
||||
"description": policy.Description,
|
||||
"first_response_time_threshold": policy.FirstResponseTimeThreshold,
|
||||
"next_response_time_threshold": policy.NextResponseTimeThreshold,
|
||||
"resolution_time_threshold": policy.ResolutionTimeThreshold,
|
||||
"only_during_business_hours": policy.OnlyDuringBusinessHours,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAppliedSlas returns the Chatwoot SLA reports table payload.
|
||||
// GET /api/v1/accounts/:account_id/applied_slas
|
||||
func (h *SlaPolicyHandler) ListAppliedSlas(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
filter := parseAppliedSlaReportFilter(c)
|
||||
page := parseAppliedSlaPage(c)
|
||||
report, svcErr := h.svc.ListAppliedSlaReports(c.Request.Context(), accountID, filter, page)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("List applied SLA reports for account %d: %v", accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"payload": serializeAppliedSlaReportItems(c.Request.Context(), h.svc.DB(), report.AppliedSLAs),
|
||||
"meta": gin.H{
|
||||
"count": report.Count,
|
||||
"current_page": report.CurrentPage,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetAppliedSlaMetrics retrieves Chatwoot SLA report metrics.
|
||||
// GET /api/v1/accounts/:account_id/applied_slas/metrics
|
||||
func (h *SlaPolicyHandler) GetAppliedSlaMetrics(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
metrics, svcErr := h.svc.GetAppliedSlaReportMetrics(c.Request.Context(), accountID, parseAppliedSlaReportFilter(c))
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Get applied SLA report metrics for account %d: %v", accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, metrics)
|
||||
}
|
||||
|
||||
// GetAppliedSlaDownload exports missed applied SLAs as Chatwoot CSV.
|
||||
// GET /api/v1/accounts/:account_id/applied_slas/download
|
||||
func (h *SlaPolicyHandler) GetAppliedSlaDownload(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
applied, svcErr := h.svc.ListAppliedSlaReportDownload(c.Request.Context(), accountID, parseAppliedSlaReportFilter(c))
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Get SLA download for account %d: %v", accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/csv")
|
||||
c.Header("Content-Disposition", "attachment; filename=breached_conversation.csv")
|
||||
c.String(http.StatusOK, buildAppliedSlaCSV(c, h.svc.DB(), applied))
|
||||
}
|
||||
|
||||
func parseAppliedSlaReportFilter(c *gin.Context) service.AppliedSlaReportFilter {
|
||||
return service.AppliedSlaReportFilter{
|
||||
Since: parseUnixQueryTime(c.Query("since")),
|
||||
Until: parseUnixQueryTime(c.Query("until")),
|
||||
InboxID: parseOptionalUintQuery(c.Query("inbox_id")),
|
||||
TeamID: parseOptionalUintQuery(c.Query("team_id")),
|
||||
SlaPolicyID: parseOptionalUintQuery(c.Query("sla_policy_id")),
|
||||
LabelList: c.Query("label_list"),
|
||||
AssignedAgentID: parseOptionalUintQuery(c.Query("assigned_agent_id")),
|
||||
SLAStatus: c.Query("status"),
|
||||
}
|
||||
}
|
||||
|
||||
func parseAppliedSlaPage(c *gin.Context) int {
|
||||
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if err != nil || page < 1 {
|
||||
return 1
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
func parseOptionalUintQuery(raw string) *uint {
|
||||
if raw == "" || raw == "null" || raw == "undefined" {
|
||||
return nil
|
||||
}
|
||||
parsed, err := strconv.ParseUint(raw, 10, 32)
|
||||
if err != nil || parsed == 0 {
|
||||
return nil
|
||||
}
|
||||
value := uint(parsed)
|
||||
return &value
|
||||
}
|
||||
|
||||
func parseUnixQueryTime(raw string) *time.Time {
|
||||
if raw == "" || raw == "0" || raw == "null" || raw == "undefined" {
|
||||
return nil
|
||||
}
|
||||
seconds, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err == nil {
|
||||
t := time.Unix(seconds, 0).UTC()
|
||||
return &t
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339, raw); err == nil {
|
||||
return &t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func serializeAppliedSlaReportItems(ctx context.Context, db *gorm.DB, applied []model.AppliedSLA) []gin.H {
|
||||
payload := make([]gin.H, 0, len(applied))
|
||||
for i := range applied {
|
||||
item := &applied[i]
|
||||
payload = append(payload, gin.H{
|
||||
"applied_sla": serializeAppliedSlaReportApplied(item),
|
||||
"conversation": serializeAppliedSlaReportConversation(ctx, db, item.ConversationID),
|
||||
"sla_events": serializeSlaEvents(item.SlaEvents),
|
||||
})
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeAppliedSlaReportApplied(applied *model.AppliedSLA) gin.H {
|
||||
return gin.H{
|
||||
"id": applied.ID,
|
||||
"sla_id": applied.SlaPolicyID,
|
||||
"sla_status": applied.SLAStatus,
|
||||
"created_at": applied.CreatedAt.Unix(),
|
||||
"updated_at": applied.UpdatedAt.Unix(),
|
||||
"sla_description": applied.SlaPolicy.Description,
|
||||
"sla_name": applied.SlaPolicy.Name,
|
||||
"sla_first_response_time_threshold": applied.SlaPolicy.FirstResponseTimeThreshold,
|
||||
"sla_next_response_time_threshold": applied.SlaPolicy.NextResponseTimeThreshold,
|
||||
"sla_only_during_business_hours": applied.SlaPolicy.OnlyDuringBusinessHours,
|
||||
"sla_resolution_time_threshold": applied.SlaPolicy.ResolutionTimeThreshold,
|
||||
}
|
||||
}
|
||||
|
||||
func serializeAppliedSlaReportConversation(ctx context.Context, db *gorm.DB, conversationID uint) gin.H {
|
||||
conversation := findAppliedSlaConversation(ctx, db, conversationID)
|
||||
if conversation == nil {
|
||||
return gin.H{"id": conversationID, "contact": gin.H{}, "labels": ""}
|
||||
}
|
||||
payload := gin.H{
|
||||
"id": conversationDisplayID(conversation),
|
||||
"contact": gin.H{},
|
||||
"labels": conversation.Labels,
|
||||
}
|
||||
if db == nil {
|
||||
return payload
|
||||
}
|
||||
var contact model.Contact
|
||||
if err := db.WithContext(ctx).First(&contact, conversation.ContactID).Error; err == nil {
|
||||
payload["contact"] = gin.H{"name": contact.Name}
|
||||
}
|
||||
if conversation.AssigneeID != nil && *conversation.AssigneeID != 0 {
|
||||
var user model.User
|
||||
if err := db.WithContext(ctx).First(&user, *conversation.AssigneeID).Error; err == nil {
|
||||
payload["assignee"] = serializeUser(&user, conversation.AccountID)
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeSlaEvents(events []model.SlaEvent) []gin.H {
|
||||
payload := make([]gin.H, 0, len(events))
|
||||
for i := range events {
|
||||
event := events[i]
|
||||
payload = append(payload, gin.H{
|
||||
"id": event.ID,
|
||||
"event_type": event.EventType,
|
||||
"meta": jsonObject(event.Meta),
|
||||
"created_at": event.CreatedAt.Unix(),
|
||||
"updated_at": event.UpdatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func buildAppliedSlaCSV(c *gin.Context, db *gorm.DB, applied []model.AppliedSLA) string {
|
||||
var b strings.Builder
|
||||
w := csv.NewWriter(&b)
|
||||
_ = w.Write([]string{"Conversation ID", "SLA policy breached", "Assignee", "Team", "Inbox", "Labels", "Conversation link", "Breached events"})
|
||||
for i := range applied {
|
||||
item := &applied[i]
|
||||
conversation := findAppliedSlaConversation(c.Request.Context(), db, item.ConversationID)
|
||||
row := []string{"", item.SlaPolicy.Name, "", "", "", "", "", appliedSlaEventNames(item.SlaEvents)}
|
||||
if conversation != nil {
|
||||
displayID := conversationDisplayID(conversation)
|
||||
row[0] = strconv.FormatUint(uint64(displayID), 10)
|
||||
row[4] = lookupInboxName(c.Request.Context(), db, conversation.InboxID)
|
||||
row[5] = conversation.Labels
|
||||
row[6] = appliedSlaConversationURL(c, conversation.AccountID, displayID)
|
||||
if conversation.AssigneeID != nil {
|
||||
row[2] = lookupUserName(c.Request.Context(), db, *conversation.AssigneeID)
|
||||
}
|
||||
if conversation.TeamID != nil {
|
||||
row[3] = lookupTeamName(c.Request.Context(), db, *conversation.TeamID)
|
||||
}
|
||||
}
|
||||
_ = w.Write(row)
|
||||
}
|
||||
w.Flush()
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func findAppliedSlaConversation(ctx context.Context, db *gorm.DB, conversationID uint) *model.Conversation {
|
||||
if db == nil || conversationID == 0 {
|
||||
return nil
|
||||
}
|
||||
var conversation model.Conversation
|
||||
if err := db.WithContext(ctx).First(&conversation, conversationID).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
return &conversation
|
||||
}
|
||||
|
||||
func lookupUserName(ctx context.Context, db *gorm.DB, userID uint) string {
|
||||
if db == nil || userID == 0 {
|
||||
return ""
|
||||
}
|
||||
var user model.User
|
||||
if err := db.WithContext(ctx).First(&user, userID).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
return user.Name
|
||||
}
|
||||
|
||||
func lookupTeamName(ctx context.Context, db *gorm.DB, teamID uint) string {
|
||||
if db == nil || teamID == 0 {
|
||||
return ""
|
||||
}
|
||||
var team model.Team
|
||||
if err := db.WithContext(ctx).First(&team, teamID).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
return team.Name
|
||||
}
|
||||
|
||||
func lookupInboxName(ctx context.Context, db *gorm.DB, inboxID uint) string {
|
||||
if db == nil || inboxID == 0 {
|
||||
return ""
|
||||
}
|
||||
var inbox model.Inbox
|
||||
if err := db.WithContext(ctx).First(&inbox, inboxID).Error; err != nil {
|
||||
return ""
|
||||
}
|
||||
return inbox.Name
|
||||
}
|
||||
|
||||
func appliedSlaEventNames(events []model.SlaEvent) string {
|
||||
names := make([]string, 0, len(events))
|
||||
for i := range events {
|
||||
names = append(names, string(events[i].EventType))
|
||||
}
|
||||
return strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
func appliedSlaConversationURL(c *gin.Context, accountID uint, displayID uint) string {
|
||||
path := fmt.Sprintf("/app/accounts/%d/conversations/%d", accountID, displayID)
|
||||
if c.Request == nil || c.Request.Host == "" {
|
||||
return path
|
||||
}
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
return scheme + "://" + c.Request.Host + path
|
||||
}
|
||||
|
||||
// ListInboxes retrieves all inboxes associated with a SLA policy.
|
||||
// GET /api/v1/accounts/:account_id/sla_policies/:id/inboxes
|
||||
func (h *SlaPolicyHandler) ListInboxes(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid policy id")
|
||||
return
|
||||
}
|
||||
|
||||
inboxes, svcErr := h.svc.ListInboxes(c.Request.Context(), accountID, policyID)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("List inboxes for SLA policy %d, account %d: %v", policyID, accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, inboxes)
|
||||
}
|
||||
|
||||
// AddInbox associates an inbox with a SLA policy.
|
||||
// POST /api/v1/accounts/:account_id/sla_policies/:id/inboxes
|
||||
func (h *SlaPolicyHandler) AddInbox(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid policy id")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
InboxID uint `json:"inbox_id" validate:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
spi, svcErr := h.svc.AddInbox(c.Request.Context(), accountID, policyID, req.InboxID)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Add inbox %d to SLA policy %d, account %d: %v", req.InboxID, policyID, accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, spi)
|
||||
}
|
||||
|
||||
// RemoveInbox removes an inbox association from a SLA policy.
|
||||
// DELETE /api/v1/accounts/:account_id/sla_policies/:id/inboxes/:inbox_id
|
||||
func (h *SlaPolicyHandler) RemoveInbox(c *gin.Context) {
|
||||
accountID := getAccountID(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
|
||||
return
|
||||
}
|
||||
|
||||
policyID, err := parseUintParam(c, "id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid policy id")
|
||||
return
|
||||
}
|
||||
|
||||
inboxID, err := parseUintParam(c, "inbox_id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "invalid inbox id")
|
||||
return
|
||||
}
|
||||
|
||||
svcErr := h.svc.RemoveInbox(c.Request.Context(), accountID, policyID, inboxID)
|
||||
if svcErr != nil {
|
||||
applogger.L().Errorf("Remove inbox %d from SLA policy %d, account %d: %v", inboxID, policyID, accountID, svcErr)
|
||||
handleServiceError(c, svcErr)
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
Reference in New Issue
Block a user