Fold more text logic into the platform
Because it is better at managing the database, the comms/text package will just be for integration.
This commit is contained in:
parent
e8e840ec44
commit
407b478637
5 changed files with 98 additions and 115 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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{}
|
||||
|
|
|
|||
4
main.go
4
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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue