package v1 import ( "net/http" "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 := parseAccountIDParam(c) if id == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } account, err := h.svc.GetByID(c.Request.Context(), 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 := parseAccountIDParam(c) if id == 0 { 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(), 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 := parseAccountIDParam(c) if id == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } if err := h.svc.Delete(c.Request.Context(), 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 := parseAccountIDParam(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } pg := pagination.Parse(c) users, total, err := h.svc.ListUsers(c.Request.Context(), 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 := parseAccountIDParam(c) if accountID == 0 { 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(), 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 := parseAccountIDParam(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } userID, err := parseUintParam(c, "user_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid user id") return } if err := h.svc.RemoveUser(c.Request.Context(), accountID, 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 := parseAccountIDParam(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } pg := pagination.Parse(c) agents, total, err := h.svc.GetAgents(c.Request.Context(), 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 := parseAccountIDParam(c) if id == 0 { 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(), 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 := parseAccountIDParam(c) if id == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } userID := getUserID(c) if err := h.svc.UpdateActiveAt(c.Request.Context(), 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 := parseAccountIDParam(c) if id == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id") return } userID := getUserID(c) keys, err := h.svc.CacheKeys(c.Request.Context(), 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) }