From 96f008d67ea1e0c27e3c51b22128a38939d279e1 Mon Sep 17 00:00:00 2001 From: "$(pass /github/name)" <$(pass /github/email)> Date: Wed, 3 Jul 2024 12:35:41 +0200 Subject: [PATCH] added mail functionality --- cmd/membership/main.go | 4 +- configs/config.template.json | 16 ++--- go.mod | 5 ++ go.sum | 4 ++ internal/config/config.go | 10 ++++ internal/controllers/user_controller.go | 16 ++++- internal/database/db.go | 9 ++- internal/server/server.go | 8 ++- internal/services/email_service.go | 77 +++++++++++++++++++++++++ internal/utils/validators.go | 7 +-- 10 files changed, 132 insertions(+), 24 deletions(-) create mode 100644 internal/services/email_service.go diff --git a/cmd/membership/main.go b/cmd/membership/main.go index 83ee34f..374075c 100644 --- a/cmd/membership/main.go +++ b/cmd/membership/main.go @@ -3,6 +3,6 @@ package main import "GoMembership/internal/server" func main() { - server.Run() -} + server.Run() +} diff --git a/configs/config.template.json b/configs/config.template.json index c1d3346..5756bdf 100644 --- a/configs/config.template.json +++ b/configs/config.template.json @@ -1,13 +1,13 @@ { - "db": - { + "db": { "DBPath": "data/db.sqlite3" }, - "smtp": { - "server": "mail.server.com", - "user": "username", - "password": "password", - "port": 465, - "mailtype": "html" + "smtp": { + "Host": "mail.server.com", + "User": "username", + "Password": "password", + "Port": 465, + "Mailtype": "html", + "AdminEmail": "admin@server.com" } } diff --git a/go.mod b/go.mod index 54319cd..2801dc3 100644 --- a/go.mod +++ b/go.mod @@ -8,3 +8,8 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 golang.org/x/crypto v0.24.0 ) + +require ( + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect +) diff --git a/go.sum b/go.sum index 6a11972..2640a1b 100644 --- a/go.sum +++ b/go.sum @@ -6,3 +6,7 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +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/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= diff --git a/internal/config/config.go b/internal/config/config.go index c135c4c..d4b4db3 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -18,9 +18,19 @@ type AuthenticationConfig struct { CSRFSecret string } +type SMTPConfig struct { + Host string `json:"Host"` + User string `json:"User"` + Password string `json:"Password"` + Mailtype string `json:"Mailtype"` + AdminEmail string `json:"AdminEmail"` + Port int `json:"Port"` +} + type Config struct { DB DatabaseConfig `json:"db"` Auth AuthenticationConfig + SMTP SMTPConfig `json:"smtp"` } var ( diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index 70b3d57..c7d01c1 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -11,11 +11,12 @@ import ( ) type UserController struct { - service services.UserService + service services.UserService + emailService services.EmailService } -func NewUserController(service services.UserService) *UserController { - return &UserController{service} +func NewUserController(service services.UserService, emailService *services.EmailService) *UserController { + return &UserController{service, *emailService} } func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { @@ -32,6 +33,15 @@ func (uc *UserController) RegisterUser(w http.ResponseWriter, r *http.Request) { logger.Error.Printf("Couldn't register User: %v", err) return } + // Send welcome email to the user + if err := uc.emailService.SendWelcomeEmail(user); err != nil { + logger.Error.Printf("Failed to send welcome email to user: %v", err) + } + + // Notify admin of new user registration + if err := uc.emailService.NotifyAdminOfNewUser(user.Email); err != nil { + logger.Error.Printf("Failed to notify admin of new user registration: %v", err) + } w.WriteHeader(http.StatusCreated) } diff --git a/internal/database/db.go b/internal/database/db.go index a02bf0b..adf3ef3 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -27,18 +27,17 @@ func initializeDB(dbPath string, schemaPath string) error { return nil } -func Connect() *sql.DB { - cfg := config.LoadConfig() - _, err := os.Stat(cfg.DB.DBPath) +func Connect(DBcfg config.DatabaseConfig) *sql.DB { + _, err := os.Stat(DBcfg.DBPath) if os.IsNotExist(err) { - initErr := initializeDB(cfg.DB.DBPath, "internal/database/schema.sql") + initErr := initializeDB(DBcfg.DBPath, "internal/database/schema.sql") if initErr != nil { logger.Error.Fatalf("Couldn't create database: %v", initErr) } logger.Info.Println("Created new database") } - db, err := sql.Open("sqlite3", cfg.DB.DBPath) + db, err := sql.Open("sqlite3", DBcfg.DBPath) if err != nil { logger.Error.Fatal(err) } diff --git a/internal/server/server.go b/internal/server/server.go index 732a2a8..05b1913 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,6 +1,7 @@ package server import ( + "GoMembership/internal/config" "GoMembership/internal/controllers" "GoMembership/internal/database" "GoMembership/internal/middlewares" @@ -14,12 +15,15 @@ import ( ) func Run() { - db := database.Connect() + cfg := config.LoadConfig() + + db := database.Connect(cfg.DB) defer db.Close() + emailService := services.NewEmailService(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.User, cfg.SMTP.Password, cfg.SMTP.AdminEmail) userRepo := repositories.NewUserRepository(db) userService := services.NewUserService(userRepo) - userController := controllers.NewUserController(userService) + userController := controllers.NewUserController(userService, emailService) router := mux.NewRouter() // router.Handle("/csrf-token", middlewares.GenerateCSRFTokenHandler()).Methods("GET") diff --git a/internal/services/email_service.go b/internal/services/email_service.go new file mode 100644 index 0000000..6180968 --- /dev/null +++ b/internal/services/email_service.go @@ -0,0 +1,77 @@ +package services + +import ( + "GoMembership/internal/models" + "GoMembership/pkg/logger" + "bytes" + "gopkg.in/gomail.v2" + "html/template" +) + +type EmailService struct { + dialer *gomail.Dialer + adminEmail string +} + +func NewEmailService(host string, port int, username, password, adminEmail string) *EmailService { + dialer := gomail.NewDialer(host, port, username, password) + return &EmailService{dialer: dialer, adminEmail: adminEmail} +} + +func (s *EmailService) SendEmail(to string, subject string, body string) error { + msg := gomail.NewMessage() + msg.SetHeader("From", s.dialer.Username) + msg.SetHeader("To", to) + msg.SetHeader("Subject", subject) + msg.SetBody("text/html", body) + + if err := s.dialer.DialAndSend(msg); err != nil { + logger.Error.Printf("Could not send email to %s: %v", to, err) + return err + } + logger.Info.Printf("Email sent to %s", to) + return nil +} + +func ParseTemplate(path string, data interface{}) (string, error) { + // Read the email template file + tpl, err := template.ParseFiles(path) + if err != nil { + logger.Error.Printf("Failed to parse email template: %v", err) + return "", err + } + + // Buffer to hold the rendered template + var tplBuffer bytes.Buffer + if err := tpl.Execute(&tplBuffer, data); err != nil { + logger.Error.Printf("Failed to execute email template: %v", err) + return "", err + } + + return tplBuffer.String(), nil +} +func (s *EmailService) SendWelcomeEmail(user models.User) error { + + // Prepare data to be injected into the template + data := struct { + FirstName string + LastName string + }{ + FirstName: user.FirstName, + LastName: user.LastName, + } + + subject := "Willkommen beim Dörpsmobil Hasloh e.V." + body, err := ParseTemplate("internal/templates/mail_welcome.html", data) + if err != nil { + logger.Error.Print("Couldn't send welcome mail") + return err + } + return s.SendEmail(user.Email, subject, body) +} + +func (s *EmailService) NotifyAdminOfNewUser(userEmail string) error { + subject := "New User Registration" + body := "

A new user has registered with the email: " + userEmail + "

" + return s.SendEmail(s.adminEmail, subject, body) +} diff --git a/internal/utils/validators.go b/internal/utils/validators.go index 71ca830..42d8b4d 100644 --- a/internal/utils/validators.go +++ b/internal/utils/validators.go @@ -3,8 +3,7 @@ package utils import "regexp" func IsEmailValid(email string) bool { - regex := `^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$` - re := regexp.MustCompile(regex) - return re.MatchString(email) + regex := `^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$` + re := regexp.MustCompile(regex) + return re.MatchString(email) } -