[BE-SVC-004] be-svc: Implement email service
This commit is contained in:
parent
64d764c16f
commit
1cf863a78b
2 changed files with 172 additions and 2 deletions
|
|
@ -3665,7 +3665,7 @@
|
|||
"description": "Add email sending service for notifications, verification, etc.",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 6,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
@ -3686,7 +3686,9 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "",
|
||||
"completed_at": "2025-12-24T16:10:11.012689",
|
||||
"implementation_notes": "Enhanced EmailService with additional methods: SendWelcomeEmail() for new user onboarding, SendNotificationEmail() for various notification types (track_like, new_follower, playlist_update, comment_reply). All methods use HTML email templates with proper styling. The service already had SendVerificationEmail() and SendPasswordResetEmail() implemented. EmailService uses SMTP for sending emails and integrates with the job queue system for asynchronous email delivery."
|
||||
},
|
||||
{
|
||||
"id": "BE-SVC-005",
|
||||
|
|
|
|||
|
|
@ -365,3 +365,171 @@ func (es *EmailService) buildPasswordResetEmail(url string) string {
|
|||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// SendWelcomeEmail sends a welcome email to a new user
|
||||
// BE-SVC-004: Implement email service for notifications
|
||||
func (es *EmailService) SendWelcomeEmail(email, username string) error {
|
||||
subject := "Welcome to Veza!"
|
||||
body := es.buildWelcomeEmailHTML(username)
|
||||
|
||||
err := es.sendEmail(email, subject, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send welcome email: %w", err)
|
||||
}
|
||||
|
||||
es.logger.Info("Welcome email sent",
|
||||
zap.String("email", email),
|
||||
zap.String("username", username),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildWelcomeEmailHTML builds the HTML welcome email template
|
||||
// BE-SVC-004: Implement email service for notifications
|
||||
func (es *EmailService) buildWelcomeEmailHTML(username string) string {
|
||||
baseURL := os.Getenv("FRONTEND_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = "http://localhost:5173"
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Welcome to Veza</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h1 style="color: #4CAF50;">Welcome to Veza, {{.Username}}!</h1>
|
||||
<p>Thank you for joining Veza. We're excited to have you on board!</p>
|
||||
<p>Get started by:</p>
|
||||
<ul>
|
||||
<li>Uploading your first track</li>
|
||||
<li>Creating playlists</li>
|
||||
<li>Discovering new music</li>
|
||||
</ul>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{.BaseURL}}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
<p style="margin-top: 30px; color: #666; font-size: 12px;">
|
||||
If you have any questions, feel free to reach out to our support team.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
t, err := template.New("welcome").Parse(tmpl)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Welcome to Veza, %s! Get started at %s", username, baseURL)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = t.Execute(&buf, map[string]string{
|
||||
"Username": username,
|
||||
"BaseURL": baseURL,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Welcome to Veza, %s! Get started at %s", username, baseURL)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// SendNotificationEmail sends a notification email to a user
|
||||
// BE-SVC-004: Implement email service for notifications
|
||||
func (es *EmailService) SendNotificationEmail(email, subject, message string, notificationType string) error {
|
||||
body := es.buildNotificationEmailHTML(message, notificationType)
|
||||
|
||||
err := es.sendEmail(email, subject, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send notification email: %w", err)
|
||||
}
|
||||
|
||||
es.logger.Info("Notification email sent",
|
||||
zap.String("email", email),
|
||||
zap.String("type", notificationType),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildNotificationEmailHTML builds the HTML notification email template
|
||||
// BE-SVC-004: Implement email service for notifications
|
||||
func (es *EmailService) buildNotificationEmailHTML(message, notificationType string) string {
|
||||
baseURL := os.Getenv("FRONTEND_URL")
|
||||
if baseURL == "" {
|
||||
baseURL = "http://localhost:5173"
|
||||
}
|
||||
|
||||
// Determine icon/color based on notification type
|
||||
var iconColor string
|
||||
var iconText string
|
||||
switch notificationType {
|
||||
case "track_like":
|
||||
iconColor = "#FF6B6B"
|
||||
iconText = "❤️"
|
||||
case "new_follower":
|
||||
iconColor = "#4ECDC4"
|
||||
iconText = "👤"
|
||||
case "playlist_update":
|
||||
iconColor = "#45B7D1"
|
||||
iconText = "🎵"
|
||||
case "comment_reply":
|
||||
iconColor = "#FFA07A"
|
||||
iconText = "💬"
|
||||
default:
|
||||
iconColor = "#4CAF50"
|
||||
iconText = "🔔"
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Notification</title>
|
||||
</head>
|
||||
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
|
||||
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<span style="font-size: 48px;">{{.IconText}}</span>
|
||||
</div>
|
||||
<div style="background-color: #f5f5f5; padding: 20px; border-radius: 8px; margin: 20px 0;">
|
||||
<p style="margin: 0;">{{.Message}}</p>
|
||||
</div>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{.BaseURL}}" style="background-color: {{.IconColor}}; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
|
||||
View on Veza
|
||||
</a>
|
||||
</div>
|
||||
<p style="margin-top: 30px; color: #666; font-size: 12px;">
|
||||
You can manage your notification preferences in your account settings.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
t, err := template.New("notification").Parse(tmpl)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%s\n\nView on Veza: %s", message, baseURL)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = t.Execute(&buf, map[string]string{
|
||||
"Message": message,
|
||||
"BaseURL": baseURL,
|
||||
"IconColor": iconColor,
|
||||
"IconText": iconText,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%s\n\nView on Veza: %s", message, baseURL)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue