diff --git a/background/background.go b/background/background.go
index 24e6da21..8ebded61 100644
--- a/background/background.go
+++ b/background/background.go
@@ -5,10 +5,10 @@ import (
"fmt"
"sync"
- "github.com/Gleipnir-Technology/nidus-sync/comms/email"
"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/email"
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
"github.com/rs/zerolog/log"
)
diff --git a/background/email.go b/background/email.go
index 352adabb..6db77aea 100644
--- a/background/email.go
+++ b/background/email.go
@@ -3,7 +3,7 @@ package background
import (
"context"
- "github.com/Gleipnir-Technology/nidus-sync/comms/email"
+ "github.com/Gleipnir-Technology/nidus-sync/platform/email"
"github.com/rs/zerolog/log"
)
diff --git a/comms/email/db.go b/comms/email/db.go
deleted file mode 100644
index bf211ee9..00000000
--- a/comms/email/db.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package email
-
-import (
- "context"
- "crypto/sha256"
- "database/sql"
- "encoding/hex"
- "fmt"
- "sort"
- "strings"
- "time"
-
- "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"
- "github.com/aarondl/opt/omit"
- "github.com/aarondl/opt/omitnull"
- "github.com/rs/zerolog/log"
-)
-
-func convertToPGData(data map[string]string) pgtypes.HStore {
- result := pgtypes.HStore{}
- for k, v := range data {
- result[k] = sql.Null[string]{V: v, Valid: true}
- }
- return result
-}
-
-func convertFromPGData(d pgtypes.HStore) map[string]string {
- result := make(map[string]string, 0)
- for k, v := range d {
- value, err := v.Value()
- if err != nil {
- log.Warn().Err(err).Str("key", k).Msg("Failed to convert from HSTORE")
- continue
- }
- value_str, ok := value.(string)
- if !ok {
- log.Warn().Msg("Failed to convert to string")
- }
- result[k] = value_str
- }
- return result
-}
-
-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) (err error) {
- data_for_insert := convertToPGData(data)
- var type_ enums.CommsMessagetypeemail
- switch template_id {
- case templateReportNotificationConfirmationID:
- type_ = enums.CommsMessagetypeemailReportNotificationConfirmation
- case templateInitialID:
- type_ = enums.CommsMessagetypeemailInitialContact
- default:
- return fmt.Errorf("Unrecognized template ID %d", template_id)
- }
- _, err = models.CommsEmailLogs.Insert(&models.CommsEmailLogSetter{
- //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)
-
- return err
-}
-func generatePublicId(t enums.CommsMessagetypeemail, m map[string]string) string {
- 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("type:%s,", t))
- 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)
-}
diff --git a/comms/email/email.go b/comms/email/email.go
index ed33d08d..21e96422 100644
--- a/comms/email/email.go
+++ b/comms/email/email.go
@@ -18,23 +18,7 @@ type attachmentRequest struct {
Content string `json:"content"`
}
-type contentEmailBase struct {
- URLLogo string
- URLUnsubscribe string
- URLViewInBrowser string
-}
-
-type contentEmailReportConfirmation struct {
- Base contentEmailBase
- URLReportStatus string
-}
-type contentEmailInitial struct {
- Base contentEmailBase
- Destination string
- URLSubscribe string
-}
-
-type emailRequest struct {
+type Request struct {
From string `json:"from"`
To string `json:"to"`
CC []string `json:"cc,omitempty"`
@@ -83,7 +67,7 @@ type emailResponse struct {
var FORWARDEMAIL_API = "https://api.forwardemail.net/v1/emails"
-func sendEmail(ctx context.Context, email emailRequest, t enums.CommsMessagetypeemail) (response emailResponse, err error) {
+func Send(ctx context.Context, email Request, t enums.CommsMessagetypeemail) (response emailResponse, err error) {
payload, err := json.Marshal(email)
if err != nil {
return response, fmt.Errorf("Failed to marshal email request: %w", err)
diff --git a/comms/email/template/report-notification-confirmation.html b/comms/email/template/report-notification-confirmation.html
deleted file mode 100644
index c0537cda..00000000
--- a/comms/email/template/report-notification-confirmation.html
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
- Thank You for Your Mosquito Report
-
-
-
-
- {{if not .IsBrowser}}
-
- {{end}}
-
-
-
-
-
Thank You for Your Report
-
-
We've received your mosquito report {{.C.ReportIDStr}}. 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:
-
-
-
-
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.
-
You can unsubscribe from notifications about this report by clicking here
-
-
-
-
-
-
diff --git a/main.go b/main.go
index 35fbbc3d..9c40d266 100644
--- a/main.go
+++ b/main.go
@@ -13,11 +13,11 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/background"
- "github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/html"
"github.com/Gleipnir-Technology/nidus-sync/llm"
+ "github.com/Gleipnir-Technology/nidus-sync/platform/email"
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
"github.com/Gleipnir-Technology/nidus-sync/rmo"
nidussync "github.com/Gleipnir-Technology/nidus-sync/sync"
diff --git a/comms/email/initial.go b/platform/email/initial.go
similarity index 90%
rename from comms/email/initial.go
rename to platform/email/initial.go
index aaacdc44..510b4b03 100644
--- a/comms/email/initial.go
+++ b/platform/email/initial.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
+ "github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
@@ -11,6 +12,12 @@ import (
"github.com/rs/zerolog/log"
)
+type contentEmailInitial struct {
+ Base contentEmailBase
+ Destination string
+ URLSubscribe string
+}
+
type jobInitial struct {
base jobEmailBase
}
@@ -20,7 +27,7 @@ func (job jobInitial) Destination() string {
}
func maybeSendInitialEmail(ctx context.Context, destination string) error {
- err := ensureInDB(ctx, destination)
+ err := EnsureInDB(ctx, destination)
if err != nil {
return fmt.Errorf("Failed to add email recipient to database: %w", err)
}
@@ -65,7 +72,7 @@ func sendEmailInitialContact(ctx context.Context, destination string) error {
if err != nil {
return fmt.Errorf("Failed to store email log: %w", err)
}
- resp, err := sendEmail(ctx, emailRequest{
+ resp, err := email.Send(ctx, email.Request{
From: source,
HTML: html,
Subject: subject,
diff --git a/comms/email/job.go b/platform/email/job.go
similarity index 100%
rename from comms/email/job.go
rename to platform/email/job.go
diff --git a/comms/email/report_notification_confirmation.go b/platform/email/report_notification_confirmation.go
similarity index 93%
rename from comms/email/report_notification_confirmation.go
rename to platform/email/report_notification_confirmation.go
index 1551d6cd..14308915 100644
--- a/comms/email/report_notification_confirmation.go
+++ b/platform/email/report_notification_confirmation.go
@@ -4,11 +4,17 @@ import (
"context"
"fmt"
+ "github.com/Gleipnir-Technology/nidus-sync/comms/email"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/rs/zerolog/log"
)
+type contentEmailReportConfirmation struct {
+ Base contentEmailBase
+ URLReportStatus string
+}
+
func NewJobReportNotificationConfirmation(destination, report_id string) Job {
return jobEmailReportNotificationConfirmation{
dest: destination,
@@ -68,7 +74,7 @@ func sendEmailReportConfirmation(ctx context.Context, job Job) error {
if err != nil {
return fmt.Errorf("Failed to store email log: %w", err)
}
- resp, err := sendEmail(ctx, emailRequest{
+ resp, err := email.Send(ctx, email.Request{
From: config.ForwardEmailReportAddress,
HTML: html,
Subject: subject,
diff --git a/comms/email/template.go b/platform/email/template.go
similarity index 98%
rename from comms/email/template.go
rename to platform/email/template.go
index cc7dcd29..0eba3377 100644
--- a/comms/email/template.go
+++ b/platform/email/template.go
@@ -37,6 +37,12 @@ var (
templateReportNotificationConfirmationID int32
)
+type contentEmailBase struct {
+ URLLogo string
+ URLUnsubscribe string
+ URLViewInBrowser string
+}
+
type ContentEmailRender struct {
IsBrowser bool
C any
diff --git a/comms/email/template/initial-contact.html b/platform/email/template/initial-contact.html
similarity index 100%
rename from comms/email/template/initial-contact.html
rename to platform/email/template/initial-contact.html
diff --git a/comms/email/template/initial-contact.txt b/platform/email/template/initial-contact.txt
similarity index 100%
rename from comms/email/template/initial-contact.txt
rename to platform/email/template/initial-contact.txt
diff --git a/platform/email/template/report-notification-confirmation.html b/platform/email/template/report-notification-confirmation.html
new file mode 100644
index 00000000..3bb88e8a
--- /dev/null
+++ b/platform/email/template/report-notification-confirmation.html
@@ -0,0 +1,130 @@
+
+
+
+
+
+ Thank You for Your Mosquito Report
+
+
+
+
+ {{ if not .IsBrowser }}
+
+ {{ end }}
+
+
+
+
+
+
Thank You for Your Report
+
+
+ We've received your mosquito report {{ .C.ReportIDStr }}. 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:
+
+
+
+
+
+ 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.
+
+
+ You can unsubscribe from notifications about this report by clicking
+ here
+
+
+
+
+
+
+
diff --git a/comms/email/template/report-notification-confirmation.txt b/platform/email/template/report-notification-confirmation.txt
similarity index 100%
rename from comms/email/template/report-notification-confirmation.txt
rename to platform/email/template/report-notification-confirmation.txt
diff --git a/platform/report/notification.go b/platform/report/notification.go
index b656106e..193401f1 100644
--- a/platform/report/notification.go
+++ b/platform/report/notification.go
@@ -19,6 +19,7 @@ import (
"github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/db/sql"
+ "github.com/Gleipnir-Technology/nidus-sync/platform/email"
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
"github.com/rs/zerolog/log"
//"github.com/stephenafamo/scan"
@@ -65,16 +66,20 @@ func GenerateReportID() (string, error) {
return builder.String(), nil
}
-func RegisterNotificationEmail(ctx context.Context, txn bob.Tx, report_id string, email string) *ErrorWithCode {
+func RegisterNotificationEmail(ctx context.Context, txn bob.Tx, report_id string, destination string) *ErrorWithCode {
some_report, err := findSomeReport(ctx, report_id)
if err != nil {
return err
}
- err = some_report.addNotificationEmail(ctx, txn, email)
+ e := email.EnsureInDB(ctx, destination)
+ if e != nil {
+ return newInternalError(e, "Failed to ensure phone is in DB")
+ }
+ err = some_report.addNotificationEmail(ctx, txn, destination)
if err != nil {
return err
}
- background.ReportSubscriptionConfirmationEmail(email, report_id)
+ background.ReportSubscriptionConfirmationEmail(destination, report_id)
return nil
}
@@ -83,6 +88,10 @@ func RegisterNotificationPhone(ctx context.Context, txn bob.Tx, report_id string
if err != nil {
return err
}
+ e := text.EnsureInDB(ctx, phone)
+ if e != nil {
+ return newInternalError(e, "Failed to ensure phone is in DB")
+ }
err = some_report.addNotificationPhone(ctx, txn, phone)
if err != nil {
return err
@@ -92,11 +101,11 @@ func RegisterNotificationPhone(ctx context.Context, txn bob.Tx, report_id string
}
func RegisterSubscriptionEmail(ctx context.Context, txn bob.Tx, email string) *ErrorWithCode {
- log.Warn().Msg("RegisterSubscription not implemented yet")
+ log.Warn().Str("email", email).Msg("RegisterSubscription not implemented yet")
return nil
}
func RegisterSubscriptionPhone(ctx context.Context, txn bob.Tx, phone text.E164) *ErrorWithCode {
- log.Warn().Msg("RegisterSubscription not implemented yet")
+ log.Warn().Str("phone", text.PhoneString(phone)).Msg("RegisterSubscription not implemented yet")
return nil
}
diff --git a/platform/text/text.go b/platform/text/text.go
index d54a62d9..dec95246 100644
--- a/platform/text/text.go
+++ b/platform/text/text.go
@@ -23,10 +23,9 @@ import (
type E164 = phonenumbers.PhoneNumber
-func PhoneString(p E164) string {
- return phonenumbers.Format(&p, phonenumbers.E164)
+func EnsureInDB(ctx context.Context, destination E164) (err error) {
+ return ensureInDB(ctx, PhoneString(destination))
}
-
func HandleTextMessage(src string, dst string, body string) {
ctx := context.Background()
@@ -98,15 +97,27 @@ func ParsePhoneNumber(input string) (*E164, error) {
return phonenumbers.Parse(input, "US")
}
+func PhoneString(p E164) string {
+ return phonenumbers.Format(&p, phonenumbers.E164)
+}
+
func StoreSources() error {
ctx := context.TODO()
for _, n := range []string{config.PhoneNumberReportStr, config.PhoneNumberSupportStr, config.VoipMSNumber} {
var err error
// Deal with Voip.ms not expecting API calls with the prefixed +1
if !strings.HasPrefix(n, "+1") {
- err = ensureInDB(ctx, "+1"+n)
+ dest, err := ParsePhoneNumber("+1" + n)
+ if err != nil {
+ return fmt.Errorf("Failed to parse +1'%s' as phone number: %w", n, err)
+ }
+ err = EnsureInDB(ctx, *dest)
} else {
- err = ensureInDB(ctx, n)
+ dest, err := ParsePhoneNumber(n)
+ if err != nil {
+ return fmt.Errorf("Failed to parse '%s' as phone number: %w", n, err)
+ }
+ err = EnsureInDB(ctx, *dest)
}
if err != nil {
return fmt.Errorf("Failed to add number '%s' to DB: %w", n, err)
@@ -172,21 +183,6 @@ func sendInitialText(ctx context.Context, src string, dst string) error {
return nil
}
-func ensureInitialText(ctx context.Context, src string, dst string) error {
- //
- rows, err := models.CommsTextLogs.Query(
- models.SelectWhere.CommsTextLogs.Destination.EQ(dst),
- models.SelectWhere.CommsTextLogs.IsWelcome.EQ(true),
- ).All(ctx, db.PGInstance.BobDB)
- if err != nil {
- return fmt.Errorf("Failed to query text logs: %w", err)
- }
- if len(rows) > 0 {
- return nil
- }
- return sendInitialText(ctx, src, dst)
-}
-
func ensureInDB(ctx context.Context, destination string) (err error) {
_, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination)
if err != nil {
@@ -208,6 +204,21 @@ func ensureInDB(ctx context.Context, destination string) (err error) {
return nil
}
+func ensureInitialText(ctx context.Context, src string, dst string) error {
+ //
+ rows, err := models.CommsTextLogs.Query(
+ models.SelectWhere.CommsTextLogs.Destination.EQ(dst),
+ models.SelectWhere.CommsTextLogs.IsWelcome.EQ(true),
+ ).All(ctx, db.PGInstance.BobDB)
+ if err != nil {
+ return fmt.Errorf("Failed to query text logs: %w", err)
+ }
+ if len(rows) > 0 {
+ return nil
+ }
+ return sendInitialText(ctx, src, dst)
+}
+
func generateNextMessage(ctx context.Context, history []llm.Message, customer_phone string) (llm.Message, error) {
_handle_report_status := func() (string, error) {
return "Report: ABCD-1234-5678, District: Delta MVCD, Status: scheduled, Appointment: Wednesday 3:30pm", nil
diff --git a/rmo/email.go b/rmo/email.go
index 0b879ad6..473adbfc 100644
--- a/rmo/email.go
+++ b/rmo/email.go
@@ -3,10 +3,10 @@ package rmo
import (
"net/http"
- "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/Gleipnir-Technology/nidus-sync/html"
+ "github.com/Gleipnir-Technology/nidus-sync/platform/email"
"github.com/aarondl/opt/omit"
"github.com/go-chi/chi/v5"
)