From 42caa77b3eb266f191313f8cdee485abce029a63 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 19 Jan 2026 21:21:02 +0000 Subject: [PATCH] Use the same code paths to render the browser version of emails --- comms/email.go | 32 ++++++++++++++++++++++++++------ htmlpage/fileserver.go | 2 +- htmlpage/html.go | 2 +- htmlpage/response.go | 2 +- public-report/report.go | 13 +++++++++++-- public-report/routes.go | 1 + 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/comms/email.go b/comms/email.go index 37d11124..8352c092 100644 --- a/comms/email.go +++ b/comms/email.go @@ -9,17 +9,17 @@ import ( "net/http" "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/htmlpage" "github.com/rs/zerolog/log" ) +func RenderEmailReportConfirmation(w http.ResponseWriter, report_id string) { + content := contentEmailSubscriptionConfirmation(report_id) + renderOrError(w, reportConfirmationT, content) +} func SendEmailReportConfirmation(to string, report_id string) error { report_id_str := publicReportID(report_id) - 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), - } + content := contentEmailSubscriptionConfirmation(report_id) text, html, err := renderEmailTemplates(reportConfirmationT, content) if err != nil { return fmt.Errorf("Failed to render template %s: %w", reportConfirmationT.name, err) @@ -104,6 +104,15 @@ type emailResponse struct { Message string `json:"message"` } +func contentEmailSubscriptionConfirmation(report_id string) contentEmailReportConfirmation { + return 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/email/report/%s/subscription-confirmation", report_id), + } +} + func publicReportID(s string) string { if len(s) != 12 { return s @@ -111,6 +120,17 @@ func publicReportID(s string) string { return s[0:4] + "-" + s[4:8] + "-" + s[8:12] } +func renderOrError(w http.ResponseWriter, template *builtTemplate, context interface{}) { + buf := &bytes.Buffer{} + err := template.executeTemplateHTML(buf, context) + if err != nil { + log.Error().Err(err).Str("name", template.name).Msg("Failed to render template") + htmlpage.RespondError(w, "Failed to render template", err, http.StatusInternalServerError) + return + } + buf.WriteTo(w) +} + func sendEmail(email emailRequest) (response emailResponse, err error) { url := "https://api.forwardemail.net/v1/emails" diff --git a/htmlpage/fileserver.go b/htmlpage/fileserver.go index d84647bf..5dc69871 100644 --- a/htmlpage/fileserver.go +++ b/htmlpage/fileserver.go @@ -54,7 +54,7 @@ func FileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe // Try to open from local filesystem for development fileToServe, err = root.Open(requestedPath) if err != nil { - respondError(w, "Failed to open file", err, http.StatusNotFound) + RespondError(w, "Failed to open file", err, http.StatusNotFound) return } } diff --git a/htmlpage/html.go b/htmlpage/html.go index 0fa93df0..7d379c26 100644 --- a/htmlpage/html.go +++ b/htmlpage/html.go @@ -85,7 +85,7 @@ func RenderOrError(w http.ResponseWriter, template *BuiltTemplate, context inter err := template.executeTemplate(buf, context) if err != nil { log.Error().Err(err).Strs("files", template.files).Msg("Failed to render template") - respondError(w, "Failed to render template", err, http.StatusInternalServerError) + RespondError(w, "Failed to render template", err, http.StatusInternalServerError) return } buf.WriteTo(w) diff --git a/htmlpage/response.go b/htmlpage/response.go index 499e54fb..74b5cc2d 100644 --- a/htmlpage/response.go +++ b/htmlpage/response.go @@ -34,7 +34,7 @@ func (crw *customResponseWriter) Write(b []byte) (int, error) { } // Respond with an error that is visible to the user -func respondError(w http.ResponseWriter, m string, e error, s int) { +func RespondError(w http.ResponseWriter, m string, e error, s int) { log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error") http.Error(w, m, s) } diff --git a/public-report/report.go b/public-report/report.go index d557e909..3a6960bd 100644 --- a/public-report/report.go +++ b/public-report/report.go @@ -4,14 +4,18 @@ import ( "crypto/rand" "fmt" "math/big" + "net/http" "strings" + + "github.com/Gleipnir-Technology/nidus-sync/comms" + "github.com/go-chi/chi/v5" ) // GenerateReportID creates a 12-character random string using only unambiguous // capital letters and numbers func GenerateReportID() (string, error) { - // Define character set (no O, I, Z to avoid confusion) - const charset = "ABCDEFGHJKLMNPQRSTUVWXY0123456789" + // Define character set (no O/0, I/l/1, 2/Z to avoid confusion) + const charset = "ABCDEFGHJKLMNPQRSTUVWXY3456789" const length = 12 var builder strings.Builder @@ -31,3 +35,8 @@ func GenerateReportID() (string, error) { return builder.String(), nil } + +func getEmailReportSubscriptionConfirmation(w http.ResponseWriter, r *http.Request) { + report_id := chi.URLParam(r, "report_id") + comms.RenderEmailReportConfirmation(w, report_id) +} diff --git a/public-report/routes.go b/public-report/routes.go index 67188698..dcd26315 100644 --- a/public-report/routes.go +++ b/public-report/routes.go @@ -8,6 +8,7 @@ import ( func Router() chi.Router { r := chi.NewRouter() r.Get("/", getRoot) + r.Get("/email/report/{report_id}/subscription-confirmation", getEmailReportSubscriptionConfirmation) r.Get("/nuisance", getNuisance) r.Post("/nuisance-submit", postNuisance) r.Get("/nuisance-submit-complete", getNuisanceSubmitComplete)