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.
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user