134 lines
4.5 KiB
Go
134 lines
4.5 KiB
Go
package service
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
)
|
||
|
||
type EmailService struct {
|
||
apiKey string
|
||
fromEmail string
|
||
fromName string
|
||
baseURL string
|
||
}
|
||
|
||
type ResendRequest struct {
|
||
From string `json:"from"`
|
||
To []string `json:"to"`
|
||
Subject string `json:"subject"`
|
||
HTML string `json:"html"`
|
||
}
|
||
|
||
func NewEmailService(apiKey, fromEmail, fromName, baseURL string) *EmailService {
|
||
return &EmailService{
|
||
apiKey: apiKey,
|
||
fromEmail: fromEmail,
|
||
fromName: fromName,
|
||
baseURL: baseURL,
|
||
}
|
||
}
|
||
|
||
func (s *EmailService) SendVerificationEmail(toEmail, username, token string) error {
|
||
verifyURL := fmt.Sprintf("%s/verify-email?token=%s", s.baseURL, token)
|
||
|
||
html := fmt.Sprintf(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<style>
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #FAF9F6; margin: 0; padding: 20px; }
|
||
.container { max-width: 500px; margin: 0 auto; padding: 40px; background: white; border-radius: 24px; }
|
||
h2 { color: #0f766e; margin-bottom: 8px; }
|
||
.button { display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #14b8a6, #0f766e); color: white; text-decoration: none; border-radius: 16px; font-weight: 600; }
|
||
.link { color: #0f766e; word-break: break-all; }
|
||
.footer { margin-top: 30px; font-size: 13px; color: #888; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h2>Привет, %s! 👋</h2>
|
||
<p>Спасибо за регистрацию в Pulse! Подтверди свой email, нажав на кнопку ниже:</p>
|
||
<p style="margin: 30px 0;"><a href="%s" class="button">Подтвердить email</a></p>
|
||
<p>Или скопируй ссылку:<br><a href="%s" class="link">%s</a></p>
|
||
<p class="footer">Ссылка действительна 24 часа. Если ты не регистрировался — просто проигнорируй это письмо.</p>
|
||
</div>
|
||
</body>
|
||
</html>`, username, verifyURL, verifyURL, verifyURL)
|
||
|
||
return s.send(toEmail, "Подтверди свой email — Pulse", html)
|
||
}
|
||
|
||
func (s *EmailService) SendPasswordResetEmail(toEmail, username, token string) error {
|
||
resetURL := fmt.Sprintf("%s/reset-password?token=%s", s.baseURL, token)
|
||
|
||
html := fmt.Sprintf(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<style>
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #FAF9F6; margin: 0; padding: 20px; }
|
||
.container { max-width: 500px; margin: 0 auto; padding: 40px; background: white; border-radius: 24px; }
|
||
h2 { color: #0f766e; margin-bottom: 8px; }
|
||
.button { display: inline-block; padding: 14px 28px; background: linear-gradient(135deg, #14b8a6, #0f766e); color: white; text-decoration: none; border-radius: 16px; font-weight: 600; }
|
||
.link { color: #0f766e; word-break: break-all; }
|
||
.footer { margin-top: 30px; font-size: 13px; color: #888; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h2>Сброс пароля</h2>
|
||
<p>Привет, %s! Мы получили запрос на сброс пароля для твоего аккаунта в Pulse.</p>
|
||
<p style="margin: 30px 0;"><a href="%s" class="button">Сбросить пароль</a></p>
|
||
<p>Или скопируй ссылку:<br><a href="%s" class="link">%s</a></p>
|
||
<p class="footer">Ссылка действительна 1 час. Если ты не запрашивал сброс пароля — просто проигнорируй это письмо.</p>
|
||
</div>
|
||
</body>
|
||
</html>`, username, resetURL, resetURL, resetURL)
|
||
|
||
return s.send(toEmail, "Сброс пароля — Pulse", html)
|
||
}
|
||
|
||
func (s *EmailService) send(to, subject, html string) error {
|
||
if s.apiKey == "" {
|
||
fmt.Printf("[EMAIL] Would send to %s: %s\n", to, subject)
|
||
return nil
|
||
}
|
||
|
||
payload := ResendRequest{
|
||
From: fmt.Sprintf("%s <%s>", s.fromName, s.fromEmail),
|
||
To: []string{to},
|
||
Subject: subject,
|
||
HTML: html,
|
||
}
|
||
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
req, err := http.NewRequest("POST", "https://api.resend.com/emails", bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
req.Header.Set("Authorization", "Bearer "+s.apiKey)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
client := &http.Client{}
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode >= 400 {
|
||
return fmt.Errorf("resend API error: %d", resp.StatusCode)
|
||
}
|
||
|
||
return nil
|
||
}
|