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/internal/service" "github.com/gochat/gochat/pkg/pagination" "github.com/gochat/gochat/pkg/response" ) // PlatformAccountHandler handles Platform API account endpoints (AccessToken auth). // Reference: Chatwoot Platform::Api::V1::AccountsController — AccessToken authenticated // // Uses Permissible system: PlatformApp must have permissible access to operate // on specific accounts. Create/index auto-permissible. type PlatformAccountHandler struct { accountRepo *repository.AccountRepo permissibleRepo *repository.PermissibleRepo accountSvc *service.AccountService } // NewPlatformAccountHandler creates a new PlatformAccount handler. func NewPlatformAccountHandler( accountRepo *repository.AccountRepo, permissibleRepo *repository.PermissibleRepo, accountSvc *service.AccountService, ) *PlatformAccountHandler { return &PlatformAccountHandler{ accountRepo: accountRepo, permissibleRepo: permissibleRepo, accountSvc: accountSvc, } } // List returns all accounts the PlatformApp has permissible access to. // GET /platform/api/v1/accounts // Reference: Chatwoot Platform::Api::V1::AccountsController#index func (h *PlatformAccountHandler) List(c *gin.Context) { platformAppID := getPlatformAppID(c) page := pagination.Parse(c) permissibles, err := h.permissibleRepo.FindByPlatformAppID(c.Request.Context(), platformAppID) if err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } var accounts []model.Account var total int64 for _, perm := range permissibles { if perm.PermissibleType == model.PermissibleTypeAccount { acct, err := h.accountRepo.FindByID(c.Request.Context(), perm.PermissibleID) if err != nil { continue } accounts = append(accounts, *acct) } } total = int64(len(accounts)) start := page.Offset if start > len(accounts) { start = len(accounts) } end := start + page.PerPage if end > len(accounts) { end = len(accounts) } response.OKWithMeta(c, accounts[start:end], page.Page, page.PerPage, total) } // Show retrieves an account by ID. // GET /platform/api/v1/accounts/:id // Reference: Chatwoot Platform::Api::V1::AccountsController#show // Requires: Permissible verification func (h *PlatformAccountHandler) Show(c *gin.Context) { accountID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAccount, accountID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } acct, err := h.accountRepo.FindByID(c.Request.Context(), accountID) if err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "account not found") return } response.OK(c, acct) } // Create creates a new account and auto-creates Permissible record. // POST /platform/api/v1/accounts // Reference: Chatwoot Platform::Api::V1::AccountsController#create // Auto-permissible: PlatformApp automatically gets access to the created account. func (h *PlatformAccountHandler) Create(c *gin.Context) { platformAppID := getPlatformAppID(c) var req struct { Name string `json:"name" binding:"required"` Domain string `json:"domain,omitempty"` Locale string `json:"locale,omitempty"` Timezone string `json:"timezone,omitempty"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } acct := &model.Account{ Name: req.Name, Domain: req.Domain, Locale: defaultStr(req.Locale, "en"), Timezone: defaultStr(req.Timezone, "UTC"), Active: true, Status: "active", } if err := h.accountRepo.Create(c.Request.Context(), acct); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } // Auto-create Permissible perm := &model.Permissible{ PlatformAppID: platformAppID, PermissibleType: model.PermissibleTypeAccount, PermissibleID: acct.ID, } if err := h.permissibleRepo.Create(c.Request.Context(), perm); err != nil { // Non-critical — log but don't block account creation } response.Created(c, acct) } // Update updates an account. // PATCH /platform/api/v1/accounts/:id // Reference: Chatwoot Platform::Api::V1::AccountsController#update // Requires: Permissible verification func (h *PlatformAccountHandler) Update(c *gin.Context) { accountID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAccount, accountID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } acct, err := h.accountRepo.FindByID(c.Request.Context(), accountID) if err != nil { response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "account not found") return } var req struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` Locale string `json:"locale,omitempty"` Timezone string `json:"timezone,omitempty"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error()) return } if req.Name != "" { acct.Name = req.Name } if req.Domain != "" { acct.Domain = req.Domain } if req.Locale != "" { acct.Locale = req.Locale } if req.Timezone != "" { acct.Timezone = req.Timezone } if err := h.accountRepo.Update(c.Request.Context(), acct); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } response.OK(c, acct) } // Destroy deletes an account. // DELETE /platform/api/v1/accounts/:id // Reference: Chatwoot Platform::Api::V1::AccountsController#destroy // Requires: Permissible verification func (h *PlatformAccountHandler) Destroy(c *gin.Context) { accountID, err := parseUintParam(c, "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account ID") return } platformAppID := getPlatformAppID(c) // Verify Permissible access perm, err := h.permissibleRepo.FindByPlatformAppAndResource(c.Request.Context(), platformAppID, model.PermissibleTypeAccount, accountID) if err != nil || perm == nil { response.AbortWithStatusError(c, http.StatusForbidden, response.ErrForbidden, "non permissible resource") return } if err := h.accountRepo.Delete(c.Request.Context(), accountID); err != nil { response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } response.NoContent(c) } // --- Helper --- func defaultStr(val, fallback string) string { if val == "" { return fallback } return val }