209 lines
6.8 KiB
Go
209 lines
6.8 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"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
|
|
}
|
|
|
|
existing, err := s.repo.FindByInboxAndUser(ctx, req.InboxID, req.UserID)
|
|
if err == nil && existing != nil {
|
|
return existing, nil
|
|
}
|
|
|
|
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 s.repo.FindByInboxAndUser(ctx, req.InboxID, req.UserID)
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// AddMembers adds the requested users without removing existing members.
|
|
// Reference: Chatwoot inbox_members#create - add_members(agents_to_be_added_ids).
|
|
func (s *InboxMemberService) AddMembers(ctx context.Context, req UpdateMultipleRequest) ([]model.InboxMember, error) {
|
|
if err := pkgvalidator.ValidateStruct(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
current, err := s.repo.FindByInbox(ctx, req.InboxID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
currentIDs := map[uint]bool{}
|
|
for _, member := range current {
|
|
currentIDs[member.UserID] = true
|
|
}
|
|
|
|
for _, userID := range uniqueUintIDs(req.UserIDs) {
|
|
if currentIDs[userID] {
|
|
continue
|
|
}
|
|
im := &model.InboxMember{InboxID: req.InboxID, UserID: userID, Role: "agent", AvailabilityStatus: "offline"}
|
|
if err := s.repo.Create(ctx, im); err != nil {
|
|
applogger.L().Errorf("InboxMemberService.AddMembers: failed to add member user_id=%d: %v", userID, err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return s.repo.FindByInbox(ctx, req.InboxID)
|
|
}
|
|
|
|
// 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 calculates the add/remove diff instead of blindly recreating rows.
|
|
func (s *InboxMemberService) UpdateMultiple(ctx context.Context, req UpdateMultipleRequest) ([]model.InboxMember, error) {
|
|
if err := pkgvalidator.ValidateStruct(req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
current, err := s.repo.FindByInbox(ctx, req.InboxID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
desiredIDs := map[uint]bool{}
|
|
for _, userID := range uniqueUintIDs(req.UserIDs) {
|
|
desiredIDs[userID] = true
|
|
}
|
|
currentIDs := map[uint]bool{}
|
|
for _, member := range current {
|
|
currentIDs[member.UserID] = true
|
|
if !desiredIDs[member.UserID] {
|
|
if err := s.RemoveMember(ctx, req.InboxID, member.UserID); err != nil {
|
|
applogger.L().Errorf("InboxMemberService.UpdateMultiple: failed to remove member user_id=%d: %v", member.UserID, err)
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
for userID := range desiredIDs {
|
|
if currentIDs[userID] {
|
|
continue
|
|
}
|
|
im := &model.InboxMember{InboxID: req.InboxID, UserID: userID, Role: "agent", AvailabilityStatus: "offline"}
|
|
if err := s.repo.Create(ctx, im); err != nil {
|
|
applogger.L().Errorf("InboxMemberService.UpdateMultiple: failed to add member user_id=%d: %v", userID, err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return s.repo.FindByInbox(ctx, req.InboxID)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
func uniqueUintIDs(ids []uint) []uint {
|
|
seen := map[uint]bool{}
|
|
result := make([]uint, 0, len(ids))
|
|
for _, id := range ids {
|
|
if id == 0 || seen[id] {
|
|
continue
|
|
}
|
|
seen[id] = true
|
|
result = append(result, id)
|
|
}
|
|
return result
|
|
}
|