package service import ( "context" "crypto/rand" "encoding/hex" "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" ) // ContactInboxService implements business logic for ContactInbox operations. // Reference: Chatwoot app/models/contact_inbox.rb type ContactInboxService struct { repo *repository.ContactInboxRepo } // NewContactInboxService creates a new ContactInbox service. func NewContactInboxService(repo *repository.ContactInboxRepo) *ContactInboxService { return &ContactInboxService{repo: repo} } // GetByID retrieves a single contact_inbox. func (s *ContactInboxService) GetByID(ctx context.Context, id uint) (*model.ContactInbox, error) { return s.repo.FindByID(ctx, id) } // GetByContactAndInbox retrieves a contact_inbox by contact and inbox. func (s *ContactInboxService) GetByContactAndInbox(ctx context.Context, contactID, inboxID uint) (*model.ContactInbox, error) { return s.repo.FindByContactAndInbox(ctx, contactID, inboxID) } // ListByContact retrieves all contact_inboxes for a contact. func (s *ContactInboxService) ListByContact(ctx context.Context, contactID uint) ([]model.ContactInbox, error) { return s.repo.FindByContact(ctx, contactID) } // ListByInbox retrieves all contact_inboxes for an inbox (paginated). func (s *ContactInboxService) ListByInbox(ctx context.Context, inboxID uint, offset, limit int) ([]model.ContactInbox, int64, error) { return s.repo.FindByInbox(ctx, inboxID, offset, limit) } // GetBySourceID retrieves a contact_inbox by source_id and inbox. func (s *ContactInboxService) GetBySourceID(ctx context.Context, inboxID uint, sourceID string) (*model.ContactInbox, error) { return s.repo.FindBySourceID(ctx, inboxID, sourceID) } // CreateContactInboxRequest is the DTO for creating a contact_inbox. type CreateContactInboxRequest struct { ContactID uint `json:"contact_id" validate:"required"` InboxID uint `json:"inbox_id" validate:"required"` SourceID string `json:"source_id" validate:"required"` } // Create adds a new contact_inbox join record. func (s *ContactInboxService) Create(ctx context.Context, req CreateContactInboxRequest) (*model.ContactInbox, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } // Check for duplicate existing, err := s.repo.FindByContactAndInbox(ctx, req.ContactID, req.InboxID) if err == nil && existing != nil { return nil, errors.New("contact_inbox already exists for this contact and inbox") } hmacToken, err := generateToken(24) if err != nil { return nil, err } pubsubToken, err := generateToken(24) if err != nil { return nil, err } ci := &model.ContactInbox{ ContactID: req.ContactID, InboxID: req.InboxID, SourceID: req.SourceID, HMACToken: hmacToken, PubsubToken: pubsubToken, } if err := s.repo.Create(ctx, ci); err != nil { applogger.L().Errorf("ContactInboxService.Create failed: %v", err) return nil, err } return ci, nil } // Delete removes a contact_inbox by ID. func (s *ContactInboxService) Delete(ctx context.Context, id uint) error { return s.repo.Delete(ctx, id) } // DeleteByContactAndInbox removes the join record. func (s *ContactInboxService) DeleteByContactAndInbox(ctx context.Context, contactID, inboxID uint) error { return s.repo.DeleteByContactAndInbox(ctx, contactID, inboxID) } // FilterContactInboxes retrieves contact_inboxes by account with optional filters. // GET /api/v1/accounts/:id/contact_inboxes/filter // Reference: Chatwoot contact_inboxes_controller#filter func (s *ContactInboxService) FilterContactInboxes(ctx context.Context, accountID uint, inboxID, contactID *uint, sourceID string, offset, limit int) ([]model.ContactInbox, int64, error) { return s.repo.Filter(ctx, accountID, inboxID, contactID, sourceID, offset, limit) } // generateToken creates a random hex token of the specified byte length. func generateToken(byteLen int) (string, error) { b := make([]byte, byteLen) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil }