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}} -
- Email not displaying correctly? View it in your browser -
- {{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:

- -
- View Report Status -
- -

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 }} +
+ Email not displaying correctly? + View it in your browser +
+ {{ 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: +

+ +
+ View Report Status +
+ +

+ 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" )