| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- package admin
- import (
- "html"
- "strings"
- "github.com/Wei-Shaw/sub2api/internal/handler/dto"
- "github.com/Wei-Shaw/sub2api/internal/pkg/response"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- // TestSMTPRequest 测试SMTP连接请求
- type TestSMTPRequest struct {
- SMTPHost string `json:"smtp_host"`
- SMTPPort int `json:"smtp_port"`
- SMTPUsername string `json:"smtp_username"`
- SMTPPassword string `json:"smtp_password"`
- SMTPUseTLS *bool `json:"smtp_use_tls"`
- }
- func resolveSMTPUseTLS(requested *bool, savedConfig *service.SMTPConfig) bool {
- if requested != nil {
- return *requested
- }
- return savedConfig != nil && savedConfig.UseTLS
- }
- // TestSMTPConnection 测试SMTP连接
- // POST /api/v1/admin/settings/test-smtp
- func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
- var req TestSMTPRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- req.SMTPHost = strings.TrimSpace(req.SMTPHost)
- req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
- var savedConfig *service.SMTPConfig
- if cfg, err := h.emailService.GetSMTPConfig(c.Request.Context()); err == nil && cfg != nil {
- savedConfig = cfg
- }
- if req.SMTPHost == "" && savedConfig != nil {
- req.SMTPHost = savedConfig.Host
- }
- if req.SMTPPort <= 0 {
- if savedConfig != nil && savedConfig.Port > 0 {
- req.SMTPPort = savedConfig.Port
- } else {
- req.SMTPPort = 587
- }
- }
- if req.SMTPUsername == "" && savedConfig != nil {
- req.SMTPUsername = savedConfig.Username
- }
- password := strings.TrimSpace(req.SMTPPassword)
- if password == "" && savedConfig != nil {
- password = savedConfig.Password
- }
- if req.SMTPHost == "" {
- response.BadRequest(c, "SMTP host is required")
- return
- }
- config := &service.SMTPConfig{
- Host: req.SMTPHost,
- Port: req.SMTPPort,
- Username: req.SMTPUsername,
- Password: password,
- UseTLS: resolveSMTPUseTLS(req.SMTPUseTLS, savedConfig),
- }
- err := h.emailService.TestSMTPConnectionWithConfig(config)
- if err != nil {
- response.BadRequest(c, "SMTP connection test failed: "+err.Error())
- return
- }
- response.Success(c, gin.H{"message": "SMTP connection successful"})
- }
- // SendTestEmailRequest 发送测试邮件请求
- type SendTestEmailRequest struct {
- Email string `json:"email" binding:"required,email"`
- SMTPHost string `json:"smtp_host"`
- SMTPPort int `json:"smtp_port"`
- SMTPUsername string `json:"smtp_username"`
- SMTPPassword string `json:"smtp_password"`
- SMTPFrom string `json:"smtp_from_email"`
- SMTPFromName string `json:"smtp_from_name"`
- SMTPUseTLS *bool `json:"smtp_use_tls"`
- }
- // SendTestEmail 发送测试邮件
- // POST /api/v1/admin/settings/send-test-email
- func (h *SettingHandler) SendTestEmail(c *gin.Context) {
- var req SendTestEmailRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- req.SMTPHost = strings.TrimSpace(req.SMTPHost)
- req.SMTPUsername = strings.TrimSpace(req.SMTPUsername)
- req.SMTPFrom = strings.TrimSpace(req.SMTPFrom)
- req.SMTPFromName = strings.TrimSpace(req.SMTPFromName)
- var savedConfig *service.SMTPConfig
- if cfg, err := h.emailService.GetSMTPConfig(c.Request.Context()); err == nil && cfg != nil {
- savedConfig = cfg
- }
- if req.SMTPHost == "" && savedConfig != nil {
- req.SMTPHost = savedConfig.Host
- }
- if req.SMTPPort <= 0 {
- if savedConfig != nil && savedConfig.Port > 0 {
- req.SMTPPort = savedConfig.Port
- } else {
- req.SMTPPort = 587
- }
- }
- if req.SMTPUsername == "" && savedConfig != nil {
- req.SMTPUsername = savedConfig.Username
- }
- password := strings.TrimSpace(req.SMTPPassword)
- if password == "" && savedConfig != nil {
- password = savedConfig.Password
- }
- if req.SMTPFrom == "" && savedConfig != nil {
- req.SMTPFrom = savedConfig.From
- }
- if req.SMTPFromName == "" && savedConfig != nil {
- req.SMTPFromName = savedConfig.FromName
- }
- if req.SMTPHost == "" {
- response.BadRequest(c, "SMTP host is required")
- return
- }
- config := &service.SMTPConfig{
- Host: req.SMTPHost,
- Port: req.SMTPPort,
- Username: req.SMTPUsername,
- Password: password,
- From: req.SMTPFrom,
- FromName: req.SMTPFromName,
- UseTLS: resolveSMTPUseTLS(req.SMTPUseTLS, savedConfig),
- }
- siteName := h.settingService.GetSiteName(c.Request.Context())
- subject := "[" + siteName + "] Test Email"
- body := `
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <style>
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px; }
- .container { max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
- .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; }
- .content { padding: 40px 30px; text-align: center; }
- .success { color: #10b981; font-size: 48px; margin-bottom: 20px; }
- .footer { background-color: #f8f9fa; padding: 20px; text-align: center; color: #999; font-size: 12px; }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>` + html.EscapeString(siteName) + `</h1>
- </div>
- <div class="content">
- <div class="success">✓</div>
- <h2>Email Configuration Successful!</h2>
- <p>This is a test email to verify your SMTP settings are working correctly.</p>
- </div>
- <div class="footer">
- <p>This is an automated test message.</p>
- </div>
- </div>
- </body>
- </html>
- `
- if err := h.emailService.SendEmailWithConfig(config, req.Email, subject, body); err != nil {
- response.BadRequest(c, "Failed to send test email: "+err.Error())
- return
- }
- response.Success(c, gin.H{"message": "Test email sent successfully"})
- }
- // ListEmailTemplates returns all editable notification email templates.
- // GET /api/v1/admin/settings/email-templates
- func (h *SettingHandler) ListEmailTemplates(c *gin.Context) {
- if h.notificationEmailService == nil {
- response.InternalError(c, "notification email service is not configured")
- return
- }
- events := h.notificationEmailService.ListEventInfos()
- templates, err := h.notificationEmailService.ListTemplates(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- response.Success(c, dto.EmailTemplateListResponse{
- Events: emailTemplateEventOptionsToDTO(events),
- Locales: h.notificationEmailService.SupportedLocales(),
- Templates: emailTemplateSummariesToDTO(templates),
- Placeholders: emailTemplatePlaceholderUnion(events),
- })
- }
- // GetEmailTemplate returns one editable notification email template.
- // GET /api/v1/admin/settings/email-templates/:event/:locale
- func (h *SettingHandler) GetEmailTemplate(c *gin.Context) {
- if h.notificationEmailService == nil {
- response.InternalError(c, "notification email service is not configured")
- return
- }
- tmpl, err := h.notificationEmailService.GetTemplate(c.Request.Context(), c.Param("event"), c.Param("locale"))
- if err != nil {
- response.BadRequest(c, err.Error())
- return
- }
- response.Success(c, emailTemplateDetailToDTO(tmpl))
- }
- // UpdateEmailTemplate saves an override for one event/locale template.
- // PUT /api/v1/admin/settings/email-templates/:event/:locale
- func (h *SettingHandler) UpdateEmailTemplate(c *gin.Context) {
- if h.notificationEmailService == nil {
- response.InternalError(c, "notification email service is not configured")
- return
- }
- var req dto.UpdateEmailTemplateRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- tmpl, err := h.notificationEmailService.UpdateTemplate(c.Request.Context(), c.Param("event"), c.Param("locale"), req.Subject, req.HTML)
- if err != nil {
- response.BadRequest(c, err.Error())
- return
- }
- response.Success(c, emailTemplateDetailToDTO(tmpl))
- }
- // RestoreOfficialEmailTemplate removes an override and returns the built-in template.
- // POST /api/v1/admin/settings/email-templates/:event/:locale/restore-official
- func (h *SettingHandler) RestoreOfficialEmailTemplate(c *gin.Context) {
- if h.notificationEmailService == nil {
- response.InternalError(c, "notification email service is not configured")
- return
- }
- tmpl, err := h.notificationEmailService.RestoreOfficialTemplate(c.Request.Context(), c.Param("event"), c.Param("locale"))
- if err != nil {
- response.BadRequest(c, err.Error())
- return
- }
- response.Success(c, emailTemplateDetailToDTO(tmpl))
- }
- // PreviewEmailTemplate renders a template with safe sample variables without saving it.
- // POST /api/v1/admin/settings/email-templates/preview
- func (h *SettingHandler) PreviewEmailTemplate(c *gin.Context) {
- if h.notificationEmailService == nil {
- response.InternalError(c, "notification email service is not configured")
- return
- }
- var req dto.PreviewEmailTemplateRequest
- if err := c.ShouldBindJSON(&req); err != nil {
- response.BadRequest(c, "Invalid request: "+err.Error())
- return
- }
- preview, err := h.notificationEmailService.PreviewTemplate(c.Request.Context(), service.NotificationEmailPreviewInput{
- Event: req.Event,
- Locale: req.Locale,
- Subject: req.Subject,
- HTML: req.HTML,
- Variables: req.Variables,
- })
- if err != nil {
- response.BadRequest(c, err.Error())
- return
- }
- response.Success(c, dto.EmailTemplatePreviewResponse{Subject: preview.Subject, HTML: preview.HTML})
- }
- func emailTemplateEventOptionsToDTO(events []service.NotificationEmailEventInfo) []dto.EmailTemplateEventOption {
- items := make([]dto.EmailTemplateEventOption, 0, len(events))
- for _, event := range events {
- items = append(items, dto.EmailTemplateEventOption{
- Value: event.Event,
- Label: event.Label,
- Description: event.Description,
- Category: event.Category,
- Optional: event.Optional,
- })
- }
- return items
- }
- func emailTemplateSummariesToDTO(templates []service.NotificationEmailTemplate) []dto.EmailTemplateSummary {
- items := make([]dto.EmailTemplateSummary, 0, len(templates))
- for _, tmpl := range templates {
- items = append(items, dto.EmailTemplateSummary{
- Event: tmpl.Event,
- Locale: tmpl.Locale,
- Subject: tmpl.Subject,
- IsCustom: tmpl.IsCustom,
- UpdatedAt: emailTemplateUpdatedAt(tmpl),
- })
- }
- return items
- }
- func emailTemplateDetailToDTO(tmpl service.NotificationEmailTemplate) dto.EmailTemplateDetail {
- return dto.EmailTemplateDetail{
- Event: tmpl.Event,
- Locale: tmpl.Locale,
- Subject: tmpl.Subject,
- HTML: tmpl.HTML,
- IsCustom: tmpl.IsCustom,
- UpdatedAt: emailTemplateUpdatedAt(tmpl),
- Placeholders: tmpl.Placeholders,
- }
- }
- func emailTemplateUpdatedAt(tmpl service.NotificationEmailTemplate) string {
- if tmpl.UpdatedAt == nil {
- return ""
- }
- return tmpl.UpdatedAt.Format("2006-01-02T15:04:05Z07:00")
- }
- func emailTemplatePlaceholderUnion(events []service.NotificationEmailEventInfo) []string {
- seen := make(map[string]struct{})
- placeholders := make([]string, 0)
- for _, event := range events {
- for _, placeholder := range event.Placeholders {
- if _, ok := seen[placeholder]; ok {
- continue
- }
- seen[placeholder] = struct{}{}
- placeholders = append(placeholders, placeholder)
- }
- }
- return placeholders
- }
|