diff --git a/cmd/answer/wire_gen.go b/cmd/answer/wire_gen.go index 9097d841..661fb031 100644 --- a/cmd/answer/wire_gen.go +++ b/cmd/answer/wire_gen.go @@ -163,7 +163,7 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database, reasonController := controller.NewReasonController(reasonService) themeController := controller_backyard.NewThemeController() siteInfoRepo := repo.NewSiteInfo(dataData) - siteInfoService := service.NewSiteInfoService(siteInfoRepo) + siteInfoService := service.NewSiteInfoService(siteInfoRepo, emailService) siteInfoController := controller_backyard.NewSiteInfoController(siteInfoService) siteinfoController := controller.NewSiteinfoController(siteInfoService) notificationRepo := notification.NewNotificationRepo(dataData) diff --git a/go.mod b/go.mod index b6cb4f0e..89a851fb 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.18 require ( github.com/Chain-Zhang/pinyin v0.1.3 + github.com/anargu/gin-brotli v0.0.0-20220116052358-12bf532d5267 github.com/bwmarrin/snowflake v0.3.0 - github.com/gin-contrib/gzip v0.0.6 github.com/gin-gonic/gin v1.8.1 github.com/go-playground/locales v0.14.0 github.com/go-playground/universal-translator v0.18.0 @@ -16,7 +16,6 @@ require ( github.com/google/wire v0.5.0 github.com/jinzhu/copier v0.3.5 github.com/jinzhu/now v1.1.5 - github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/lib/pq v1.10.2 github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mojocn/base64Captcha v1.3.5 @@ -33,6 +32,7 @@ require ( github.com/swaggo/swag v1.8.6 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be golang.org/x/net v0.0.0-20220927171203-f486391704dc + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df xorm.io/builder v0.3.12 xorm.io/core v0.7.3 xorm.io/xorm v1.3.2 @@ -40,7 +40,6 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/anargu/gin-brotli v0.0.0-20220116052358-12bf532d5267 // indirect github.com/andybalholm/brotli v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -86,6 +85,7 @@ require ( golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.1.12 // indirect google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 646519a4..238004c4 100644 --- a/go.sum +++ b/go.sum @@ -351,8 +351,6 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= -github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= -github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -1016,6 +1014,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1026,6 +1026,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/internal/base/validator/validator.go b/internal/base/validator/validator.go index eb8b45da..857795af 100644 --- a/internal/base/validator/validator.go +++ b/internal/base/validator/validator.go @@ -15,6 +15,7 @@ import ( "github.com/segmentfault/answer/internal/base/translator" myErrors "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/i18n" + "github.com/segmentfault/pacman/log" ) // MyValidator my validator @@ -92,6 +93,7 @@ func (m *MyValidator) Check(value interface{}) (errField *ErrorField, err error) if err != nil { var valErrors validator.ValidationErrors if !errors.As(err, &valErrors) { + log.Error(err) return nil, errors.New("validate check exception") } diff --git a/internal/controller_backyard/siteinfo_controller.go b/internal/controller_backyard/siteinfo_controller.go index dc6e14bc..cc950a4c 100644 --- a/internal/controller_backyard/siteinfo_controller.go +++ b/internal/controller_backyard/siteinfo_controller.go @@ -80,3 +80,34 @@ func (sc *SiteInfoController) UpdateInterface(ctx *gin.Context) { err := sc.siteInfoService.SaveSiteInterface(ctx, req) handler.HandleResponse(ctx, err, nil) } + +// GetSMTPConfig get smtp config +// @Summary GetSMTPConfig get smtp config +// @Description GetSMTPConfig get smtp config +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Success 200 {object} handler.RespBody{data=schema.SiteInterfaceResp} +// @Router /answer/admin/api/setting/smtp [get] +func (sc *SiteInfoController) GetSMTPConfig(ctx *gin.Context) { + resp, err := sc.siteInfoService.GetSMTPConfig(ctx) + handler.HandleResponse(ctx, err, resp) +} + +// UpdateSMTPConfig update smtp config +// @Summary update smtp config +// @Description update smtp config +// @Security ApiKeyAuth +// @Tags admin +// @Produce json +// @Param data body schema.SiteInterfaceReq true "general" +// @Success 200 {object} handler.RespBody{} +// @Router /answer/admin/api/setting/smtp [put] +func (sc *SiteInfoController) UpdateSMTPConfig(ctx *gin.Context) { + req := &schema.UpdateSMTPConfigReq{} + if handler.BindAndCheck(ctx, req) { + return + } + err := sc.siteInfoService.UpdateSMTPConfig(ctx, req) + handler.HandleResponse(ctx, err, nil) +} diff --git a/internal/repo/config/config_repo.go b/internal/repo/config/config_repo.go index aa68387e..e4e0a10a 100644 --- a/internal/repo/config/config_repo.go +++ b/internal/repo/config/config_repo.go @@ -120,3 +120,14 @@ func (cr *configRepo) GetConfigById(id int, value any) (err error) { value = json.Unmarshal([]byte(conf.(string)), value) return } + +func (cr *configRepo) SetConfig(key, value string) (err error) { + id := Key2IDMapping[key] + _, err = cr.data.DB.ID(id).Update(&entity.Config{Value: value}) + if err != nil { + err = errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() + } else { + Key2ValueMapping[key] = value + } + return +} diff --git a/internal/router/answer_api_router.go b/internal/router/answer_api_router.go index be642844..e75f7648 100644 --- a/internal/router/answer_api_router.go +++ b/internal/router/answer_api_router.go @@ -223,4 +223,6 @@ func (a *AnswerAPIRouter) RegisterAnswerCmsAPIRouter(r *gin.RouterGroup) { r.GET("/siteinfo/interface", a.siteInfoController.GetInterface) r.PUT("/siteinfo/general", a.siteInfoController.UpdateGeneral) r.PUT("/siteinfo/interface", a.siteInfoController.UpdateInterface) + r.GET("/setting/smtp", a.siteInfoController.GetSMTPConfig) + r.PUT("/setting/smtp", a.siteInfoController.UpdateSMTPConfig) } diff --git a/internal/schema/siteinfo_schema.go b/internal/schema/siteinfo_schema.go index 1d29a34f..39e3f2e7 100644 --- a/internal/schema/siteinfo_schema.go +++ b/internal/schema/siteinfo_schema.go @@ -24,3 +24,25 @@ type SiteInfoResp struct { General *SiteGeneralResp `json:"general"` Face *SiteInterfaceResp `json:"interface"` } + +// UpdateSMTPConfigReq get smtp config request +type UpdateSMTPConfigReq struct { + FromEmailAddress string `validate:"omitempty,gt=0,lte=256" json:"from_email_address"` + FromName string `validate:"omitempty,gt=0,lte=256" json:"from_name"` + SMTPHost string `validate:"omitempty,gt=0,lte=256" json:"smtp_host"` + SMTPPort int `validate:"omitempty,min=1,max=65535" json:"smtp_port"` + Encryption string `validate:"omitempty,oneof=SSL" json:"encryption"` // "" SSL TLS + SMTPUsername string `validate:"omitempty,gt=0,lte=256" json:"smtp_username"` + SMTPPassword string `validate:"omitempty,gt=0,lte=256" json:"smtp_password"` +} + +// GetSMTPConfigResp get smtp config response +type GetSMTPConfigResp struct { + FromEmailAddress string `json:"from_email_address"` + FromName string `json:"from_name"` + SMTPHost string `json:"smtp_host"` + SMTPPort int `json:"smtp_port"` + Encryption string `json:"encryption"` // "" SSL TLS + SMTPUsername string `json:"smtp_username"` + SMTPPassword string `json:"smtp_password"` +} diff --git a/internal/service/config/config_service.go b/internal/service/config/config_service.go index dd1429fb..818b7f28 100644 --- a/internal/service/config/config_service.go +++ b/internal/service/config/config_service.go @@ -8,6 +8,7 @@ type ConfigRepo interface { GetArrayString(key string) ([]string, error) GetConfigType(key string) (int, error) GetConfigById(id int, value any) (err error) + SetConfig(key, value string) (err error) } // ConfigService user service diff --git a/internal/service/export/email_service.go b/internal/service/export/email_service.go index c50cf885..37f5f354 100644 --- a/internal/service/export/email_service.go +++ b/internal/service/export/email_service.go @@ -3,14 +3,14 @@ package export import ( "bytes" "encoding/json" - "fmt" "html/template" - "net/smtp" - "github.com/jordan-wright/email" + "github.com/segmentfault/answer/internal/base/reason" "github.com/segmentfault/answer/internal/service/config" + "github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/log" "golang.org/x/net/context" + "gopkg.in/gomail.v2" ) // EmailService kit service @@ -35,18 +35,24 @@ func NewEmailService(configRepo config.ConfigRepo, emailRepo EmailRepo) *EmailSe // EmailConfig email config type EmailConfig struct { - EmailWebName string `json:"email_web_name"` - EmailFrom string `json:"email_from"` - EmailFromPass string `json:"email_from_pass"` - EmailFromHostname string `json:"email_from_hostname"` - EmailFromSMTP string `json:"email_from_smtp"` - EmailFromName string `json:"email_from_name"` - EmailRegisterTitle string `json:"email_register_title"` - EmailRegisterBody string `json:"email_register_body"` - EmailPassResetTitle string `json:"email_pass_reset_title"` - EmailPassResetBody string `json:"email_pass_reset_body"` - EmailChangeTitle string `json:"email_change_title"` - EmailChangeBody string `json:"email_change_body"` + FromEmailAddress string `json:"from_email_address"` + FromName string `json:"from_name"` + SMTPHost string `json:"smtp_host"` + SMTPPort int `json:"smtp_port"` + Encryption string `json:"encryption"` // "" SSL + SMTPUsername string `json:"smtp_username"` + SMTPPassword string `json:"smtp_password"` + + RegisterTitle string `json:"register_title"` + RegisterBody string `json:"register_body"` + PassResetTitle string `json:"pass_reset_title"` + PassResetBody string `json:"pass_reset_body"` + ChangeTitle string `json:"change_title"` + ChangeBody string `json:"change_body"` +} + +func (e *EmailConfig) IsSSL() bool { + return e.Encryption == "SSL" } type RegisterTemplateData struct { @@ -65,25 +71,28 @@ type ChangeEmailTemplateData struct { } // Send email send -func (es *EmailService) Send(ctx context.Context, emailAddr, title, body, code, content string) { - emailClient := email.NewEmail() - - ec, err := es.getEmailConfig() +func (es *EmailService) Send(ctx context.Context, toEmailAddr, subject, body, code, codeContent string) { + ec, err := es.GetEmailConfig() if err != nil { log.Error(err) return } - emailClient.From = fmt.Sprintf("%s <%s>", ec.EmailFromName, ec.EmailFrom) - emailClient.To = []string{emailAddr} - emailClient.Subject = title - emailClient.HTML = []byte(body) - err = emailClient.Send(ec.EmailFromSMTP, smtp.PlainAuth("", ec.EmailFrom, ec.EmailFromPass, ec.EmailFromHostname)) - if err != nil { + m := gomail.NewMessage() + m.SetHeader("From", ec.FromEmailAddress) + m.SetHeader("To", toEmailAddr) + m.SetHeader("Subject", subject) + m.SetBody("text/html", body) + + d := gomail.NewDialer(ec.SMTPHost, ec.SMTPPort, ec.SMTPUsername, ec.SMTPPassword) + if ec.IsSSL() { + d.SSL = true + } + if err := d.DialAndSend(m); err != nil { log.Error(err) } - err = es.emailRepo.SetCode(ctx, code, content) + err = es.emailRepo.SetCode(ctx, code, codeContent) if err != nil { log.Error(err) } @@ -100,13 +109,15 @@ func (es *EmailService) VerifyUrlExpired(ctx context.Context, code string) (cont } func (es *EmailService) RegisterTemplate(registerUrl string) (title, body string, err error) { - ec, err := es.getEmailConfig() + ec, err := es.GetEmailConfig() if err != nil { return } - templateData := RegisterTemplateData{ec.EmailWebName, registerUrl} - tmpl, err := template.New("register_title").Parse(ec.EmailRegisterTitle) + templateData := RegisterTemplateData{ + SiteName: ec.FromName, RegisterUrl: registerUrl, + } + tmpl, err := template.New("register_title").Parse(ec.RegisterTitle) if err != nil { return "", "", err } @@ -117,7 +128,7 @@ func (es *EmailService) RegisterTemplate(registerUrl string) (title, body string return "", "", err } - tmpl, err = template.New("register_body").Parse(ec.EmailRegisterBody) + tmpl, err = template.New("register_body").Parse(ec.RegisterBody) err = tmpl.Execute(bodyBuf, templateData) if err != nil { return "", "", err @@ -127,13 +138,13 @@ func (es *EmailService) RegisterTemplate(registerUrl string) (title, body string } func (es *EmailService) PassResetTemplate(passResetUrl string) (title, body string, err error) { - ec, err := es.getEmailConfig() + ec, err := es.GetEmailConfig() if err != nil { return } - templateData := PassResetTemplateData{ec.EmailWebName, passResetUrl} - tmpl, err := template.New("pass_reset_title").Parse(ec.EmailPassResetTitle) + templateData := PassResetTemplateData{SiteName: ec.FromName, PassResetUrl: passResetUrl} + tmpl, err := template.New("pass_reset_title").Parse(ec.PassResetTitle) if err != nil { return "", "", err } @@ -144,7 +155,7 @@ func (es *EmailService) PassResetTemplate(passResetUrl string) (title, body stri return "", "", err } - tmpl, err = template.New("pass_reset_body").Parse(ec.EmailPassResetBody) + tmpl, err = template.New("pass_reset_body").Parse(ec.PassResetBody) err = tmpl.Execute(bodyBuf, templateData) if err != nil { return "", "", err @@ -153,16 +164,16 @@ func (es *EmailService) PassResetTemplate(passResetUrl string) (title, body stri } func (es *EmailService) ChangeEmailTemplate(changeEmailUrl string) (title, body string, err error) { - ec, err := es.getEmailConfig() + ec, err := es.GetEmailConfig() if err != nil { return } templateData := ChangeEmailTemplateData{ - SiteName: ec.EmailWebName, + SiteName: ec.FromName, ChangeEmailUrl: changeEmailUrl, } - tmpl, err := template.New("email_change_title").Parse(ec.EmailChangeTitle) + tmpl, err := template.New("email_change_title").Parse(ec.ChangeTitle) if err != nil { return "", "", err } @@ -173,7 +184,7 @@ func (es *EmailService) ChangeEmailTemplate(changeEmailUrl string) (title, body return "", "", err } - tmpl, err = template.New("email_change_body").Parse(ec.EmailChangeBody) + tmpl, err = template.New("email_change_body").Parse(ec.ChangeBody) err = tmpl.Execute(bodyBuf, templateData) if err != nil { return "", "", err @@ -181,7 +192,7 @@ func (es *EmailService) ChangeEmailTemplate(changeEmailUrl string) (title, body return titleBuf.String(), bodyBuf.String(), nil } -func (es *EmailService) getEmailConfig() (ec *EmailConfig, err error) { +func (es *EmailService) GetEmailConfig() (ec *EmailConfig, err error) { emailConf, err := es.configRepo.GetString("email.config") if err != nil { return nil, err @@ -189,7 +200,13 @@ func (es *EmailService) getEmailConfig() (ec *EmailConfig, err error) { ec = &EmailConfig{} err = json.Unmarshal([]byte(emailConf), ec) if err != nil { - return nil, err + return nil, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() } return ec, nil } + +// SetEmailConfig set email config +func (es *EmailService) SetEmailConfig(ec *EmailConfig) (err error) { + data, _ := json.Marshal(ec) + return es.configRepo.SetConfig("email.config", string(data)) +} diff --git a/internal/service/siteinfo_service.go b/internal/service/siteinfo_service.go index 1e59473d..022db57f 100644 --- a/internal/service/siteinfo_service.go +++ b/internal/service/siteinfo_service.go @@ -4,20 +4,24 @@ import ( "context" "encoding/json" + "github.com/jinzhu/copier" "github.com/segmentfault/answer/internal/base/reason" "github.com/segmentfault/answer/internal/entity" "github.com/segmentfault/answer/internal/schema" + "github.com/segmentfault/answer/internal/service/export" "github.com/segmentfault/answer/internal/service/siteinfo_common" "github.com/segmentfault/pacman/errors" ) type SiteInfoService struct { siteInfoRepo siteinfo_common.SiteInfoRepo + emailService *export.EmailService } -func NewSiteInfoService(siteInfoRepo siteinfo_common.SiteInfoRepo) *SiteInfoService { +func NewSiteInfoService(siteInfoRepo siteinfo_common.SiteInfoRepo, emailService *export.EmailService) *SiteInfoService { return &SiteInfoService{ siteInfoRepo: siteInfoRepo, + emailService: emailService, } } @@ -113,3 +117,22 @@ func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.Site err = s.siteInfoRepo.SaveByType(ctx, siteType, &data) return } + +// GetSMTPConfig get smtp config +func (s *SiteInfoService) GetSMTPConfig(ctx context.Context) ( + resp *schema.GetSMTPConfigResp, err error) { + emailConfig, err := s.emailService.GetEmailConfig() + if err != nil { + return nil, err + } + resp = &schema.GetSMTPConfigResp{} + _ = copier.Copy(resp, emailConfig) + return resp, nil +} + +// UpdateSMTPConfig get smtp config +func (s *SiteInfoService) UpdateSMTPConfig(ctx context.Context, req *schema.UpdateSMTPConfigReq) (err error) { + ec := &export.EmailConfig{} + _ = copier.Copy(ec, req) + return s.emailService.SetEmailConfig(ec) +}