Files
gochat/internal/service/inbox_member_service.go
T
2026-06-04 15:44:48 +08:00

159 lines
5.4 KiB
Go

package service
import (
"context"
"errors"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
applogger "github.com/gochat/gochat/pkg/logger"
pkgvalidator "github.com/gochat/gochat/pkg/validator"
)
// InboxMemberService implements business logic for InboxMember operations.
// Reference: Chatwoot app/models/inbox_member.rb
type InboxMemberService struct {
repo *repository.InboxMemberRepo
}
// NewInboxMemberService creates a new InboxMember service.
func NewInboxMemberService(repo *repository.InboxMemberRepo) *InboxMemberService {
return &InboxMemberService{repo: repo}
}
// GetByID retrieves a single inbox_member.
func (s *InboxMemberService) GetByID(ctx context.Context, id uint) (*model.InboxMember, error) {
return s.repo.FindByID(ctx, id)
}
// ListByInbox retrieves all members (agents) assigned to an inbox.
func (s *InboxMemberService) ListByInbox(ctx context.Context, inboxID uint) ([]model.InboxMember, error) {
return s.repo.FindByInbox(ctx, inboxID)
}
// ListByUser retrieves all inboxes a user is assigned to.
func (s *InboxMemberService) ListByUser(ctx context.Context, userID uint) ([]model.InboxMember, error) {
return s.repo.FindByUser(ctx, userID)
}
// AddMemberRequest is the DTO for adding a member to an inbox.
type AddMemberRequest struct {
InboxID uint `json:"inbox_id" validate:"required"`
UserID uint `json:"user_id" validate:"required"`
}
// AddMember assigns a user (agent) to an inbox.
func (s *InboxMemberService) AddMember(ctx context.Context, req AddMemberRequest) (*model.InboxMember, error) {
if err := pkgvalidator.ValidateStruct(req); err != nil {
return nil, err
}
// Check for duplicate
existing, err := s.repo.FindByInboxAndUser(ctx, req.InboxID, req.UserID)
if err == nil && existing != nil {
return nil, errors.New("user is already a member of this inbox")
}
im := &model.InboxMember{
InboxID: req.InboxID,
UserID: req.UserID,
}
if err := s.repo.Create(ctx, im); err != nil {
applogger.L().Errorf("InboxMemberService.AddMember failed: %v", err)
return nil, err
}
return im, nil
}
// RemoveMember removes a user from an inbox.
func (s *InboxMemberService) RemoveMember(ctx context.Context, inboxID, userID uint) error {
return s.repo.DeleteByInboxAndUser(ctx, inboxID, userID)
}
// RemoveAllMembers removes all members from an inbox.
func (s *InboxMemberService) RemoveAllMembers(ctx context.Context, inboxID uint) error {
return s.repo.DeleteByInbox(ctx, inboxID)
}
// UpdateMemberRequest is the DTO for updating a member's role/availability.
type UpdateMemberRequest struct {
Role string `json:"role" validate:"omitempty,oneof=agent supervisor"`
AvailabilityStatus string `json:"availability_status" validate:"omitempty,oneof=online offline busy"`
}
// UpdateMember updates a member's role or availability status.
func (s *InboxMemberService) UpdateMember(ctx context.Context, inboxID, userID uint, req UpdateMemberRequest) (*model.InboxMember, error) {
if err := pkgvalidator.ValidateStruct(req); err != nil {
return nil, err
}
updates := map[string]interface{}{}
if req.Role != "" {
updates["role"] = req.Role
}
if req.AvailabilityStatus != "" {
updates["availability_status"] = req.AvailabilityStatus
}
if len(updates) == 0 {
// Nothing to update, just return the existing record
return s.repo.FindByInboxAndUser(ctx, inboxID, userID)
}
if err := s.repo.UpdateByInboxAndUser(ctx, inboxID, userID, updates); err != nil {
applogger.L().Errorf("InboxMemberService.UpdateMember failed: %v", err)
return nil, err
}
return s.repo.FindByInboxAndUser(ctx, inboxID, userID)
}
// UpdateMultipleRequest is the DTO for batch-updating inbox members.
// Reference: Chatwoot inbox_members_controller#update (batch replace all members)
type UpdateMultipleRequest struct {
InboxID uint `json:"inbox_id" validate:"required"`
UserIDs []uint `json:"user_ids" validate:"required,min=1"`
}
// UpdateMultiple replaces all members of an inbox with the given user list.
// Reference: Chatwoot inbox_members#update — replaces entire member list in one call.
// This is the "seat assignment" batch update: remove all existing, then add new ones.
func (s *InboxMemberService) UpdateMultiple(ctx context.Context, req UpdateMultipleRequest) ([]model.InboxMember, error) {
if err := pkgvalidator.ValidateStruct(req); err != nil {
return nil, err
}
// Remove all existing members for this inbox
if err := s.RemoveAllMembers(ctx, req.InboxID); err != nil {
applogger.L().Errorf("InboxMemberService.UpdateMultiple: failed to remove existing members: %v", err)
return nil, err
}
// Add each new member
var members []model.InboxMember
for _, userID := range req.UserIDs {
im := &model.InboxMember{
InboxID: req.InboxID,
UserID: userID,
Role: "agent",
AvailabilityStatus: "online",
}
if err := s.repo.Create(ctx, im); err != nil {
applogger.L().Errorf("InboxMemberService.UpdateMultiple: failed to add member user_id=%d: %v", userID, err)
continue
}
members = append(members, *im)
}
return members, nil
}
// IsMemberOfInbox checks whether a user is a member (assigned agent) of an inbox.
// Used by ConversationService.AssignAgent to validate seat assignment routing.
func (s *InboxMemberService) IsMemberOfInbox(ctx context.Context, inboxID, userID uint) bool {
member, err := s.repo.FindByInboxAndUser(ctx, inboxID, userID)
if err != nil || member == nil {
return false
}
return true
}