diff --git a/comms/email.go b/comms/email.go index 4c9de747..cd1e930e 100644 --- a/comms/email.go +++ b/comms/email.go @@ -2,6 +2,7 @@ package comms import ( "bytes" + "embed" "encoding/json" "fmt" "io" @@ -11,12 +12,46 @@ import ( "github.com/rs/zerolog/log" ) -type AttachmentRequest struct { +func SendEmailReportConfirmation(to string, report_id string) error { + content := contentEmailReportConfirmation{ + URLLogo: "https://dev-sync.nidus.cloud/static/img/nidus-logo-no-lettering-64.png", + URLReportStatus: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s", report_id), + URLReportUnsubscribe: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/unsubscribe", report_id), + URLViewInBrowser: fmt.Sprintf("https://dev-sync.nidus.cloud/report/%s/subscribe-confirmation", report_id), + } + text, html, err := renderEmailTemplates(reportConfirmationT, content) + if err != nil { + return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) + } + return sendEmail(emailRequest{ + From: config.ForwardEmailReportAddress, + HTML: html, + Subject: fmt.Sprintf("Mosquito Report %s Submission", report_id), + Text: text, + To: to, + }) +} + +var ( + reportConfirmationT = buildTemplate("report-subscription-confirmation") +) + +//go:embed template/* +var embeddedFiles embed.FS + +type attachmentRequest struct { Filename string `json:"filename"` Content string `json:"content"` } -type EmailRequest struct { +type contentEmailReportConfirmation struct { + URLLogo string + URLReportStatus string + URLReportUnsubscribe string + URLViewInBrowser string +} + +type emailRequest struct { From string `json:"from"` To string `json:"to"` CC []string `json:"cc,omitempty"` @@ -24,18 +59,18 @@ type EmailRequest struct { Subject string `json:"subject"` Text string `json:"text"` HTML string `json:"html,omitempty"` - Attachments []AttachmentRequest `json:"attachments,omitempty"` + Attachments []attachmentRequest `json:"attachments,omitempty"` Sender string `json:"sender"` ReplyTo string `json:"replyTo,omitempty"` InReplyTo string `json:"inReplyTo,omitempty"` References []string `json:"references,omitempty"` } -type EmailResponse struct { +type emailResponse struct { Message string `json:"message"` } -func SendEmail(email EmailRequest) error { +func sendEmail(email emailRequest) error { url := "https://api.forwardemail.net/v1/emails" payload, err := json.Marshal(email) diff --git a/comms/template.go b/comms/template.go new file mode 100644 index 00000000..0ec5e8cd --- /dev/null +++ b/comms/template.go @@ -0,0 +1,146 @@ +package comms + +import ( + "bytes" + "embed" + "errors" + "fmt" + templatehtml "html/template" + "io" + "os" + "path" + "strings" + templatetxt "text/template" + + "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/rs/zerolog/log" +) + +type builtTemplate struct { + name string + templateHTML *templatehtml.Template + templateTXT *templatetxt.Template +} + +func (bt *builtTemplate) executeTemplateHTML(w io.Writer, content any) error { + if bt.templateHTML == nil { + file := templateFileHTML(bt.name) + templ, err := parseFromDiskHTML(file) + if err != nil { + return fmt.Errorf("Failed to parse template file: %w", err) + } + if templ == nil { + w.Write([]byte("Failed to read from disk: ")) + return errors.New("Template parsing failed") + } + //log.Debug().Str("name", templ.Name()).Msg("Parsed template") + return templ.ExecuteTemplate(w, bt.name+".html", content) + } else { + return bt.templateHTML.ExecuteTemplate(w, bt.name+".html", content) + } +} +func (bt *builtTemplate) executeTemplateTXT(w io.Writer, content any) error { + if bt.templateTXT == nil { + file := templateFileTXT(bt.name) + templ, err := parseFromDiskTXT(file) + if err != nil { + return fmt.Errorf("Failed to parse template file: %w", err) + } + if templ == nil { + w.Write([]byte("Failed to read from disk: ")) + return errors.New("Template parsing failed") + } + //log.Debug().Str("name", templ.Name()).Msg("Parsed template") + return templ.ExecuteTemplate(w, bt.name+".txt", content) + } else { + return bt.templateTXT.ExecuteTemplate(w, bt.name+".txt", content) + } +} +func templateFileHTML(name string) string { + return fmt.Sprintf("comms/template/%s.html", name) +} +func templateFileTXT(name string) string { + return fmt.Sprintf("comms/template/%s.txt", name) +} + +func buildTemplate(name string) *builtTemplate { + files_on_disk := true + file_html := templateFileHTML(name) + file_txt := templateFileTXT(name) + for _, f := range []string{file_html, file_txt} { + _, err := os.Stat(f) + if err != nil { + files_on_disk = false + if !config.IsProductionEnvironment() { + log.Warn().Str("file", f).Msg("email template file is not on disk") + } + break + } + } + var result builtTemplate + if files_on_disk { + result = builtTemplate{ + name: name, + templateHTML: nil, + templateTXT: nil, + } + } else { + result = builtTemplate{ + name: name, + templateHTML: parseEmbeddedHTML(embeddedFiles, "comms", file_html), + templateTXT: parseEmbeddedTXT(embeddedFiles, "comms", file_txt), + } + } + return &result +} + +func parseEmbeddedHTML(embeddedFiles embed.FS, subdir string, file string) *templatehtml.Template { + // Remap the file names to embedded paths + embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)} + name := path.Base(embeddedFilePaths[0]) + log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") + return templatehtml.Must( + templatehtml.New(name).ParseFS(embeddedFiles, embeddedFilePaths...)) +} +func parseEmbeddedTXT(embeddedFiles embed.FS, subdir string, file string) *templatetxt.Template { + // Remap the file names to embedded paths + embeddedFilePaths := []string{strings.TrimPrefix(file, subdir)} + name := path.Base(embeddedFilePaths[0]) + log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template") + return templatetxt.Must( + templatetxt.New(name).ParseFS(embeddedFiles, embeddedFilePaths...)) +} + +func parseFromDiskHTML(file string) (*templatehtml.Template, error) { + name := path.Base(file) + //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") + templ, err := templatehtml.New(name).ParseFiles(file) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %w", file, err) + } + return templ, nil +} + +func parseFromDiskTXT(file string) (*templatetxt.Template, error) { + name := path.Base(file) + //log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk") + templ, err := templatetxt.New(name).ParseFiles(file) + if err != nil { + return nil, fmt.Errorf("Failed to parse %s: %w", file, err) + } + return templ, nil +} + +func renderEmailTemplates(t *builtTemplate, content interface{}) (text string, html string, err error) { + buf_txt := &bytes.Buffer{} + err = t.executeTemplateTXT(buf_txt, content) + if err != nil { + return "", "", fmt.Errorf("Failed to render TXT template: %w", err) + } + buf_html := &bytes.Buffer{} + err = t.executeTemplateHTML(buf_html, content) + if err != nil { + return "", "", fmt.Errorf("Failed to render HTML template: %w", err) + } + return buf_txt.String(), buf_html.String(), nil +} diff --git a/comms/template/report-subscription-confirmation.html b/comms/template/report-subscription-confirmation.html new file mode 100644 index 00000000..8fe723b1 --- /dev/null +++ b/comms/template/report-subscription-confirmation.html @@ -0,0 +1,99 @@ + + + + + + Thank You for Your Mosquito Report + + + +
+
+ Email not displaying correctly? View it in your browser +
+ +
+ + +
+ +
+

Thank You for Your Report

+ +

We've received your mosquito report. Thanks! We appreciate you taking the time to submit it.

+ +

You can check the current status of your report at any time by clicking the button below:

+ +
+ View Report Status +
+ +

We'll send you additional updates as work is scheduled and completed.

+ +

If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email.

+
+ + +
+ + diff --git a/comms/template/report-subscription-confirmation.txt b/comms/template/report-subscription-confirmation.txt new file mode 100644 index 00000000..31df8a44 --- /dev/null +++ b/comms/template/report-subscription-confirmation.txt @@ -0,0 +1,9 @@ +We've received your mosquito report. Thanks! We appreciate you taking the time to submit it. + +You can check the current status of your report at any time at {{.URLReportStatus}} + +We'll send you additional updates as work is scheduled and completed. + +If you have any questions or need further assistance, please don't hesitate to contact us by replying to this email. + +If you no longer wish to receive these updates, navigate your browser to {{.URLReportUnsubscribe}} to unsubscribe. diff --git a/public-report/quick.go b/public-report/quick.go index eadda73e..a3cb41e8 100644 --- a/public-report/quick.go +++ b/public-report/quick.go @@ -7,7 +7,6 @@ import ( "time" "github.com/Gleipnir-Technology/nidus-sync/comms" - "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" @@ -182,12 +181,10 @@ func postRegisterNotifications(w http.ResponseWriter, r *http.Request) { return } if email != "" { - comms.SendEmail(comms.EmailRequest{ - From: config.ForwardEmailReportAddress, - To: email, - Subject: "test email", - Text: "This is just testing that I can send email", - }) + err := comms.SendEmailReportConfirmation(email, report_id) + if err != nil { + log.Error().Err(err).Msg("Failed to send email") + } } if phone != "" { //err := comms.SendSMS(phone, "testing 1 2 3")