diff --git a/comms/email/db.go b/comms/email/db.go index d12e9316..c571ade2 100644 --- a/comms/email/db.go +++ b/comms/email/db.go @@ -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) diff --git a/comms/email/initial.go b/comms/email/initial.go index fd42fa1d..dbf92192 100644 --- a/comms/email/initial.go +++ b/comms/email/initial.go @@ -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 { diff --git a/comms/email/report_notification_confirmation.go b/comms/email/report_notification_confirmation.go index efefd502..756a37bc 100644 --- a/comms/email/report_notification_confirmation.go +++ b/comms/email/report_notification_confirmation.go @@ -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) diff --git a/comms/email/template.go b/comms/email/template.go index a59bbfe4..c3ad1a2c 100644 --- a/comms/email/template.go +++ b/comms/email/template.go @@ -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), diff --git a/db/dbinfo/comms.email_log.bob.go b/db/dbinfo/comms.email_log.bob.go index 09417e10..0cb69830 100644 --- a/db/dbinfo/comms.email_log.bob.go +++ b/db/dbinfo/comms.email_log.bob.go @@ -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, }, diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index fe532561..1d9ca8e2 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -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 } diff --git a/db/factory/comms.email_log.bob.go b/db/factory/comms.email_log.bob.go index 04e52de6..e5d70db7 100644 --- a/db/factory/comms.email_log.bob.go +++ b/db/factory/comms.email_log.bob.go @@ -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) } }) } diff --git a/db/factory/comms.email_template.bob.go b/db/factory/comms.email_template.bob.go index a40c2f68..82774dda 100644 --- a/db/factory/comms.email_template.bob.go +++ b/db/factory/comms.email_template.bob.go @@ -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...) diff --git a/db/migrations/00052_comms_email_log_notnulls.sql b/db/migrations/00052_comms_email_log_notnulls.sql new file mode 100644 index 00000000..4e49e957 --- /dev/null +++ b/db/migrations/00052_comms_email_log_notnulls.sql @@ -0,0 +1,2 @@ +-- +goose Up +ALTER TABLE comms.email_log ALTER COLUMN template_id SET NOT NULL; diff --git a/db/models/comms.email_log.bob.go b/db/models/comms.email_log.bob.go index d22a9269..6c39c8cd 100644 --- a/db/models/comms.email_log.bob.go +++ b/db/models/comms.email_log.bob.go @@ -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 } diff --git a/db/models/comms.email_template.bob.go b/db/models/comms.email_template.bob.go index 0f62168d..83471826 100644 --- a/db/models/comms.email_template.bob.go +++ b/db/models/comms.email_template.bob.go @@ -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 } diff --git a/rmo/email.go b/rmo/email.go index ebab6503..4a0e8106 100644 --- a/rmo/email.go +++ b/rmo/email.go @@ -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) } diff --git a/rmo/routes.go b/rmo/routes.go index cace372c..f3028dad 100644 --- a/rmo/routes.go +++ b/rmo/routes.go @@ -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)