116 lines
3.2 KiB
Go
116 lines
3.2 KiB
Go
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)
|
|
}
|