Files
gochat/backend/internal/handler/api/v1/team_handler.go
T

323 lines
9.8 KiB
Go

package v1
import (
"net/http"
"strconv"
"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/pagination"
"github.com/gochat/gochat/pkg/response"
)
// TeamHandler handles Team CRUD + members.
// Reference: Chatwoot app/controllers/api/v1/teams_controller.rb
type TeamHandler struct {
svc *service.TeamService
}
// NewTeamHandler creates a new Team handler.
func NewTeamHandler(svc *service.TeamService) *TeamHandler {
return &TeamHandler{svc: svc}
}
// List returns all teams for an account.
// GET /api/v1/accounts/:account_id/teams
func (h *TeamHandler) List(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
pg := pagination.Parse(c)
teams, total, err := h.svc.List(c.Request.Context(), accountID, pg.Offset, pg.PerPage)
if err != nil {
applogger.L().Errorf("List teams for account %d: %v", accountID, err)
handleServiceError(c, err)
return
}
_ = total
c.JSON(http.StatusOK, serializeTeams(c, h.svc, teams))
}
// Get returns a single team by ID.
// GET /api/v1/accounts/:account_id/teams/:id
func (h *TeamHandler) Get(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
id, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
team, svcErr := h.svc.Get(c.Request.Context(), uint(id), accountID)
if svcErr != nil {
applogger.L().Errorf("Get team %d for account %d: %v", id, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeTeamForAccount(c, h.svc, team))
}
// Create creates a new team within an account.
// POST /api/v1/accounts/:account_id/teams
func (h *TeamHandler) Create(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
var req service.CreateTeamRequest
if err := bindJSONWrappedOrRaw(c, "team", &req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
return
}
team, svcErr := h.svc.Create(c.Request.Context(), accountID, req)
if svcErr != nil {
applogger.L().Errorf("Create team for account %d: %v", accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeTeamForAccount(c, h.svc, team))
}
// Update updates an existing team.
// PUT /api/v1/accounts/:account_id/teams/:id
func (h *TeamHandler) Update(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
id, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
var req service.UpdateTeamRequest
if err := bindJSONWrappedOrRaw(c, "team", &req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
return
}
team, svcErr := h.svc.Update(c.Request.Context(), uint(id), accountID, req)
if svcErr != nil {
applogger.L().Errorf("Update team %d for account %d: %v", id, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeTeamForAccount(c, h.svc, team))
}
// Delete soft-deletes a team.
// DELETE /api/v1/accounts/:account_id/teams/:id
func (h *TeamHandler) Delete(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
id, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
if svcErr := h.svc.Delete(c.Request.Context(), uint(id), accountID); svcErr != nil {
applogger.L().Errorf("Delete team %d for account %d: %v", id, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.Status(http.StatusOK)
}
// AddMembers adds users to a team.
// POST /api/v1/accounts/:account_id/teams/:id/members
func (h *TeamHandler) AddMembers(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
id, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
var req service.TeamMemberRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
return
}
_, svcErr := h.svc.AddMembers(c.Request.Context(), uint(id), accountID, req.UserIDs)
if svcErr != nil {
applogger.L().Errorf("Add members to team %d for account %d: %v", id, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
members, svcErr := h.svc.ListMembers(c.Request.Context(), uint(id), accountID)
if svcErr != nil {
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeTeamMemberAgents(accountID, members))
}
// RemoveMembers removes a user from a team.
// DELETE /api/v1/accounts/:account_id/teams/:id/members/:user_id
func (h *TeamHandler) RemoveMembers(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
id, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
userID, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid user ID")
return
}
if svcErr := h.svc.RemoveMember(c.Request.Context(), uint(id), uint(userID), accountID); svcErr != nil {
applogger.L().Errorf("Remove member %d from team %d for account %d: %v", userID, id, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.Status(http.StatusOK)
}
// ListMembers retrieves all members of a team.
// GET /api/v1/accounts/:account_id/teams/:id/members
func (h *TeamHandler) ListMembers(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
id, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
members, svcErr := h.svc.ListMembers(c.Request.Context(), uint(id), accountID)
if svcErr != nil {
applogger.L().Errorf("List members for team %d for account %d: %v", id, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeTeamMemberAgents(accountID, members))
}
// UpdateMembers adds/removes members to match the provided user_ids list.
// PATCH /api/v1/accounts/:account_id/teams/:team_id/team_members
// Reference: Chatwoot team_members#update — calculates add/remove diffs from user_ids
func (h *TeamHandler) UpdateMembers(c *gin.Context) {
accountID := getAccountID(c)
if accountID == 0 {
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified")
return
}
teamID, err := parseTeamIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid team ID")
return
}
var req service.TeamMemberRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, err.Error())
return
}
members, svcErr := h.svc.UpdateMembers(c.Request.Context(), uint(teamID), accountID, req.UserIDs)
if svcErr != nil {
applogger.L().Errorf("Update members for team %d for account %d: %v", teamID, accountID, svcErr)
handleServiceError(c, svcErr)
return
}
c.JSON(http.StatusOK, serializeTeamMemberAgents(accountID, members))
}
func parseTeamIDParam(c *gin.Context) (uint64, error) {
value := c.Param("team_id")
if value == "" {
value = c.Param("id")
}
return strconv.ParseUint(value, 10, 32)
}
func serializeTeams(c *gin.Context, svc *service.TeamService, teams []model.Team) []map[string]any {
payload := make([]map[string]any, 0, len(teams))
for i := range teams {
payload = append(payload, serializeTeamForAccount(c, svc, &teams[i]))
}
return payload
}
func serializeTeamForAccount(c *gin.Context, svc *service.TeamService, team *model.Team) map[string]any {
if team == nil {
return map[string]any{}
}
payload := map[string]any{
"id": team.ID,
"name": team.Name,
"description": team.Description,
"allow_auto_assign": team.AllowAutoAssignment,
"icon": team.Icon,
"icon_color": team.IconColor,
"account_id": team.AccountID,
"is_member": false,
}
userID := getUserID(c)
if userID != 0 && svc != nil && svc.DB() != nil {
var count int64
svc.DB().WithContext(c.Request.Context()).Model(&model.TeamMember{}).Where("team_id = ? AND user_id = ?", team.ID, userID).Count(&count)
payload["is_member"] = count > 0
}
return payload
}
func serializeTeamMemberAgents(accountID uint, members []model.TeamMember) []map[string]any {
payload := make([]map[string]any, 0, len(members))
for i := range members {
member := members[i]
payload = append(payload, serializeAgentUser(&member.User, accountID, "", member.AvailabilityStatus, false, 0))
}
return payload
}