alpha
alex 2022-12-26 00:06:00 +01:00
commit 2e6cbef3e1
13 changed files with 478 additions and 0 deletions

14
config.example.yml Normal file
View File

@ -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 <no-reply@xxx.xx>"
templates:
folderPath: "./templates/"
configPath: "./templates/templates.json"

10
go.mod Normal file
View File

@ -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
)

46
go.sum Normal file
View File

@ -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=

37
mailer/mailer.go Normal file
View File

@ -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
}

44
mailer/template.go Normal file
View File

@ -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")
}

39
main.go Normal file
View File

@ -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})
}

12
modules/cache/cache.go vendored Normal file
View File

@ -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

48
modules/config/config.go Normal file
View File

@ -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)
}
}

View File

@ -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
}

99
modules/structs/mail.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,7 @@
package structs
type RabbitMqMessage struct {
Cmd int
Rec string
Body any
}

View File

@ -0,0 +1,26 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<body>
<h1>%header%</h1>
<p>%informationText%</p>
<p>{{.ActivationCode}}</p>
<a
href="{{.Url}}"
style="
padding: 8px 20px;
background-color: #4b72fa;
color: #fff;
font-weight: bolder;
font-size: 16px;
display: inline-block;
margin: 20px 0px;
margin-right: 20px;
text-decoration: none;
"
>%buttonText%</a
>
<p>%alternativeUrl% <a href="{{.Url}}">{{.Url}}</a></p>
</body>
</html>

26
templates/templates.json Normal file
View File

@ -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"
}
}
}
}