package service import ( "context" "errors" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" pkgvalidator "github.com/gochat/gochat/pkg/validator" ) // ContactNoteService implements business logic for ContactNote operations. // Reference: Chatwoot notes on contacts (Contacts::NoteAction) type ContactNoteService struct { contactRepo *repository.ContactRepo noteRepo *repository.ContactNoteRepo } // NewContactNoteService creates a new ContactNote service. func NewContactNoteService(contactRepo *repository.ContactRepo, noteRepo *repository.ContactNoteRepo) *ContactNoteService { return &ContactNoteService{contactRepo: contactRepo, noteRepo: noteRepo} } // ListNotes retrieves all notes for a contact. func (s *ContactNoteService) ListNotes(ctx context.Context, accountID, contactID uint) ([]model.ContactNote, error) { // Verify contact belongs to account _, err := s.contactRepo.FindByAccountAndID(ctx, accountID, contactID) if err != nil { return nil, errors.New("contact not found") } return s.noteRepo.FindByContact(ctx, contactID) } // NoteCreateRequest is the DTO for creating a contact note. // (Named NoteCreateRequest to avoid conflict with CreateNoteRequest in contact_service.go) type NoteCreateRequest struct { Content string `json:"content" validate:"required,min=1"` } // CreateNote creates a new note on a contact. func (s *ContactNoteService) CreateNote(ctx context.Context, accountID, contactID, userID uint, req NoteCreateRequest) (*model.ContactNote, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } // Verify contact belongs to account _, err := s.contactRepo.FindByAccountAndID(ctx, accountID, contactID) if err != nil { return nil, errors.New("contact not found") } note := &model.ContactNote{ ContactID: contactID, UserID: userID, Content: req.Content, } if err := s.noteRepo.Create(ctx, note); err != nil { return nil, err } // Reload to populate User relation return s.noteRepo.FindByID(ctx, note.ID) } // ReparentNotes moves all notes from one contact to another (used in contact merge). func (s *ContactNoteService) ReparentNotes(ctx context.Context, fromContactID, toContactID uint) error { return s.noteRepo.ReparentNotes(ctx, fromContactID, toContactID) } // NoteUpdateRequest is the DTO for updating a contact note. type NoteUpdateRequest struct { Content string `json:"content" validate:"required,min=1"` } // GetByID retrieves a single contact note by ID. func (s *ContactNoteService) GetByID(ctx context.Context, accountID, noteID uint) (*model.ContactNote, error) { note, err := s.noteRepo.FindByID(ctx, noteID) if err != nil { return nil, errors.New("note not found") } // Verify the note's contact belongs to the account _, err = s.contactRepo.FindByAccountAndID(ctx, accountID, note.ContactID) if err != nil { return nil, errors.New("note not found") } return note, nil } // UpdateNote updates the content of a contact note. func (s *ContactNoteService) UpdateNote(ctx context.Context, accountID, noteID uint, req NoteUpdateRequest) (*model.ContactNote, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } note, err := s.noteRepo.FindByID(ctx, noteID) if err != nil { return nil, errors.New("note not found") } // Verify the note's contact belongs to the account _, err = s.contactRepo.FindByAccountAndID(ctx, accountID, note.ContactID) if err != nil { return nil, errors.New("note not found") } note.Content = req.Content if err := s.noteRepo.Update(ctx, note); err != nil { return nil, err } // Reload to populate User relation return s.noteRepo.FindByID(ctx, note.ID) } // DeleteNote deletes a contact note. func (s *ContactNoteService) DeleteNote(ctx context.Context, accountID, noteID uint) error { note, err := s.noteRepo.FindByID(ctx, noteID) if err != nil { return errors.New("note not found") } // Verify the note's contact belongs to the account _, err = s.contactRepo.FindByAccountAndID(ctx, accountID, note.ContactID) if err != nil { return errors.New("note not found") } return s.noteRepo.Delete(ctx, noteID) }