Make 'view in browser' on emails work correctly

This commit is contained in:
Eli Ribble 2026-02-02 19:34:37 +00:00
parent 00a75a556e
commit 9d7ca81508
No known key found for this signature in database
13 changed files with 106 additions and 85 deletions

View file

@ -27,6 +27,20 @@ func convertToPGData(data map[string]string) pgtypes.HStore {
return result
}
func convertFromPGData(d pgtypes.HStore) map[string]string {
result := make(map[string]string, 0)
for k, v := range d {
var s string
err := v.Scan(&s)
if err != nil {
log.Warn().Str("key", k).Msg("Failed to convert from HSTORE")
continue
}
result[k] = s
}
return result
}
func ensureInDB(ctx context.Context, destination string) (err error) {
_, err = models.FindCommsEmailContact(ctx, db.PGInstance.BobDB, destination)
if err != nil {
@ -61,7 +75,7 @@ func insertEmailLog(ctx context.Context, data map[string]string, destination str
SentAt: omitnull.FromPtr[time.Time](nil),
Source: omit.From(source),
Subject: omit.From(subject),
TemplateID: omitnull.From(templateInitialID),
TemplateID: omit.From(templateInitialID),
TemplateData: omit.From(data_for_insert),
Type: omit.From(enums.CommsMessagetypeemailInitialContact),
}).One(ctx, db.PGInstance.BobDB)

View file

@ -46,7 +46,7 @@ func sendEmailInitialContact(ctx context.Context, destination string) error {
data["url_subscribe"] = config.MakeURLReport("/email/subscribe?email=%s", destination)
data["url_unsubscribe"] = config.MakeURLReport("/email/unsubscribe")
public_id := generatePublicId(enums.CommsMessagetypeemailInitialContact, data)
data["url_browser"] = config.MakeURLReport("/email?id=%s", public_id)
data["url_browser"] = config.MakeURLReport("/email/%s", public_id)
text, html, err := renderEmailTemplates(templateInitialID, data)
if err != nil {

View file

@ -55,7 +55,7 @@ func sendEmailReportConfirmation(ctx context.Context, job Job) error {
data["URLLogo"] = config.MakeURLReport("/static/img/nidus-logo-no-lettering-64.png")
data["URLReportStatus"] = config.MakeURLReport("/foo")
data["URLReportUnsubscribe"] = config.MakeURLReport("/email/unsubscribe")
data["URLViewInBrowser"] = config.MakeURLReport("/email?id=%s", public_id)
data["URLViewInBrowser"] = config.MakeURLReport("/email/%s", public_id)
text, html, err := renderEmailTemplates(templateReportNotificationConfirmationID, data)
if err != nil {
return fmt.Errorf("Failed to render email report notification template: %w", err)

View file

@ -19,6 +19,7 @@ import (
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"github.com/Gleipnir-Technology/bob/dialect/psql/um"
"github.com/Gleipnir-Technology/bob/types/pgtypes"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
@ -90,6 +91,20 @@ func LoadTemplates() error {
return nil
}
func RenderHTML(template_id int32, s pgtypes.HStore) (html []byte, err error) {
data := convertFromPGData(s)
t, ok := templateByID[template_id]
if !ok {
return []byte{}, fmt.Errorf("Failed to lookup template %d", template_id)
}
buf_html := &bytes.Buffer{}
err = t.executeTemplateHTML(buf_html, data)
if err != nil {
return []byte{}, fmt.Errorf("Failed to render HTML template: %w", err)
}
return buf_html.Bytes(), nil
}
func loadTemplateID(ctx context.Context, tx bob.Tx, t enums.CommsMessagetypeemail) (int32, error) {
templates, err := models.CommsEmailTemplates.Query(
models.SelectWhere.CommsEmailTemplates.MessageType.EQ(t),

View file

@ -90,9 +90,9 @@ var CommsEmailLogs = Table[
TemplateID: column{
Name: "template_id",
DBType: "integer",
Default: "NULL",
Default: "",
Comment: "",
Nullable: true,
Nullable: false,
Generated: false,
AutoIncr: false,
},

View file

@ -228,7 +228,7 @@ func (f *Factory) FromExistingCommsEmailLog(m *models.CommsEmailLog) *CommsEmail
o.SentAt = func() null.Val[time.Time] { return m.SentAt }
o.Source = func() string { return m.Source }
o.Subject = func() string { return m.Subject }
o.TemplateID = func() null.Val[int32] { return m.TemplateID }
o.TemplateID = func() int32 { return m.TemplateID }
o.TemplateData = func() pgtypes.HStore { return m.TemplateData }
o.Type = func() enums.CommsMessagetypeemail { return m.Type }

View file

@ -47,7 +47,7 @@ type CommsEmailLogTemplate struct {
SentAt func() null.Val[time.Time]
Source func() string
Subject func() string
TemplateID func() null.Val[int32]
TemplateID func() int32
TemplateData func() pgtypes.HStore
Type func() enums.CommsMessagetypeemail
@ -89,7 +89,7 @@ func (t CommsEmailLogTemplate) setModelRels(o *models.CommsEmailLog) {
if t.r.TemplateEmailTemplate != nil {
rel := t.r.TemplateEmailTemplate.o.Build()
rel.R.TemplateEmailLogs = append(rel.R.TemplateEmailLogs, o)
o.TemplateID = null.From(rel.ID) // h2
o.TemplateID = rel.ID // h2
o.R.TemplateEmailTemplate = rel
}
}
@ -133,7 +133,7 @@ func (o CommsEmailLogTemplate) BuildSetter() *models.CommsEmailLogSetter {
}
if o.TemplateID != nil {
val := o.TemplateID()
m.TemplateID = omitnull.FromNull(val)
m.TemplateID = omit.From(val)
}
if o.TemplateData != nil {
val := o.TemplateData()
@ -242,6 +242,10 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) {
val := random_string(nil, "255")
m.Subject = omit.From(val)
}
if !(m.TemplateID.IsValue()) {
val := random_int32(nil)
m.TemplateID = omit.From(val)
}
if !(m.TemplateData.IsValue()) {
val := random_pgtypes_HStore(nil)
m.TemplateData = omit.From(val)
@ -258,25 +262,6 @@ func ensureCreatableCommsEmailLog(m *models.CommsEmailLogSetter) {
func (o *CommsEmailLogTemplate) insertOptRels(ctx context.Context, exec bob.Executor, m *models.CommsEmailLog) error {
var err error
isTemplateEmailTemplateDone, _ := commsEmailLogRelTemplateEmailTemplateCtx.Value(ctx)
if !isTemplateEmailTemplateDone && o.r.TemplateEmailTemplate != nil {
ctx = commsEmailLogRelTemplateEmailTemplateCtx.WithValue(ctx, true)
if o.r.TemplateEmailTemplate.o.alreadyPersisted {
m.R.TemplateEmailTemplate = o.r.TemplateEmailTemplate.o.Build()
} else {
var rel1 *models.CommsEmailTemplate
rel1, err = o.r.TemplateEmailTemplate.o.Create(ctx, exec)
if err != nil {
return err
}
err = m.AttachTemplateEmailTemplate(ctx, exec, rel1)
if err != nil {
return err
}
}
}
return err
}
@ -304,12 +289,30 @@ func (o *CommsEmailLogTemplate) Create(ctx context.Context, exec bob.Executor) (
opt.Destination = omit.From(rel0.Address)
if o.r.TemplateEmailTemplate == nil {
CommsEmailLogMods.WithNewTemplateEmailTemplate().Apply(ctx, o)
}
var rel1 *models.CommsEmailTemplate
if o.r.TemplateEmailTemplate.o.alreadyPersisted {
rel1 = o.r.TemplateEmailTemplate.o.Build()
} else {
rel1, err = o.r.TemplateEmailTemplate.o.Create(ctx, exec)
if err != nil {
return nil, err
}
}
opt.TemplateID = omit.From(rel1.ID)
m, err := models.CommsEmailLogs.Insert(opt).One(ctx, exec)
if err != nil {
return nil, err
}
m.R.DestinationEmailContact = rel0
m.R.TemplateEmailTemplate = rel1
if err := o.insertOptRels(ctx, exec, m); err != nil {
return nil, err
@ -673,14 +676,14 @@ func (m commsEmailLogMods) RandomSubject(f *faker.Faker) CommsEmailLogMod {
}
// Set the model columns to this value
func (m commsEmailLogMods) TemplateID(val null.Val[int32]) CommsEmailLogMod {
func (m commsEmailLogMods) TemplateID(val int32) CommsEmailLogMod {
return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) {
o.TemplateID = func() null.Val[int32] { return val }
o.TemplateID = func() int32 { return val }
})
}
// Set the Column from the function
func (m commsEmailLogMods) TemplateIDFunc(f func() null.Val[int32]) CommsEmailLogMod {
func (m commsEmailLogMods) TemplateIDFunc(f func() int32) CommsEmailLogMod {
return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) {
o.TemplateID = f
})
@ -695,32 +698,10 @@ func (m commsEmailLogMods) UnsetTemplateID() CommsEmailLogMod {
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
// The generated value is sometimes null
func (m commsEmailLogMods) RandomTemplateID(f *faker.Faker) CommsEmailLogMod {
return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) {
o.TemplateID = func() null.Val[int32] {
if f == nil {
f = &defaultFaker
}
val := random_int32(f)
return null.From(val)
}
})
}
// Generates a random value for the column using the given faker
// if faker is nil, a default faker is used
// The generated value is never null
func (m commsEmailLogMods) RandomTemplateIDNotNull(f *faker.Faker) CommsEmailLogMod {
return CommsEmailLogModFunc(func(_ context.Context, o *CommsEmailLogTemplate) {
o.TemplateID = func() null.Val[int32] {
if f == nil {
f = &defaultFaker
}
val := random_int32(f)
return null.From(val)
o.TemplateID = func() int32 {
return random_int32(f)
}
})
}

View file

@ -77,7 +77,7 @@ func (t CommsEmailTemplateTemplate) setModelRels(o *models.CommsEmailTemplate) {
for _, r := range t.r.TemplateEmailLogs {
related := r.o.BuildMany(r.number)
for _, rel := range related {
rel.TemplateID = null.From(o.ID) // h2
rel.TemplateID = o.ID // h2
rel.R.TemplateEmailTemplate = o
}
rel = append(rel, related...)

View file

@ -0,0 +1,2 @@
-- +goose Up
ALTER TABLE comms.email_log ALTER COLUMN template_id SET NOT NULL;

View file

@ -35,7 +35,7 @@ type CommsEmailLog struct {
SentAt null.Val[time.Time] `db:"sent_at" `
Source string `db:"source" `
Subject string `db:"subject" `
TemplateID null.Val[int32] `db:"template_id" `
TemplateID int32 `db:"template_id" `
TemplateData pgtypes.HStore `db:"template_data" `
Type enums.CommsMessagetypeemail `db:"type" `
@ -114,7 +114,7 @@ type CommsEmailLogSetter struct {
SentAt omitnull.Val[time.Time] `db:"sent_at" `
Source omit.Val[string] `db:"source" `
Subject omit.Val[string] `db:"subject" `
TemplateID omitnull.Val[int32] `db:"template_id" `
TemplateID omit.Val[int32] `db:"template_id" `
TemplateData omit.Val[pgtypes.HStore] `db:"template_data" `
Type omit.Val[enums.CommsMessagetypeemail] `db:"type" `
}
@ -145,7 +145,7 @@ func (s CommsEmailLogSetter) SetColumns() []string {
if s.Subject.IsValue() {
vals = append(vals, "subject")
}
if !s.TemplateID.IsUnset() {
if s.TemplateID.IsValue() {
vals = append(vals, "template_id")
}
if s.TemplateData.IsValue() {
@ -182,8 +182,8 @@ func (s CommsEmailLogSetter) Overwrite(t *CommsEmailLog) {
if s.Subject.IsValue() {
t.Subject = s.Subject.MustGet()
}
if !s.TemplateID.IsUnset() {
t.TemplateID = s.TemplateID.MustGetNull()
if s.TemplateID.IsValue() {
t.TemplateID = s.TemplateID.MustGet()
}
if s.TemplateData.IsValue() {
t.TemplateData = s.TemplateData.MustGet()
@ -248,8 +248,8 @@ func (s *CommsEmailLogSetter) Apply(q *dialect.InsertQuery) {
vals[7] = psql.Raw("DEFAULT")
}
if !s.TemplateID.IsUnset() {
vals[8] = psql.Arg(s.TemplateID.MustGetNull())
if s.TemplateID.IsValue() {
vals[8] = psql.Arg(s.TemplateID.MustGet())
} else {
vals[8] = psql.Raw("DEFAULT")
}
@ -333,7 +333,7 @@ func (s CommsEmailLogSetter) Expressions(prefix ...string) []bob.Expression {
}})
}
if !s.TemplateID.IsUnset() {
if s.TemplateID.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "template_id")...),
psql.Arg(s.TemplateID),
@ -612,7 +612,7 @@ func (o *CommsEmailLog) TemplateEmailTemplate(mods ...bob.Mod[*dialect.SelectQue
}
func (os CommsEmailLogSlice) TemplateEmailTemplate(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailTemplatesQuery {
pkTemplateID := make(pgtypes.Array[null.Val[int32]], 0, len(os))
pkTemplateID := make(pgtypes.Array[int32], 0, len(os))
for _, o := range os {
if o == nil {
continue
@ -678,7 +678,7 @@ func (commsEmailLog0 *CommsEmailLog) AttachDestinationEmailContact(ctx context.C
func attachCommsEmailLogTemplateEmailTemplate0(ctx context.Context, exec bob.Executor, count int, commsEmailLog0 *CommsEmailLog, commsEmailTemplate1 *CommsEmailTemplate) (*CommsEmailLog, error) {
setter := &CommsEmailLogSetter{
TemplateID: omitnull.From(commsEmailTemplate1.ID),
TemplateID: omit.From(commsEmailTemplate1.ID),
}
err := commsEmailLog0.Update(ctx, exec, setter)
@ -733,7 +733,7 @@ type commsEmailLogWhere[Q psql.Filterable] struct {
SentAt psql.WhereNullMod[Q, time.Time]
Source psql.WhereMod[Q, string]
Subject psql.WhereMod[Q, string]
TemplateID psql.WhereNullMod[Q, int32]
TemplateID psql.WhereMod[Q, int32]
TemplateData psql.WhereMod[Q, pgtypes.HStore]
Type psql.WhereMod[Q, enums.CommsMessagetypeemail]
}
@ -752,7 +752,7 @@ func buildCommsEmailLogWhere[Q psql.Filterable](cols commsEmailLogColumns) comms
SentAt: psql.WhereNull[Q, time.Time](cols.SentAt),
Source: psql.Where[Q, string](cols.Source),
Subject: psql.Where[Q, string](cols.Subject),
TemplateID: psql.WhereNull[Q, int32](cols.TemplateID),
TemplateID: psql.Where[Q, int32](cols.TemplateID),
TemplateData: psql.Where[Q, pgtypes.HStore](cols.TemplateData),
Type: psql.Where[Q, enums.CommsMessagetypeemail](cols.Type),
}
@ -947,11 +947,8 @@ func (os CommsEmailLogSlice) LoadTemplateEmailTemplate(ctx context.Context, exec
}
for _, rel := range commsEmailTemplates {
if !o.TemplateID.IsValue() {
continue
}
if !(o.TemplateID.IsValue() && o.TemplateID.MustGet() == rel.ID) {
if !(o.TemplateID == rel.ID) {
continue
}

View file

@ -538,7 +538,7 @@ func (os CommsEmailTemplateSlice) TemplateEmailLogs(mods ...bob.Mod[*dialect.Sel
func insertCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Executor, commsEmailLogs1 []*CommsEmailLogSetter, commsEmailTemplate0 *CommsEmailTemplate) (CommsEmailLogSlice, error) {
for i := range commsEmailLogs1 {
commsEmailLogs1[i].TemplateID = omitnull.From(commsEmailTemplate0.ID)
commsEmailLogs1[i].TemplateID = omit.From(commsEmailTemplate0.ID)
}
ret, err := CommsEmailLogs.Insert(bob.ToMods(commsEmailLogs1...)).All(ctx, exec)
@ -551,7 +551,7 @@ func insertCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Ex
func attachCommsEmailTemplateTemplateEmailLogs0(ctx context.Context, exec bob.Executor, count int, commsEmailLogs1 CommsEmailLogSlice, commsEmailTemplate0 *CommsEmailTemplate) (CommsEmailLogSlice, error) {
setter := &CommsEmailLogSetter{
TemplateID: omitnull.From(commsEmailTemplate0.ID),
TemplateID: omit.From(commsEmailTemplate0.ID),
}
err := commsEmailLogs1.UpdateAll(ctx, exec, *setter)
@ -730,10 +730,7 @@ func (os CommsEmailTemplateSlice) LoadTemplateEmailLogs(ctx context.Context, exe
for _, rel := range commsEmailLogs {
if !rel.TemplateID.IsValue() {
continue
}
if !(rel.TemplateID.IsValue() && o.ID == rel.TemplateID.MustGet()) {
if !(o.ID == rel.TemplateID) {
continue
}

View file

@ -1,18 +1,33 @@
package rmo
import (
"fmt"
"net/http"
//"github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/go-chi/chi/v5"
)
func getEmailByCode(w http.ResponseWriter, r *http.Request) {
code := chi.URLParam(r, "code")
if code == "" {
http.Error(w, "You must specify a code", http.StatusBadRequest)
id := chi.URLParam(r, "code")
//id := r.FormValue("id")
if id == "" {
http.Error(w, "You must specify an id", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "Pretend email contet for %s", code)
ctx := r.Context()
email_log, err := models.CommsEmailLogs.Query(
models.SelectWhere.CommsEmailLogs.PublicID.EQ(id),
).One(ctx, db.PGInstance.BobDB)
if err != nil {
respondError(w, "Failed to query email_log: %w", err, http.StatusInternalServerError)
return
}
html, err := email.RenderHTML(email_log.TemplateID, email_log.TemplateData)
if err != nil {
respondError(w, "Failed to render email_log: %w", err, http.StatusInternalServerError)
return
}
w.Write(html)
}

View file

@ -24,7 +24,7 @@ func Router() chi.Router {
r.Get("/privacy", getPrivacy)
r.Get("/robots.txt", getRobots)
r.Get("/email", getEmailByCode)
r.Get("/email/{code}", getEmailByCode)
r.Get("/image/{uuid}", getImageByUUID)
r.Route("/mock", addMockRoutes)
r.Get("/pool-submit-complete", getPoolSubmitComplete)