package contact import ( "context" "fmt" "tm/pkg/logger" orm "tm/pkg/mongo" "tm/pkg/notification" "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 sdk notification.SDK } // NewService creates a new contact service func NewService(repo ContactRepository, logger logger.Logger, sdk notification.SDK) Service { return &contactService{ repo: repo, logger: logger, sdk: sdk, } } // 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) } // Send confirmation email s.sdk.SendNotification(ctx, ¬ification.NotificationRequest{ UserID: contact.GetID(), Title: "Contact Form Submission Confirmation", Message: GenerateContactConfirmationEmailTemplate(&ContactEmailTemplateData{ FullName: contact.FullName, Email: contact.Email, Phone: contact.Phone, Message: contact.Message, ContactID: contact.GetID(), SubmittedAt: contact.CreatedAt, CompanyName: "Opplens", CompanyURL: "https://opplens.com", SupportEmail: "info@opplens.com", }), Type: "confirmation", Priority: "normal", EventType: notification.EventTypeEmail, Methods: notification.NotificationMethods{ Email: contact.Email, }, }) // Notify admin/internal team s.sdk.SendNotification(ctx, ¬ification.NotificationRequest{ UserID: contact.GetID(), Title: fmt.Sprintf("New Contact Form Submission from %s", contact.FullName), Message: GenerateContactConfirmationEmailTemplate(&ContactEmailTemplateData{ FullName: contact.FullName, Email: contact.Email, Phone: contact.Phone, Message: contact.Message, ContactID: contact.GetID(), SubmittedAt: contact.CreatedAt, CompanyName: "Opplens", CompanyURL: "https://opplens.com", SupportEmail: "info@opplens.com", }), Type: "alert", Priority: "normal", EventType: notification.EventTypeEmail, Methods: notification.NotificationMethods{ Email: "info@opplens.com", }, }) 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 }