287 lines
8.3 KiB
Go
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, ¬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")
|
|
}
|
|
|
|
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
|
|
}
|