second commit
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
"github.com/gochat/gochat/pkg/pagination"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
)
|
||||
|
||||
// AccountHandler handles account-related API endpoints.
|
||||
// Reference: Chatwoot app/controllers/api/v1/accounts_controller.rb
|
||||
type AccountHandler struct {
|
||||
svc *service.AccountService
|
||||
}
|
||||
|
||||
// NewAccountHandler creates a new AccountHandler.
|
||||
func NewAccountHandler(svc *service.AccountService) *AccountHandler {
|
||||
return &AccountHandler{svc: svc}
|
||||
}
|
||||
|
||||
// @Summary List accounts accessible by the current user
|
||||
// @Description Returns all accounts the authenticated user has access to, with pagination support
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number" default(1)
|
||||
// @Param page_size query int false "Items per page" default(25)
|
||||
// @Success 200 {object} []model.Account
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts [get]
|
||||
func (h *AccountHandler) List(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
if userID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "user not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
accounts, total, err := h.svc.ListByUser(c.Request.Context(), userID, pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("List accounts: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list accounts")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, accounts, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// @Summary Get a single account by ID
|
||||
// @Description Retrieves detailed information about a specific account
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "Account ID"
|
||||
// @Success 200 {object} model.Account
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 404 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts/{id} [get]
|
||||
func (h *AccountHandler) Get(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.GetByID(c.Request.Context(), uint(id))
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Get account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusNotFound, response.ErrAccountNotFound, "account not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, account)
|
||||
}
|
||||
|
||||
// @Summary Create a new account
|
||||
// @Description Creates a new account and assigns the creator as administrator
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body service.CreateAccountRequest true "Account creation payload"
|
||||
// @Success 201 {object} model.Account
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts [post]
|
||||
func (h *AccountHandler) Create(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
if userID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "user not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.CreateAccountRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.Create(c.Request.Context(), userID, req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Create account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to create account")
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, account)
|
||||
}
|
||||
|
||||
// @Summary Update an existing account
|
||||
// @Description Modifies account details such as name and settings
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "Account ID"
|
||||
// @Param body body service.UpdateAccountRequest true "Account update payload"
|
||||
// @Success 200 {object} model.Account
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts/{id} [put]
|
||||
func (h *AccountHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateAccountRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.Update(c.Request.Context(), uint(id), req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Update account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update account")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, account)
|
||||
}
|
||||
|
||||
// @Summary Delete an account
|
||||
// @Description Soft-deletes an account by ID
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "Account ID"
|
||||
// @Success 204 "No Content"
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts/{id} [delete]
|
||||
func (h *AccountHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.Delete(c.Request.Context(), uint(id)); err != nil {
|
||||
applogger.L().Errorf("Delete account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to delete account")
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// ListUsers returns all users belonging to an account.
|
||||
// GET /api/v1/accounts/:id/users
|
||||
// Reference: Chatwoot Accounts::AccountUsersController#index
|
||||
func (h *AccountHandler) ListUsers(c *gin.Context) {
|
||||
accountID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
users, total, err := h.svc.ListUsers(c.Request.Context(), uint(accountID), pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("List account users: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list account users")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, users, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// AddUser adds a user to an account with a specified role.
|
||||
// POST /api/v1/accounts/:id/users
|
||||
// Reference: Chatwoot Accounts::AccountUsersController#create
|
||||
func (h *AccountHandler) AddUser(c *gin.Context) {
|
||||
accountID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.AddUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.AddUser(c.Request.Context(), uint(accountID), req); err != nil {
|
||||
applogger.L().Errorf("Add user to account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to add user to account")
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// RemoveUser removes a user from an account.
|
||||
// DELETE /api/v1/accounts/:id/users/:user_id
|
||||
// Reference: Chatwoot Accounts::AccountUsersController#destroy
|
||||
func (h *AccountHandler) RemoveUser(c *gin.Context) {
|
||||
accountID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid user id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.RemoveUser(c.Request.Context(), uint(accountID), uint(userID)); err != nil {
|
||||
applogger.L().Errorf("Remove user from account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to remove user from account")
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// GetAll lists all accounts (platform admin level, no user-scoping).
|
||||
// GET /api/v1/accounts/all
|
||||
// Reference: Chatwoot platform admin listing all accounts
|
||||
func (h *AccountHandler) GetAll(c *gin.Context) {
|
||||
pg := pagination.Parse(c)
|
||||
accounts, total, err := h.svc.GetAll(c.Request.Context(), pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("GetAll accounts: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list all accounts")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, accounts, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// GetAgents retrieves all agents (AccountUser records) for an account.
|
||||
// GET /api/v1/accounts/:id/agents
|
||||
// Reference: Chatwoot app/controllers/api/v1/accounts/agents_controller.rb#index
|
||||
func (h *AccountHandler) GetAgents(c *gin.Context) {
|
||||
accountID, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
agents, total, err := h.svc.GetAgents(c.Request.Context(), uint(accountID), pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("GetAgents for account %d: %v", accountID, err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list agents")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, agents, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// UpdateSettings updates account-level settings.
|
||||
// PUT /api/v1/accounts/:id/settings
|
||||
// Reference: Chatwoot AccountsController#update (settings subset)
|
||||
func (h *AccountHandler) UpdateSettings(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateAccountSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.UpdateSettings(c.Request.Context(), uint(id), req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Update account settings: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update account settings")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, account)
|
||||
}
|
||||
|
||||
// --- Account extension handlers (G8) ---
|
||||
// Reference: Chatwoot accounts_controller.rb#update_active_at, #cache_keys
|
||||
|
||||
// UpdateActiveAt updates the active_at timestamp for the current user in an account.
|
||||
// POST /api/v1/accounts/:id/update_active_at
|
||||
func (h *AccountHandler) UpdateActiveAt(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
if err := h.svc.UpdateActiveAt(c.Request.Context(), uint(id), userID); err != nil {
|
||||
applogger.L().Errorf("UpdateActiveAt for account %d, user %d: %v", id, userID, err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update active_at")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, gin.H{"message": "Active timestamp updated"})
|
||||
}
|
||||
|
||||
// CacheKeys returns cache key identifiers for frontend cache invalidation.
|
||||
// GET /api/v1/accounts/:id/cache_keys
|
||||
func (h *AccountHandler) CacheKeys(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
keys, err := h.svc.CacheKeys(c.Request.Context(), uint(id), userID)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("CacheKeys for account %d, user %d: %v", id, userID, err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to get cache keys")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, keys)
|
||||
}
|
||||
Reference in New Issue
Block a user