commit 2e6cbef3e18f5fd7b3e73fea84063efc269def45 Author: alex Date: Mon Dec 26 00:06:00 2022 +0100 mailer diff --git a/config.example.yml b/config.example.yml new file mode 100644 index 0000000..b1d9524 --- /dev/null +++ b/config.example.yml @@ -0,0 +1,14 @@ +debug: false +rabbitmq: + host: "127.0.0.1" + username: "guest" + password: "guest" +mail: + host: "127.0.0.1" + username: "username" + password: "password" + port: "" + from: "App " +templates: + folderPath: "./templates/" + configPath: "./templates/templates.json" \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5517255 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module clickandjoin.app/emailserver + +go 1.19 + +require ( + github.com/rabbitmq/amqp091-go v1.5.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..77e26f8 --- /dev/null +++ b/go.sum @@ -0,0 +1,46 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.5.0 h1:VouyHPBu1CrKyJVfteGknGOGCzmOz0zcv/tONLkb7rg= +github.com/rabbitmq/amqp091-go v1.5.0/go.mod h1:JsV0ofX5f1nwOGafb8L5rBItt9GyhfQfcJj+oyz0dGg= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mailer/mailer.go b/mailer/mailer.go new file mode 100644 index 0000000..948ea32 --- /dev/null +++ b/mailer/mailer.go @@ -0,0 +1,37 @@ +package mailer + +import ( + "net/smtp" + + "clickandjoin.app/emailserver/modules/cache" + "clickandjoin.app/emailserver/modules/config" + "clickandjoin.app/emailserver/modules/structs" + "github.com/sirupsen/logrus" +) + +func Init() { + cfg := config.Cfg.Mail + + cache.SmtpAuth = smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host) + + readTemplatesConfig() + loadTemplateFiles() +} + +func NewMail(mail structs.Mail) error { + logrus.Debugln("NEW MAIL") + + body, err := mail.RenderTemplate() + + if err != nil { + logrus.Errorln("Failed to render template, err:", err) + return err + } + + if err = mail.Send(body); err != nil { + logrus.Errorln("Failed to send mail, err:", err) + return err + } + + return nil +} diff --git a/mailer/template.go b/mailer/template.go new file mode 100644 index 0000000..8e23f8f --- /dev/null +++ b/mailer/template.go @@ -0,0 +1,44 @@ +package mailer + +import ( + "encoding/json" + "os" + + "clickandjoin.app/emailserver/modules/cache" + "clickandjoin.app/emailserver/modules/config" + "github.com/sirupsen/logrus" +) + +func readTemplatesConfig() { + logrus.Debugln("START READING TEMPLATE CONFIG") + + byteValue, err := os.ReadFile(config.Cfg.Templates.ConfigPath) + + if err != nil { + logrus.Errorln("Failed to read config file, err:", err) + } + + err = json.Unmarshal(byteValue, &cache.Templates) + + if err != nil { + logrus.Errorln("Failed to unmarshal templates config, err:", err) + } + + logrus.Debug("FINISHED READING TEMPLATE CONFIG") +} + +func loadTemplateFiles() { + logrus.Debugln("STARTING IMPORTING TEMPLATE FILES") + + for templateName, _ := range cache.Templates.Templates { + data, err := os.ReadFile(config.Cfg.Templates.FolderPath + templateName + ".html") + + if err != nil { + logrus.Errorln("Failed to read file, err:", err) + } + + cache.BodyTemplates[templateName] = data + } + + logrus.Debugln("FINISHED IMPORTING TEMPLATE FILES") +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..7580439 --- /dev/null +++ b/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "clickandjoin.app/emailserver/mailer" + "clickandjoin.app/emailserver/modules/config" + "clickandjoin.app/emailserver/modules/rabbitmq" + "clickandjoin.app/emailserver/modules/structs" + "github.com/sirupsen/logrus" +) + +func init() { + config.LoadConfig() + + if config.Cfg.Debug { + logrus.SetLevel(logrus.DebugLevel) + } + + logrus.Println("Debug:", config.Cfg.Debug) + + rabbitmq.Init() + + mailer.Init() +} + +func main() { + logrus.Println("test") + + test := struct { + Url string + Username string + ActivationCode string + }{ + Url: "https://test.de", + Username: "Ben", + ActivationCode: "912 211", + } + + mailer.NewMail(structs.Mail{To: []string{"info@clickandjoin.de"}, TemplateId: "emailVerification", LanguageId: "de", BodyData: test}) +} diff --git a/modules/cache/cache.go b/modules/cache/cache.go new file mode 100644 index 0000000..1b7e3d3 --- /dev/null +++ b/modules/cache/cache.go @@ -0,0 +1,12 @@ +package cache + +import "net/smtp" + +type templates struct { + Templates map[string]map[string]map[string]string +} + +var Templates templates +var BodyTemplates = make(map[string][]byte) + +var SmtpAuth smtp.Auth diff --git a/modules/config/config.go b/modules/config/config.go new file mode 100644 index 0000000..9cba25b --- /dev/null +++ b/modules/config/config.go @@ -0,0 +1,48 @@ +package config + +import ( + "os" + + "github.com/sirupsen/logrus" + "gopkg.in/yaml.v2" +) + +var Cfg Config + +type Config struct { + Debug bool + RabbitMq RabbitMq + Mail Mail + Templates Templates +} + +type RabbitMq struct { + Host string + Username string + Password string +} + +type Mail struct { + Host string + Username string + Password string + Port string + From string +} + +type Templates struct { + FolderPath string + ConfigPath string +} + +func LoadConfig() { + data, err := os.ReadFile("config.yml") + + if err != nil { + logrus.Fatalln("Failed to read config file, err:", err) + } + + if err := yaml.Unmarshal(data, &Cfg); err != nil { + logrus.Fatalln("Failed to unmarshal config file, err:", err) + } +} diff --git a/modules/rabbitmq/rabbitmq.go b/modules/rabbitmq/rabbitmq.go new file mode 100644 index 0000000..e60c90e --- /dev/null +++ b/modules/rabbitmq/rabbitmq.go @@ -0,0 +1,70 @@ +package rabbitmq + +import ( + "fmt" + + "clickandjoin.app/emailserver/modules/config" + amqp "github.com/rabbitmq/amqp091-go" + "github.com/sirupsen/logrus" +) + +var Conn *amqp.Connection +var Channel *amqp.Channel + +const queueMails = "cnj.mails" + +func getConnectionString() string { + cfg := &config.Cfg.RabbitMq + + return fmt.Sprintf("amqp://%s:%s@%s/", cfg.Username, cfg.Password, cfg.Host) +} + +func Init() { + conn, err := amqp.Dial(getConnectionString()) + + if err != nil { + logrus.Fatalln("RabbitMQ connection failed, err:", err) + } + + ch, err := conn.Channel() + + if err != nil { + logrus.Fatalln(err) + } + + Channel = ch + + err = ch.Qos( + 1, // prefetch count + 0, // prefetch size + false, // global + ) + + if err != nil { + logrus.Fatalln("Failed to set Qos, err:", err) + } + + msgs, err := ch.Consume( + queueMails, // queue + "", // consumer + false, // auto-ack + false, // exclusive + false, // no-local + false, // no-wait + nil, // args + ) + + if err != nil { + logrus.Fatalln("Failed to consume mails, err:", err) + } + + var forever chan struct{} + + go func() { + for d := range msgs { + logrus.Printf("Received a message: %s", d.Body) + } + }() + + <-forever +} diff --git a/modules/structs/mail.go b/modules/structs/mail.go new file mode 100644 index 0000000..3351364 --- /dev/null +++ b/modules/structs/mail.go @@ -0,0 +1,99 @@ +package structs + +import ( + "bytes" + "html/template" + "net/smtp" + "strings" + + "clickandjoin.app/emailserver/modules/cache" + "clickandjoin.app/emailserver/modules/config" + "github.com/sirupsen/logrus" +) + +type Mail struct { + To []string + Subject string + TemplateId string + LanguageId string + BodyData interface{} +} + +func (m *Mail) Send(body string) error { + cfg := config.Cfg.Mail + + msg := "From: " + cfg.From + "\n" + + "To: " + strings.Join(m.To, ",") + "\n" + + "Subject: " + m.Subject + "\n" + + "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + + body + + err := smtp.SendMail(cfg.Host+":"+cfg.Port, cache.SmtpAuth, cfg.From, m.To, []byte(msg)) + + if err != nil { + logrus.Warnln("smtp error:", err) + return err + } + + logrus.Debugln("send mail", msg) + return nil +} + +func (m *Mail) RenderTemplate() (string, error) { + body := cache.BodyTemplates[m.TemplateId] + + for templateName, templateData := range cache.Templates.Templates { + logrus.Debugln("RENDER TEMPLATE", templateName, templateData) + + m.Subject = templateData["mailSubject"][m.LanguageId] + + // replace body %values% with values in templates config + for key, value := range templateData { + logrus.Debugln("key", key, "value", value) + + if key == "mailSubject" { + continue + } + + body = []byte(strings.Replace(string(body), "%"+key+"%", value[m.LanguageId], -1)) + } + } + + logrus.Println("body", string(body)) + + t := template.Must(template.New("").Parse(string(body))) + + buf := new(bytes.Buffer) + + logrus.Println("bodyData", m.BodyData) + + if err := t.Execute(buf, m.BodyData); err != nil { + return "", err + } + + // define subject from config + /* + m.Subject = templates.Templates[m.TemplateId].Languages[m.LanguageId].Subject + + body := templates.Templates[m.TemplateId].Body + + // replace body %values% with values in templates config + for key, value := range templates.Templates[m.TemplateId].Languages[m.LanguageId].Texts { + strKey := fmt.Sprintf("%v", key) + strValue := fmt.Sprintf("%v", value) + + logrus.Infoln("a", strKey, strValue) + + body = strings.Replace(body, "%"+strKey+"%", strValue, -1) + } + + t := template.Must(template.New("").Parse(body)) + + buf := new(bytes.Buffer) + + if err := t.Execute(buf, m.BodyData); err != nil { + logrus.Fatalln("Error executing body data", err) + } */ + + return buf.String(), nil +} diff --git a/modules/structs/rabbitmq.go b/modules/structs/rabbitmq.go new file mode 100644 index 0000000..59d5c77 --- /dev/null +++ b/modules/structs/rabbitmq.go @@ -0,0 +1,7 @@ +package structs + +type RabbitMqMessage struct { + Cmd int + Rec string + Body any +} diff --git a/templates/emailVerification.html b/templates/emailVerification.html new file mode 100644 index 0000000..c49a37a --- /dev/null +++ b/templates/emailVerification.html @@ -0,0 +1,26 @@ + + + +

%header%

+

%informationText%

+

{{.ActivationCode}}

+ + %buttonText% + +

%alternativeUrl% {{.Url}}

+ + \ No newline at end of file diff --git a/templates/templates.json b/templates/templates.json new file mode 100644 index 0000000..7772c5b --- /dev/null +++ b/templates/templates.json @@ -0,0 +1,26 @@ +{ + "templates": { + "emailVerification": { + "mailSubject": { + "en": "Your email confirmation code", + "de": "Ihr E-Mail-Bestätigungscode" + }, + "header": { + "en": "Hello {{.Username}}", + "de": "Hallo {{.Username}}," + }, + "informationText": { + "en": "to confirm your identity we need to verify your email.", + "de": "um deine Identität zu bestätigen müssen wir deine E-Mail verifizieren." + }, + "buttonText": { + "en": "Verify email", + "de": "E-Mail verifizieren" + }, + "alternativeUrl": { + "en": "Alternatively you can click on the link here", + "de": "Alternativ kannst du auch hier auf den Link klicken" + } + } + } +} \ No newline at end of file