Files
tm_back/internal/contact/service.go
T
n.nakhostin 875447ac58 Add Contact Management Functionality
- Introduced a new contact management feature, including the ability to create, retrieve, search, update, and delete contact messages.
- Implemented the Contact entity, repository, service, and handler layers following Clean Architecture principles.
- Added API endpoints for contact operations in Swagger documentation, ensuring comprehensive API specifications.
- Enhanced error handling and validation for contact data, improving robustness and user experience.
- Updated the main application to initialize the contact repository and service, integrating them into the existing system.
2025-10-25 18:15:39 +03:30

250 lines
7.0 KiB
Go

package contact
import (
"context"
"fmt"
"tm/pkg/logger"
orm "tm/pkg/mongo"
"tm/pkg/response"
)
// Service defines the interface for contact business operations
type Service interface {
// Create creates a new contact message
Create(ctx context.Context, form *CreateContactForm) (*ContactResponse, error)
// GetByID retrieves a contact by ID
GetByID(ctx context.Context, id string) (*ContactResponse, error)
// Search searches contacts with filters and pagination
Search(ctx context.Context, form *SearchContactsForm, pagination *response.Pagination) (*ContactListResponse, error)
// UpdateStatus updates the status of a contact (admin only)
UpdateStatus(ctx context.Context, id string, form *UpdateContactStatusForm, adminID string) error
// GetStats returns contact statistics (admin only)
GetStats(ctx context.Context) (*ContactStats, error)
// Delete deletes a contact by ID (admin only)
Delete(ctx context.Context, id string) error
}
// contactService implements Service interface
type contactService struct {
repo ContactRepository
logger logger.Logger
}
// NewService creates a new contact service
func NewService(repo ContactRepository, logger logger.Logger) Service {
return &contactService{
repo: repo,
logger: logger,
}
}
// Create creates a new contact message
func (s *contactService) Create(ctx context.Context, form *CreateContactForm) (*ContactResponse, error) {
s.logger.Info("Creating new contact message", map[string]interface{}{
"email": form.Email,
"full_name": form.FullName,
})
// Create contact entity
contact := &Contact{
FullName: form.FullName,
Email: form.Email,
Phone: form.Phone,
Message: form.Message,
Status: ContactStatusPending,
}
// Save to database
if err := s.repo.Create(ctx, contact); err != nil {
s.logger.Error("Failed to create contact", map[string]interface{}{
"email": form.Email,
"error": err.Error(),
})
return nil, fmt.Errorf("failed to create contact: %w", err)
}
s.logger.Info("Contact message created successfully", map[string]interface{}{
"id": contact.GetID(),
"email": form.Email,
})
return contact.ToResponse(), nil
}
// GetByID retrieves a contact by ID
func (s *contactService) GetByID(ctx context.Context, id string) (*ContactResponse, error) {
s.logger.Debug("Retrieving contact by ID", map[string]interface{}{
"id": id,
})
contact, err := s.repo.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get contact by ID", map[string]interface{}{
"id": id,
"error": err.Error(),
})
return nil, fmt.Errorf("failed to get contact: %w", err)
}
return contact.ToResponse(), nil
}
// Search searches contacts with filters and pagination
func (s *contactService) Search(ctx context.Context, form *SearchContactsForm, pagination *response.Pagination) (*ContactListResponse, error) {
s.logger.Debug("Searching contacts", map[string]interface{}{
"filters": form,
"pagination": pagination,
})
// Validate date range if provided
if form.DateFrom != nil && form.DateTo != nil && *form.DateFrom > *form.DateTo {
return nil, fmt.Errorf("invalid date range: date_from must be before date_to")
}
// Convert response.Pagination to orm.Pagination
ormPagination := orm.Pagination{
Limit: pagination.Limit,
Skip: pagination.Offset,
SortField: pagination.SortBy,
SortOrder: 1, // Default ascending
}
if pagination.SortOrder == "desc" {
ormPagination.SortOrder = -1
}
result, err := s.repo.Search(ctx, *form, ormPagination)
if err != nil {
s.logger.Error("Failed to search contacts", map[string]interface{}{
"filters": form,
"error": err.Error(),
})
return nil, fmt.Errorf("failed to search contacts: %w", err)
}
// Convert to response format
contactResponses := make([]*ContactResponse, len(result.Items))
for i, contact := range result.Items {
contactResponses[i] = contact.ToResponse()
}
s.logger.Info("Contact search completed", map[string]interface{}{
"total_count": result.TotalCount,
"items_count": len(result.Items),
})
return &ContactListResponse{
Contacts: contactResponses,
Meta: pagination.Response(result.TotalCount),
}, nil
}
// UpdateStatus updates the status of a contact (admin only)
func (s *contactService) UpdateStatus(ctx context.Context, id string, form *UpdateContactStatusForm, adminID string) error {
s.logger.Info("Updating contact status", map[string]interface{}{
"id": id,
"status": form.Status,
"admin_id": adminID,
})
// Validate status transition
contact, err := s.repo.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get contact for status update", map[string]interface{}{
"id": id,
"error": err.Error(),
})
return fmt.Errorf("failed to get contact: %w", err)
}
// Check if contact can be updated
if !contact.CanBeUpdated() {
s.logger.Warn("Attempted to update closed contact", map[string]interface{}{
"id": id,
"current_status": contact.Status,
"new_status": form.Status,
})
return fmt.Errorf("contact is closed and cannot be updated")
}
// Update status
status := ContactStatus(form.Status)
err = s.repo.UpdateStatus(ctx, id, status, form.AdminNotes, &adminID)
if err != nil {
s.logger.Error("Failed to update contact status", map[string]interface{}{
"id": id,
"status": form.Status,
"error": err.Error(),
})
return fmt.Errorf("failed to update contact status: %w", err)
}
s.logger.Info("Contact status updated successfully", map[string]interface{}{
"id": id,
"status": form.Status,
"admin_id": adminID,
})
return nil
}
// GetStats returns contact statistics (admin only)
func (s *contactService) GetStats(ctx context.Context) (*ContactStats, error) {
s.logger.Debug("Getting contact statistics", nil)
stats, err := s.repo.GetStats(ctx)
if err != nil {
s.logger.Error("Failed to get contact stats", map[string]interface{}{
"error": err.Error(),
})
return nil, fmt.Errorf("failed to get contact stats: %w", err)
}
s.logger.Info("Contact statistics retrieved", map[string]interface{}{
"total": stats.Total,
"pending": stats.Pending,
"in_progress": stats.InProgress,
"resolved": stats.Resolved,
"closed": stats.Closed,
})
return stats, nil
}
// Delete deletes a contact by ID (admin only)
func (s *contactService) Delete(ctx context.Context, id string) error {
s.logger.Info("Deleting contact", map[string]interface{}{
"id": id,
})
// Check if contact exists
contact, err := s.repo.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to get contact for deletion", map[string]interface{}{
"id": id,
"error": err.Error(),
})
return fmt.Errorf("failed to get contact: %w", err)
}
// Delete the contact
if err := s.repo.Delete(ctx, id); err != nil {
s.logger.Error("Failed to delete contact", map[string]interface{}{
"id": id,
"error": err.Error(),
})
return fmt.Errorf("failed to delete contact: %w", err)
}
s.logger.Info("Contact deleted successfully", map[string]interface{}{
"id": id,
"email": contact.Email,
})
return nil
}