From 407b4786373ed5083898a1fe4f543e53adf69845 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Mon, 26 Jan 2026 21:21:21 +0000 Subject: [PATCH] Fold more text logic into the platform Because it is better at managing the database, the comms/text package will just be for integration. --- comms/text/db.go | 63 ---------------------------- comms/text/initial.go | 37 ---------------- comms/text/text.go | 11 +---- main.go | 4 +- platform/text.go | 98 +++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 98 insertions(+), 115 deletions(-) delete mode 100644 comms/text/initial.go diff --git a/comms/text/db.go b/comms/text/db.go index 791dac38..cd6c3d7d 100644 --- a/comms/text/db.go +++ b/comms/text/db.go @@ -1,31 +1,17 @@ package text import ( - "context" "crypto/sha256" "database/sql" "encoding/hex" "fmt" "sort" "strings" - "time" - "github.com/Gleipnir-Technology/nidus-sync/config" - "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/nyaruka/phonenumbers" - "github.com/rs/zerolog/log" "github.com/stephenafamo/bob/types/pgtypes" ) -func StoreSources() error { - ctx := context.TODO() - src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) - return ensureInDB(ctx, src) -} func convertToPGData(data map[string]string) pgtypes.HStore { result := pgtypes.HStore{} for k, v := range data { @@ -34,55 +20,6 @@ func convertToPGData(data map[string]string) pgtypes.HStore { return result } -func delayMessage(ctx context.Context, source string, destination string, content string, type_ enums.CommsTextjobtype) error { - job, err := models.CommsTextJobs.Insert(&models.CommsTextJobSetter{ - Content: omit.From(content), - Created: omit.From(time.Now()), - Destination: omit.From(destination), - //ID: - Type: omit.From(type_), - }).One(ctx, db.PGInstance.BobDB) - if err != nil { - return fmt.Errorf("Failed to add delayed text job: %w", err) - } - log.Info().Int32("id", job.ID).Msg("Created delayed text job") - return nil -} - -func ensureInDB(ctx context.Context, destination string) (err error) { - _, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) - if err != nil { - // doesn't exist - if err.Error() == "sql: no rows in result set" { - _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ - E164: omit.From(destination), - IsSubscribed: omitnull.FromPtr[bool](nil), - }).One(ctx, db.PGInstance.BobDB) - if err != nil { - return fmt.Errorf("Failed to insert new phone contact: %w", err) - } - log.Info().Str("phone", destination).Msg("Added text to the comms database") - return nil - } - return fmt.Errorf("Unexpected error searching for phone contact: %w", err) - } - return nil -} - -func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (err error) { - _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ - //ID: - Content: omit.From(content), - Created: omit.From(time.Now()), - Destination: omit.From(destination), - IsWelcome: omit.From(is_welcome), - Origin: omit.From(origin), - Source: omit.From(source), - }).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 diff --git a/comms/text/initial.go b/comms/text/initial.go deleted file mode 100644 index 0c25a62c..00000000 --- a/comms/text/initial.go +++ /dev/null @@ -1,37 +0,0 @@ -package text - -import ( - "context" - "fmt" - - "github.com/Gleipnir-Technology/nidus-sync/db" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" - "github.com/Gleipnir-Technology/nidus-sync/db/models" -) - -func ensureInitialText(ctx context.Context, src string, dst string) error { - // - origin := enums.CommsTextoriginWebsiteAction - 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 - } - content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" - err = sendText(ctx, src, dst, content, origin, true) - if err != nil { - return fmt.Errorf("Failed to send initial confirmation: %w", err) - } - return nil -} - -func SendInitialReprompt(ctx context.Context, src string, dst string) error { - content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" - err := sendText(ctx, src, dst, content, enums.CommsTextoriginLLM, false) - return err -} diff --git a/comms/text/text.go b/comms/text/text.go index 8384405f..34d2aa7a 100644 --- a/comms/text/text.go +++ b/comms/text/text.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/Gleipnir-Technology/nidus-sync/config" - "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" "github.com/twilio/twilio-go" @@ -19,15 +18,7 @@ func ParsePhoneNumber(input string) (*E164, error) { return phonenumbers.Parse(input, "US") } -func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool) error { - err := ensureInDB(ctx, destination) - if err != nil { - return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) - } - err = insertTextLog(ctx, message, destination, source, origin, is_welcome) - if err != nil { - return fmt.Errorf("Failed to insert text message in the DB: %w", err) - } +func SendText(ctx context.Context, source string, destination string, message string) error { client := twilio.NewRestClient() params := &twilioApi.CreateMessageParams{} diff --git a/main.go b/main.go index ee3b1c8c..012f9372 100644 --- a/main.go +++ b/main.go @@ -13,10 +13,10 @@ 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/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/llm" + "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/public-report" nidussync "github.com/Gleipnir-Technology/nidus-sync/sync" "github.com/go-chi/chi/v5" @@ -48,7 +48,7 @@ func main() { os.Exit(3) } - err = text.StoreSources() + err = platform.TextStoreSources() if err != nil { log.Error().Err(err).Msg("Failed to store text source phone numbers") os.Exit(4) diff --git a/platform/text.go b/platform/text.go index e857a702..f77a6e77 100644 --- a/platform/text.go +++ b/platform/text.go @@ -4,17 +4,97 @@ import ( "context" "fmt" "strings" + "time" "github.com/Gleipnir-Technology/nidus-sync/comms/text" "github.com/Gleipnir-Technology/nidus-sync/config" "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/db/sql" "github.com/Gleipnir-Technology/nidus-sync/llm" + "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" + "github.com/nyaruka/phonenumbers" "github.com/rs/zerolog/log" ) +func TextStoreSources() error { + ctx := context.TODO() + src := phonenumbers.Format(&config.PhoneNumberReport, phonenumbers.E164) + return ensureInDB(ctx, src) +} + +func delayMessage(ctx context.Context, source string, destination string, content string, type_ enums.CommsTextjobtype) error { + job, err := models.CommsTextJobs.Insert(&models.CommsTextJobSetter{ + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + //ID: + Type: omit.From(type_), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to add delayed text job: %w", err) + } + log.Info().Int32("id", job.ID).Msg("Created delayed text job") + return nil +} + +func ensureInitialText(ctx context.Context, src string, dst string) error { + // + origin := enums.CommsTextoriginWebsiteAction + 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 + } + content := "Welcome to Report Mosquitoes Online. We received your request and want to confirm text updates. Reply YES to continue. Reply STOP at any time to unsubscribe" + err = sendText(ctx, src, dst, content, origin, true) + if err != nil { + return fmt.Errorf("Failed to send initial confirmation: %w", err) + } + return nil +} + +func ensureInDB(ctx context.Context, destination string) (err error) { + _, err = models.FindCommsPhone(ctx, db.PGInstance.BobDB, destination) + if err != nil { + // doesn't exist + if err.Error() == "sql: no rows in result set" { + _, err = models.CommsPhones.Insert(&models.CommsPhoneSetter{ + E164: omit.From(destination), + IsSubscribed: omitnull.FromPtr[bool](nil), + }).One(ctx, db.PGInstance.BobDB) + if err != nil { + return fmt.Errorf("Failed to insert new phone contact: %w", err) + } + log.Info().Str("phone", destination).Msg("Added text to the comms database") + return nil + } + return fmt.Errorf("Unexpected error searching for phone contact: %w", err) + } + return nil +} + +func insertTextLog(ctx context.Context, content string, destination string, source string, origin enums.CommsTextorigin, is_welcome bool) (err error) { + _, err = models.CommsTextLogs.Insert(&models.CommsTextLogSetter{ + //ID: + Content: omit.From(content), + Created: omit.From(time.Now()), + Destination: omit.From(destination), + IsWelcome: omit.From(is_welcome), + Origin: omit.From(origin), + Source: omit.From(source), + }).One(ctx, db.PGInstance.BobDB) + + return err +} + // Translate from Twilio's representation of a RCS message sender to our concept of a phone number // From: rcs:dev_report_mosquitoes_online_dosrvwxm_agent // To: +16235525879 @@ -50,6 +130,19 @@ func loadPreviousMessages(ctx context.Context, dst, src string) ([]llm.Message, return results, nil } +func sendText(ctx context.Context, source string, destination string, message string, origin enums.CommsTextorigin, is_welcome bool) error { + err := ensureInDB(ctx, destination) + if err != nil { + return fmt.Errorf("Failed to ensure text message destination is in the DB: %w", err) + } + err = insertTextLog(ctx, message, destination, source, origin, is_welcome) + if err != nil { + return fmt.Errorf("Failed to insert text message in the DB: %w", err) + } + err = text.SendText(ctx, source, destination, message) + return nil +} + func splitPhoneSource(s string) (string, string) { parts := strings.Split(s, ":") switch len(parts) { @@ -117,15 +210,14 @@ func HandleTextMessage(from string, to string, body string) { setSubscribed(ctx, src, true) handleWaitingTextJobs(ctx, src) default: - err = text.SendInitialReprompt(ctx, dst, src) + content := "I have to start with either 'YES' or 'STOP' first, Which do you want?" + err := text.SendText(ctx, src, dst, content) if err != nil { log.Error().Err(err).Msg("Failed to resend initial prompt.") } } return } - if !(*subscribed) { - } previous_messages, err := loadPreviousMessages(ctx, dst, src) if err != nil { log.Error().Err(err).Str("dst", dst).Str("src", from).Msg("Failed to get previous messages")