diff --git a/routes/routes.go b/routes/routes.go index b9bdeef..cd3d3c6 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -2,27 +2,24 @@ package routes import ( "fmt" + "github.com/muety/wakapi/views" "html/template" - "io/fs" - "io/ioutil" "net/http" - "path" "strings" "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" "github.com/muety/wakapi/utils" - "github.com/muety/wakapi/views" ) -func Init() { - loadTemplates() -} - type action func(w http.ResponseWriter, r *http.Request) (int, string, string) var templates map[string]*template.Template +func Init() { + loadTemplates() +} + func DefaultTemplateFuncs() template.FuncMap { return template.FuncMap{ "json": utils.Json, @@ -58,44 +55,6 @@ func DefaultTemplateFuncs() template.FuncMap { } } -func loadTemplates() { - tpls := template.New("").Funcs(DefaultTemplateFuncs()) - templates = make(map[string]*template.Template) - - // Use local file system when in 'dev' environment, go embed file system otherwise - templateFs := config.ChooseFS("views", views.TemplateFiles) - - files, err := fs.ReadDir(templateFs, ".") - if err != nil { - panic(err) - } - - for _, file := range files { - tplName := file.Name() - if file.IsDir() || path.Ext(tplName) != ".html" { - continue - } - - templateFile, err := templateFs.Open(tplName) - if err != nil { - panic(err) - } - templateData, err := ioutil.ReadAll(templateFile) - if err != nil { - panic(err) - } - - templateFile.Close() - - tpl, err := tpls.New(tplName).Parse(string(templateData)) - if err != nil { - panic(err) - } - - templates[tplName] = tpl - } -} - func typeName(t uint8) string { if t == models.SummaryProject { return "project" @@ -118,6 +77,16 @@ func typeName(t uint8) string { return "unknown" } +func loadTemplates() { + // Use local file system when in 'dev' environment, go embed file system otherwise + templateFs := config.ChooseFS("views", views.TemplateFiles) + if tpls, err := utils.LoadTemplates(templateFs, DefaultTemplateFuncs()); err == nil { + templates = tpls + } else { + panic(err) + } +} + func defaultErrorRedirectTarget() string { return fmt.Sprintf("%s/?error=unauthorized", config.Get().Server.BasePath) } diff --git a/services/mail/mail.go b/services/mail/mail.go index 1dc3163..6f1c638 100644 --- a/services/mail/mail.go +++ b/services/mail/mail.go @@ -7,12 +7,10 @@ import ( "github.com/muety/wakapi/routes" "github.com/muety/wakapi/services" "github.com/muety/wakapi/utils" - "html/template" - "io/ioutil" + "github.com/muety/wakapi/views/mail" "time" conf "github.com/muety/wakapi/config" - "github.com/muety/wakapi/views" ) const ( @@ -33,6 +31,7 @@ type SendingService interface { type MailService struct { config *conf.Config sendingService SendingService + templates utils.TemplateMap } func NewMailService() services.IMailService { @@ -49,11 +48,18 @@ func NewMailService() services.IMailService { } } - return &MailService{sendingService: sendingService, config: config} + // Use local file system when in 'dev' environment, go embed file system otherwise + templateFs := conf.ChooseFS("views/mail", mail.TemplateFiles) + templates, err := utils.LoadTemplates(templateFs, routes.DefaultTemplateFuncs()) + if err != nil { + panic(err) + } + + return &MailService{sendingService: sendingService, config: config, templates: templates} } func (m *MailService) SendPasswordReset(recipient *models.User, resetLink string) error { - tpl, err := getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink}) + tpl, err := m.getPasswordResetTemplate(PasswordResetTplData{ResetLink: resetLink}) if err != nil { return err } @@ -67,7 +73,7 @@ func (m *MailService) SendPasswordReset(recipient *models.User, resetLink string } func (m *MailService) SendWakatimeFailureNotification(recipient *models.User, numFailures int) error { - tpl, err := getWakatimeFailureNotificationTemplate(WakatimeFailureNotificationNotificationTplData{ + tpl, err := m.getWakatimeFailureNotificationTemplate(WakatimeFailureNotificationNotificationTplData{ PublicUrl: m.config.Server.PublicUrl, NumFailures: numFailures, }) @@ -84,7 +90,7 @@ func (m *MailService) SendWakatimeFailureNotification(recipient *models.User, nu } func (m *MailService) SendImportNotification(recipient *models.User, duration time.Duration, numHeartbeats int) error { - tpl, err := getImportNotificationTemplate(ImportNotificationTplData{ + tpl, err := m.getImportNotificationTemplate(ImportNotificationTplData{ PublicUrl: m.config.Server.PublicUrl, Duration: fmt.Sprintf("%.0f seconds", duration.Seconds()), NumHeartbeats: numHeartbeats, @@ -102,7 +108,7 @@ func (m *MailService) SendImportNotification(recipient *models.User, duration ti } func (m *MailService) SendReport(recipient *models.User, report *models.Report) error { - tpl, err := getReportTemplate(ReportTplData{report}) + tpl, err := m.getReportTemplate(ReportTplData{report}) if err != nil { return err } @@ -115,68 +121,38 @@ func (m *MailService) SendReport(recipient *models.User, report *models.Report) return m.sendingService.Send(mail) } -func getPasswordResetTemplate(data PasswordResetTplData) (*bytes.Buffer, error) { - tpl, err := loadTemplate(tplNamePasswordReset) - if err != nil { - return nil, err - } +func (m *MailService) getPasswordResetTemplate(data PasswordResetTplData) (*bytes.Buffer, error) { var rendered bytes.Buffer - if err := tpl.Execute(&rendered, data); err != nil { + if err := m.templates[m.fmtName(tplNamePasswordReset)].Execute(&rendered, data); err != nil { return nil, err } return &rendered, nil } -func getWakatimeFailureNotificationTemplate(data WakatimeFailureNotificationNotificationTplData) (*bytes.Buffer, error) { - tpl, err := loadTemplate(tplNameWakatimeFailureNotification) - if err != nil { - return nil, err - } +func (m *MailService) getWakatimeFailureNotificationTemplate(data WakatimeFailureNotificationNotificationTplData) (*bytes.Buffer, error) { var rendered bytes.Buffer - if err := tpl.Execute(&rendered, data); err != nil { + if err := m.templates[m.fmtName(tplNameWakatimeFailureNotification)].Execute(&rendered, data); err != nil { return nil, err } return &rendered, nil } -func getImportNotificationTemplate(data ImportNotificationTplData) (*bytes.Buffer, error) { - tpl, err := loadTemplate(tplNameImportNotification) - if err != nil { - return nil, err - } +func (m *MailService) getImportNotificationTemplate(data ImportNotificationTplData) (*bytes.Buffer, error) { var rendered bytes.Buffer - if err := tpl.Execute(&rendered, data); err != nil { + if err := m.templates[m.fmtName(tplNameImportNotification)].Execute(&rendered, data); err != nil { return nil, err } return &rendered, nil } -func getReportTemplate(data ReportTplData) (*bytes.Buffer, error) { - tpl, err := loadTemplate(tplNameReport) - if err != nil { - return nil, err - } +func (m *MailService) getReportTemplate(data ReportTplData) (*bytes.Buffer, error) { var rendered bytes.Buffer - if err := tpl.Execute(&rendered, data); err != nil { + if err := m.templates[m.fmtName(tplNameReport)].Execute(&rendered, data); err != nil { return nil, err } return &rendered, nil } -func loadTemplate(tplName string) (*template.Template, error) { - tplFile, err := views.TemplateFiles.Open(fmt.Sprintf("mail/%s.tpl.html", tplName)) - if err != nil { - return nil, err - } - defer tplFile.Close() - - tplData, err := ioutil.ReadAll(tplFile) - if err != nil { - return nil, err - } - - return template. - New(tplName). - Funcs(routes.DefaultTemplateFuncs()). - Parse(string(tplData)) +func (m *MailService) fmtName(name string) string { + return fmt.Sprintf("%s.tpl.html", name) } diff --git a/utils/template.go b/utils/template.go index 1abeb5b..9282f4b 100644 --- a/utils/template.go +++ b/utils/template.go @@ -3,8 +3,13 @@ package utils import ( "encoding/json" "html/template" + "io/fs" + "io/ioutil" + "path" ) +type TemplateMap map[string]*template.Template + func Json(data interface{}) template.JS { d, err := json.Marshal(data) if err != nil { @@ -19,3 +24,40 @@ func ToRunes(s string) (r []string) { } return r } + +func LoadTemplates(templateFs fs.FS, funcs template.FuncMap) (TemplateMap, error) { + tpls := template.New("").Funcs(funcs) + templates := make(map[string]*template.Template) + + files, err := fs.ReadDir(templateFs, ".") + if err != nil { + return nil, err + } + + for _, file := range files { + tplName := file.Name() + if file.IsDir() || path.Ext(tplName) != ".html" { + continue + } + + templateFile, err := templateFs.Open(tplName) + if err != nil { + return nil, err + } + templateData, err := ioutil.ReadAll(templateFile) + if err != nil { + return nil, err + } + + templateFile.Close() + + tpl, err := tpls.New(tplName).Parse(string(templateData)) + if err != nil { + return nil, err + } + + templates[tplName] = tpl + } + + return templates, nil +} diff --git a/views/mail/head.tpl.html b/views/mail/head.tpl.html new file mode 100644 index 0000000..b76f453 --- /dev/null +++ b/views/mail/head.tpl.html @@ -0,0 +1,88 @@ + + + +Wakapi + + \ No newline at end of file diff --git a/views/mail/import_finished.tpl.html b/views/mail/import_finished.tpl.html index f332890..a8ddda1 100644 --- a/views/mail/import_finished.tpl.html +++ b/views/mail/import_finished.tpl.html @@ -1,107 +1,14 @@ - - - - Wakapi – Data Import Finished - - +{{ template "head.tpl.html" . }} + diff --git a/views/mail/mail.go b/views/mail/mail.go new file mode 100644 index 0000000..2a27047 --- /dev/null +++ b/views/mail/mail.go @@ -0,0 +1,6 @@ +package mail + +import "embed" + +//go:embed *.html +var TemplateFiles embed.FS diff --git a/views/mail/report.tpl.html b/views/mail/report.tpl.html index 60b4dac..ab8ee8a 100644 --- a/views/mail/report.tpl.html +++ b/views/mail/report.tpl.html @@ -1,107 +1,14 @@ - - - - Wakapi – Report - - +{{ template "head.tpl.html" . }} +
  -
- - - - -
- Wakapi Logo -
-
+ {{ template "theader.tpl.html" . }}
@@ -134,15 +41,7 @@
- + {{ template "tfooter.tpl.html" . }}
 
diff --git a/views/mail/reset_password.tpl.html b/views/mail/reset_password.tpl.html index d267fb9..953bb59 100644 --- a/views/mail/reset_password.tpl.html +++ b/views/mail/reset_password.tpl.html @@ -1,107 +1,14 @@ - - - - Wakapi – Reset Password - - +{{ template "head.tpl.html" . }} +
  -
- - - - -
- Wakapi Logo -
-
+ {{ template "theader.tpl.html" . }}
@@ -180,15 +87,8 @@
- + + {{ template "tfooter.tpl.html" . }}
 
diff --git a/views/mail/tfooter.tpl.html b/views/mail/tfooter.tpl.html new file mode 100644 index 0000000..d2801e9 --- /dev/null +++ b/views/mail/tfooter.tpl.html @@ -0,0 +1,9 @@ +
  -
- - - - -
- Wakapi Logo -
-
+ {{ template "theader.tpl.html" . }}
@@ -135,15 +42,7 @@
- + {{ template "tfooter.tpl.html" . }}
 
+ + + +
+ Powered by Wakapi.dev. +
+ \ No newline at end of file diff --git a/views/mail/theader.tpl.html b/views/mail/theader.tpl.html new file mode 100644 index 0000000..2f6745c --- /dev/null +++ b/views/mail/theader.tpl.html @@ -0,0 +1,9 @@ +
+ + + + +
+ Wakapi Logo +
+
\ No newline at end of file diff --git a/views/mail/wakatime_connection_failure.tpl.html b/views/mail/wakatime_connection_failure.tpl.html index 2ef4ca4..e80be3d 100644 --- a/views/mail/wakatime_connection_failure.tpl.html +++ b/views/mail/wakatime_connection_failure.tpl.html @@ -1,107 +1,14 @@ - - - - Wakapi – WakaTime Connection Failure - - +{{ template "head.tpl.html" . }} + diff --git a/views/views.go b/views/views.go index 8eb83d9..51849c2 100644 --- a/views/views.go +++ b/views/views.go @@ -2,5 +2,5 @@ package views import "embed" -//go:embed *.html mail/*.html +//go:embed *.html var TemplateFiles embed.FS
  -
- - - - -
- Wakapi Logo -
-
+ {{ template "theader.tpl.html" . }}
@@ -134,15 +41,7 @@
- + {{ template "tfooter.tpl.html" . }}