package services import ( "context" "database/sql" "encoding/json" "fmt" "log" amqp "github.com/rabbitmq/amqp091-go" ) // EmailService defines interface for email operations type EmailService interface { SendEmail(to, subject, body string) error SendTemplateEmail(ctx context.Context, to, templateSlug string, variables map[string]interface{}) error } type emailServiceImpl struct { db *sql.DB credentialsService *CredentialsService } func NewEmailService(db *sql.DB, cs *CredentialsService) EmailService { return &emailServiceImpl{ db: db, credentialsService: cs, } } type EmailJob struct { To string `json:"to"` Template string `json:"template"` // slug Variables map[string]interface{} `json:"variables"` } // SendTemplateEmail queues an email via RabbitMQ func (s *emailServiceImpl) SendTemplateEmail(ctx context.Context, to, templateSlug string, variables map[string]interface{}) error { // 1. Get AMQP URL from email_settings var amqpURL sql.NullString err := s.db.QueryRowContext(ctx, "SELECT amqp_url FROM email_settings LIMIT 1").Scan(&amqpURL) if err != nil && err != sql.ErrNoRows { log.Printf("[EmailService] Failed to fetch AMQP URL: %v", err) } url := "" if amqpURL.Valid { url = amqpURL.String } if url == "" { // Log but don't error hard if just testing, but for "system" we need IT. // Return error so caller knows. return fmt.Errorf("AMQP URL not configured in email_settings") } // 2. Connect & Publish conn, err := amqp.Dial(url) if err != nil { return fmt.Errorf("failed to connect to RabbitMQ: %w", err) } defer conn.Close() ch, err := conn.Channel() if err != nil { return fmt.Errorf("failed to open channel: %w", err) } defer ch.Close() q, err := ch.QueueDeclare( "mail_queue", // name true, // durable false, // delete when unused false, // exclusive false, // no-wait nil, // arguments ) if err != nil { return fmt.Errorf("failed to declare queue: %w", err) } job := EmailJob{To: to, Template: templateSlug, Variables: variables} body, _ := json.Marshal(job) err = ch.PublishWithContext(ctx, "", // exchange q.Name, // routing key false, // mandatory false, // immediate amqp.Publishing{ ContentType: "application/json", Body: body, }) if err != nil { return fmt.Errorf("failed to publish message: %w", err) } log.Printf("[EmailService] Queued email to %s (Template: %s)", to, templateSlug) return nil } // SendEmail queues a simple email via RabbitMQ (using default template or direct logic) // For compatibility with HEAD interface func (s *emailServiceImpl) SendEmail(to, subject, body string) error { // Wrap simpler calls into template engine if needed, or implement direct send. // For now, we reuse SendTemplateEmail with a "generic" template or similar. // Or we create a specific job for raw email. // Let's assume we map it to a "generic_notification" template vars := map[string]interface{}{ "subject": subject, "body": body, } // Using background context for simple interface return s.SendTemplateEmail(context.Background(), to, "generic_notification", vars) }