nidus-sync/platform/email/email.go

142 lines
4.7 KiB
Go
Raw Normal View History

2026-02-10 04:11:52 +00:00
package email
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/config"
2026-02-10 04:11:52 +00:00
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/platform/background"
2026-02-10 04:11:52 +00:00
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/rs/zerolog/log"
)
func EnsureInDB(ctx context.Context, destination string) (err error) {
_, err = models.FindCommsEmailContact(ctx, db.PGInstance.BobDB, destination)
if err != nil {
// doesn't exist
if err.Error() == "sql: no rows in result set" {
public_id := fmt.Sprintf("%x", sha256.Sum256([]byte(destination)))
_, err = models.CommsEmailContacts.Insert(&models.CommsEmailContactSetter{
Address: omit.From(destination),
Confirmed: omit.From(false),
IsSubscribed: omit.From(false),
PublicID: omit.From(public_id),
}).One(ctx, db.PGInstance.BobDB)
if err != nil {
return fmt.Errorf("Failed to insert new email: %w", err)
}
log.Info().Str("email", destination).Msg("Added email to the comms database")
return nil
}
return fmt.Errorf("Unexpected error searching for contact: %w", err)
}
return nil
}
func insertEmailLog(ctx context.Context, data map[string]string, destination string, public_id string, source string, subject string, template_id int32) (email_id *int32, err error) {
data_for_insert := db.ConvertToPGData(data)
2026-02-10 04:11:52 +00:00
var type_ enums.CommsMessagetypeemail
switch template_id {
case templateReportNotificationConfirmationID:
type_ = enums.CommsMessagetypeemailReportNotificationConfirmation
case templateInitialID:
type_ = enums.CommsMessagetypeemailInitialContact
default:
return nil, fmt.Errorf("Unrecognized template ID %d", template_id)
2026-02-10 04:11:52 +00:00
}
e, err := models.CommsEmailLogs.Insert(&models.CommsEmailLogSetter{
2026-02-10 04:11:52 +00:00
//ID:
Created: omit.From(time.Now()),
DeliveryStatus: omit.From("initial"),
Destination: omit.From(destination),
PublicID: omit.From(public_id),
SentAt: omitnull.FromPtr[time.Time](nil),
Source: omit.From(source),
Subject: omit.From(subject),
TemplateID: omit.From(template_id),
TemplateData: omit.From(data_for_insert),
Type: omit.From(type_),
}).One(ctx, db.PGInstance.BobDB)
if err != nil {
return nil, fmt.Errorf("insern email log: %w", err)
}
return &e.ID, nil
2026-02-10 04:11:52 +00:00
}
func generatePublicId(template int32, m map[string]string) string {
2026-02-10 04:11:52 +00:00
if m == nil || len(m) == 0 {
// Return hash of empty string for empty maps
emptyHash := sha256.Sum256([]byte(""))
return hex.EncodeToString(emptyHash[:])
}
// Get and sort keys for deterministic ordering
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
// Build a string with all key-value pairs
var sb strings.Builder
// Add type first
sb.WriteString(fmt.Sprintf("template:%d,", template))
2026-02-10 04:11:52 +00:00
for _, k := range keys {
sb.WriteString(k)
sb.WriteString(":") // Separator between key and value
sb.WriteString(m[k])
sb.WriteString(",") // Separator between pairs
}
// Compute SHA-256 hash
hasher := sha256.New()
hasher.Write([]byte(sb.String()))
hashBytes := hasher.Sum(nil)
// Convert to hex string and return
return hex.EncodeToString(hashBytes)
}
func sendEmailBegin(ctx context.Context, source string, destination string, template int32, subject string, data map[string]string) error {
public_id := generatePublicId(template, data)
data["URLViewInBrowser"] = urlEmailInBrowser(public_id)
e, err := insertEmailLog(ctx, data, destination, public_id, config.ForwardEmailRMOAddress, subject, template)
if err != nil {
return fmt.Errorf("Failed to store email log: %w", err)
}
return background.NewEmailSend(ctx, db.PGInstance.BobDB, *e)
}
func sendEmailComplete(ctx context.Context, txn bob.Executor, email_id int32) error {
email_log, err := models.FindCommsEmailLog(ctx, txn, email_id)
if err != nil {
return fmt.Errorf("find email: %w", err)
}
data := db.ConvertFromPGData(email_log.TemplateData)
text, html, err := renderEmailTemplates(email_log.TemplateID, data)
if err != nil {
return fmt.Errorf("Failed to render email report notification template: %w", err)
}
resp, err := email.Send(ctx, email.Request{
From: config.ForwardEmailRMOAddress,
HTML: html,
Subject: email_log.Subject,
Text: text,
To: email_log.Destination,
})
if err != nil {
return fmt.Errorf("Failed to send email %d: %w", email_log.ID, err)
}
log.Info().Str("response id", resp.ID).Int32("email id", email_log.ID).Msg("Sent email")
return nil
}