feat(emailApi): added api to send emails
Build and Push Docker image / build-and-push (push) Successful in 3m44s Details

This commit is contained in:
Valentin Kolb 2024-10-24 19:05:17 +02:00
parent c2f5680dff
commit 9984936881
4 changed files with 212 additions and 2 deletions

102
emailApi/main.go Normal file
View File

@ -0,0 +1,102 @@
package emailApi
import (
"fmt"
"git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/logger"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/mailer"
"github.com/pocketbase/pocketbase/tools/template"
"net/mail"
)
// sendEmailToUser sends an email notification to the user
func sendEmailToUser(app *pocketbase.PocketBase, registry *template.Registry, recipient *models.Record, sender *models.Record, emailRecord *models.Record) {
// check if recipient and message are set and recipient is not the sender
if recipient == nil || emailRecord == nil || sender == nil {
return
}
recipientEmail := recipient.GetString("email")
senderEmail := sender.GetString("email")
// render email template
html, err := registry.LoadFiles(
"html/emailNotification.html",
).Render(map[string]any{
"APP_URL": app.Settings().Meta.AppUrl,
"SENDER": sender.Username(),
"SENDER_EMAIL": senderEmail,
"CONTENT": emailRecord.GetString("content"),
})
if err != nil {
currentErrors := emailRecord.GetString("errors")
emailRecord.Set("errors", fmt.Sprintf("%s\nThe email was not sent to the user '%s' due to an error.", currentErrors, recipient.Username()))
logger.LogErrorF("Error rendering email notification to recipient with username '%s': %v", recipient.GetString("username"), err)
return
}
subject := emailRecord.GetString("subject")
headers := make(map[string]string)
headers["Reply-To"] = senderEmail
// send email
email := &mailer.Message{
From: mail.Address{
Address: app.Settings().Meta.SenderAddress,
Name: app.Settings().Meta.SenderName,
},
To: []mail.Address{{Address: recipientEmail}},
Subject: "[StuVe IT] " + subject,
HTML: html,
}
if err := app.NewMailClient().Send(email); err != nil {
currentErrors := emailRecord.GetString("errors")
emailRecord.Set("errors", fmt.Sprintf("%s\nThe email was not sent to the user '%s' due to an error.", currentErrors, recipient.Username()))
logger.LogErrorF("Error sending email notification to recipient with username '%s': %v", recipient.GetString("username"), err)
}
}
func sendEmails(app *pocketbase.PocketBase, emailRecord *models.Record) {
registry := template.NewRegistry()
// expand the createdMessageRecord to get recipient user and send email notification if recipient is set
if errs := app.Dao().ExpandRecord(emailRecord, []string{"recipients, sender"}, nil); len(errs) > 0 {
// return new error with all errors
logger.LogErrorF("Error expanding created email record: %v", errs)
emailRecord.Set("errors", fmt.Sprintf("The email was not sent to anyone due to an error."))
return
}
sender := emailRecord.ExpandedOne("sender")
recipients := emailRecord.ExpandedAll("recipients")
for _, recipient := range recipients {
sendEmailToUser(app, registry, recipient, sender, emailRecord)
}
return
}
// InitEmailApi initializes the email notifier
//
// the function sends an email notification after it was created
func InitEmailApi(app *pocketbase.PocketBase, e *core.ServeEvent) error {
logger.LogInfoF("Adding email notifier")
app.OnModelAfterCreate("emails").Add(func(e *core.ModelEvent) error {
// get created message record
createdMessageRecord, err := app.Dao().FindRecordById("emails", e.Model.GetId())
if err != nil {
return err
}
go sendEmails(app, createdMessageRecord)
return nil
})
return nil
}

97
html/blankEmail.html Normal file
View File

@ -0,0 +1,97 @@
<style>
.logo {
width: 30px;
height: 30px;
}
.group {
display: flex;
flex-direction: row;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.stack {
display: flex;
flex-direction: column;
gap: 10px;
}
.container {
padding: 15px 20px 15px 20px;
border-radius: 4px;
background-color: #f1f3f5;
}
.container > h1 {
margin-top: 0;
}
.blue-text {
color: #339af0;
}
.action-btn {
color: #fff;
background-color: #339af0;
font-weight: 600;
border-radius: 4px;
padding: 10px;
text-decoration: none;
}
.action-btn:hover {
background-color: #228be6;
}
</style>
<div class="stack">
<div class="container">
<h1 class="blue-text">Hallo 👋,</h1>
<p>Du hast eine neue Nachricht von {{.SENDER_NAME}} ({{.SENDER_EMAIL}})</p>
<br/>
<div>
<p>{{.CONTENT}}</p>
</div>
<br/>
<br/>
</div>
<div class="container">
<div class="group">
<svg class="logo" width="100%" height="100%" viewBox="0 0 100 100" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.02041,0,0,1.02041,-197.959,0)">
<rect id="Artboard1" x="194" y="0" width="98" height="98" style="fill:none;"/>
<g id="Artboard11">
<g transform="matrix(0.129488,0,0,0.128633,216.823,22.7241)">
<g transform="matrix(7.56828,0,0,7.61857,-176.258,-188.086)">
<path d="M95.493,95.155L4.5,95.155C4.5,95.155 4.726,76.243 5.56,72.26C5.961,70.348 5.798,55.477 5.798,55.477C5.798,55.477 5.802,55.187 5.8,55.048C5.795,54.633 6.268,53.615 6.72,53.294C7.011,53.086 7.343,52.821 7.343,52.419C7.345,49.181 7.341,34.535 7.341,34.535L7.339,34.302C7.329,33.742 7.68,32.975 8.093,32.614C8.55,32.214 9.207,31.923 9.441,31.656C9.589,31.487 9.709,31.294 9.815,30.999C9.9,30.761 16.837,4.845 16.837,4.845C16.837,4.845 23.776,30.762 23.861,30.999C23.967,31.294 24.087,31.487 24.234,31.656C24.469,31.923 25.126,32.214 25.583,32.614C25.996,32.975 26.347,33.742 26.337,34.302L26.334,34.535C26.334,34.535 26.331,49.181 26.333,52.419C26.333,52.821 26.665,53.086 26.956,53.294C27.408,53.615 27.88,54.633 27.876,55.048C27.874,55.187 27.878,55.477 27.878,55.477L27.863,65.838L73.37,65.833L76.83,49.964L78.56,57.898L80.294,49.942L83.757,65.832L83.757,71.587L90.119,71.584C90.119,71.584 91.628,71.592 91.636,71.588C92.215,71.34 94.358,74.803 94.912,76.282C95.606,78.131 95.493,95.155 95.493,95.155Z"
style="fill:none;"/>
</g>
<g transform="matrix(7.56828,0,0,7.61857,-176.28,-188.086)">
<path d="M94.912,76.282C95.606,78.131 95.5,98.155 95.5,98.155L4.5,98.155C4.5,98.155 4.726,76.243 5.56,72.26C5.961,70.348 5.798,55.477 5.798,55.477C5.798,55.477 5.802,55.187 5.8,55.048C5.795,54.633 6.268,53.615 6.72,53.294C7.011,53.086 7.343,52.821 7.343,52.419C7.345,49.181 7.341,34.535 7.341,34.535L7.339,34.302C7.329,33.742 7.68,32.975 8.093,32.614C8.55,32.214 9.207,31.923 9.441,31.656C9.589,31.487 9.709,31.294 9.815,30.999C9.9,30.761 16.837,4.845 16.837,4.845C16.837,4.845 23.776,30.762 23.861,30.999C23.967,31.294 24.087,31.487 24.234,31.656C24.469,31.923 25.126,32.214 25.583,32.614C25.996,32.975 26.347,33.742 26.337,34.302L26.334,34.535C26.334,34.535 26.331,49.181 26.333,52.419C26.333,52.821 26.665,53.086 26.956,53.294C27.408,53.615 27.88,54.633 27.876,55.048C27.874,55.187 27.878,55.477 27.878,55.477L27.863,65.838L73.37,65.833L76.83,49.964L78.56,57.898L80.294,49.942L83.757,65.832L83.757,71.587L90.119,71.584C90.119,71.584 91.628,71.592 91.636,71.588C92.215,71.34 94.358,74.803 94.912,76.282Z"
style="fill:rgb(34,139,230);"/>
</g>
<g transform="matrix(3.31017,0,0,3.33216,-123.695,-80.204)">
<path d="M47.904,125.072L82.509,185.184L13.3,185.184L47.904,125.072Z" style="fill:rgb(231,245,255);"/>
</g>
</g>
</g>
</g>
</svg>
<h3 class="blue-text">
StuVe-IT
</h3>
</div>
<hr/>
<div class="group">
<a href="{{.APP_URL}}/legal/terms-and-conditions">AGB der StuVe</a>
<a href="{{.APP_URL}}/legal/privacy-policy">Datenschutzerklärung</a>
<a href="{{.APP_URL}}/legal/imprint">Impressum</a>
</div>
</div>
</div>

View File

@ -203,6 +203,7 @@ func createLDAPUsersTable(app *pocketbase.PocketBase) error {
// createLDAPSyncLogsTable creates ldapSyncLogs table
func createLDAPSyncLogsTable(app *pocketbase.PocketBase) error {
// create ldapSyncs table
collection := &models.Collection{}
@ -228,6 +229,9 @@ func createLDAPSyncLogsTable(app *pocketbase.PocketBase) error {
form.Schema.AddField(&schema.SchemaField{
Name: "userSyncErrors",
Options: &schema.JsonOptions{
MaxSize: 2000000,
},
Type: schema.FieldTypeJson,
})
@ -248,6 +252,9 @@ func createLDAPSyncLogsTable(app *pocketbase.PocketBase) error {
form.Schema.AddField(&schema.SchemaField{
Name: "groupSyncErrors",
Options: &schema.JsonOptions{
MaxSize: 2000000,
},
Type: schema.FieldTypeJson,
})

View File

@ -1,6 +1,7 @@
package main
import (
"git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/emailApi"
"git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/ldapApi"
"git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/ldapSync"
"git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/logger"
@ -15,9 +16,9 @@ import (
func main() {
// load env
err := godotenv.Load(".env.local", ".env")
err := godotenv.Load(".env.local")
if err != nil {
logger.LogInfoF("The file '.env.local' / '.env' could not be loaded.")
logger.LogInfoF("The file '.env.local' could not be loaded: %v", err)
}
// create app
@ -41,6 +42,9 @@ func main() {
// setup messages email notifications
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { return messages.InitMessages(app, e) })
// setup email api
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { return emailApi.InitEmailApi(app, e) })
// start app
if err := app.Start(); err != nil {
log.Fatal(err)