Files
tm_back/internal/contact/service.go
T
2026-05-17 17:21:36 +03:30

287 lines
8.3 KiB
Go

package contact
import (
"context"
"fmt"
"tm/pkg/logger"
"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, &notification.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, &notification.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")
}
result, err := s.repo.Search(ctx, *form, pagination)
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.ListMeta(result.TotalCount, result.NextCursor, result.HasMore, result.PageOffset),
}, 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
}