diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b26f364e8..5e57c1367 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,13 +2,13 @@ name: CI on: [push, pull_request] env: go-version: '1.17.x' - postgis-version: '2.5' + postgis-version: '3.1' jobs: test: name: Test strategy: matrix: - pg-version: ['11', '12'] + pg-version: ['12', '13'] runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/CHANGELOG.md b/CHANGELOG.md index ffca190aa..bbfe70f60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +v7.0.1 +---------- + * Update to latest goflow + +v7.0.0 +---------- + * Tweak README + +v6.5.43 +---------- + * Update to latest goflow which adds reverse function + +v6.5.42 +---------- + * Change default resumes per session limit from 500 to 250 + * Update to latest goflow + +v6.5.41 +---------- + * Update to latest goflow which adds sort function + +v6.5.40 +---------- + * Add config option for maximum resumes per session + +v6.5.39 +---------- + * Add notification.email_status + +v6.5.38 +---------- + * Update to latest goflow which simplifies contactql queries after parsing + * Load contacts for flow starts from readonly database + * CI testing on PG12 and 13 + +v6.5.37 +---------- + * Look for From param if Caller param not found in incoming IVR call request + * Update to latest gocommon and go 1.17 + +v6.5.36 +---------- + * Drop ticket.subject + * Remove no longer used FlowStart.CreatedBy + v6.5.35 ---------- * Tweak mailroom startup to show warning if no distinct readonly DB configured diff --git a/README.md b/README.md index 70be6174e..6f2184908 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # About -Mailroom is the [RapidPro](https://github.com/rapidpro/rapidpro) component responsible for the execution of +Mailroom is the [RapidPro](https://github.com/rapidpro/rapidpro) components which does the heavy lifting of running flow starts, campaigns etc. flows. It interacts directly with the RapidPro database and sends and receives messages with [Courier](https://github.com/nyaruka/courier) for handling via Redis. # Deploying diff --git a/core/goflow/engine.go b/core/goflow/engine.go index 7497d2551..5b0a7a20a 100644 --- a/core/goflow/engine.go +++ b/core/goflow/engine.go @@ -61,6 +61,7 @@ func Engine(c *runtime.Config) flows.Engine { WithTicketServiceFactory(ticketFactory(c)). WithAirtimeServiceFactory(airtimeFactory(c)). WithMaxStepsPerSprint(c.MaxStepsPerSprint). + WithMaxResumesPerSession(c.MaxResumesPerSession). Build() }) @@ -84,6 +85,7 @@ func Simulator(c *runtime.Config) flows.Engine { WithTicketServiceFactory(simulatorTicketServiceFactory). // and faked tickets WithAirtimeServiceFactory(simulatorAirtimeServiceFactory). // and faked airtime transfers WithMaxStepsPerSprint(c.MaxStepsPerSprint). + WithMaxResumesPerSession(c.MaxResumesPerSession). Build() }) diff --git a/core/models/contacts.go b/core/models/contacts.go index ba62ed989..2657529ff 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -467,7 +467,6 @@ type contactEnvelope struct { TicketerID TicketerID `json:"ticketer_id"` ExternalID string `json:"external_id"` TopicID TopicID `json:"topic_id"` - Subject string `json:"subject"` Body string `json:"body"` AssigneeID UserID `json:"assignee_id"` } `json:"tickets"` @@ -532,7 +531,6 @@ LEFT JOIN ( array_agg( json_build_object( 'uuid', t.uuid, - 'subject', t.subject, 'body', t.body, 'external_id', t.external_id, 'ticketer_id', t.ticketer_id, diff --git a/core/models/notifications.go b/core/models/notifications.go index b78282a3a..bc68acab2 100644 --- a/core/models/notifications.go +++ b/core/models/notifications.go @@ -22,14 +22,23 @@ const ( NotificationTypeTicketsActivity NotificationType = "tickets:activity" ) +type EmailStatus string + +const ( + EmailStatusPending = "P" + EmailStatusSent = "S" + EmailStatusNone = "N" +) + type Notification struct { - ID NotificationID `db:"id"` - OrgID OrgID `db:"org_id"` - Type NotificationType `db:"notification_type"` - Scope string `db:"scope"` - UserID UserID `db:"user_id"` - IsSeen bool `db:"is_seen"` - CreatedOn time.Time `db:"created_on"` + ID NotificationID `db:"id"` + OrgID OrgID `db:"org_id"` + Type NotificationType `db:"notification_type"` + Scope string `db:"scope"` + UserID UserID `db:"user_id"` + IsSeen bool `db:"is_seen"` + EmailStatus EmailStatus `db:"email_status"` + CreatedOn time.Time `db:"created_on"` ChannelID ChannelID `db:"channel_id"` ContactImportID ContactImportID `db:"contact_import_id"` @@ -107,8 +116,8 @@ func NotificationsFromTicketEvents(ctx context.Context, db Queryer, oa *OrgAsset } const insertNotificationSQL = ` -INSERT INTO notifications_notification(org_id, notification_type, scope, user_id, is_seen, created_on, channel_id, contact_import_id) - VALUES(:org_id, :notification_type, :scope, :user_id, FALSE, NOW(), :channel_id, :contact_import_id) +INSERT INTO notifications_notification(org_id, notification_type, scope, user_id, is_seen, email_status, created_on, channel_id, contact_import_id) + VALUES(:org_id, :notification_type, :scope, :user_id, FALSE, 'N', NOW(), :channel_id, :contact_import_id) ON CONFLICT DO NOTHING` func insertNotifications(ctx context.Context, db Queryer, notifications []*Notification) error { diff --git a/core/models/starts.go b/core/models/starts.go index 035f9be15..0a638ca33 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -167,8 +167,6 @@ type FlowStart struct { Extra null.JSON `json:"extra,omitempty" db:"extra"` ParentSummary null.JSON `json:"parent_summary,omitempty" db:"parent_summary"` SessionHistory null.JSON `json:"session_history,omitempty" db:"session_history"` - - CreatedBy string `json:"created_by"` // TODO deprecated } } @@ -362,7 +360,6 @@ func (s *FlowStart) CreateBatch(contactIDs []ContactID, last bool, totalContacts b.b.Extra = null.JSON(s.Extra()) b.b.IsLast = last b.b.TotalContacts = totalContacts - b.b.CreatedBy = s.s.CreatedBy b.b.CreatedByID = s.s.CreatedByID return b } diff --git a/core/models/starts_test.go b/core/models/starts_test.go index b6260bfca..74f1ebdbd 100644 --- a/core/models/starts_test.go +++ b/core/models/starts_test.go @@ -113,7 +113,6 @@ func TestStartsBuilding(t *testing.T) { "UUID": "1ae96956-4b34-433e-8d1a-f05fe6923d6d", "contact_ids": [%d, %d], "create_contact": true, - "created_by": "", "created_by_id": null, "exclude_group_ids": [%d], "flow_id": %d, diff --git a/core/models/triggers_test.go b/core/models/triggers_test.go index 1100bf506..f6a8d0760 100644 --- a/core/models/triggers_test.go +++ b/core/models/triggers_test.go @@ -124,6 +124,7 @@ func TestFindMatchingMsgTrigger(t *testing.T) { joinID := testdata.InsertKeywordTrigger(db, testdata.Org1, testdata.Favorites, "join", models.MatchFirst, nil, nil) resistID := testdata.InsertKeywordTrigger(db, testdata.Org1, testdata.SingleMessage, "resist", models.MatchOnly, nil, nil) + emojiID := testdata.InsertKeywordTrigger(db, testdata.Org1, testdata.PickANumber, "👍", models.MatchFirst, nil, nil) doctorsID := testdata.InsertKeywordTrigger(db, testdata.Org1, testdata.SingleMessage, "resist", models.MatchOnly, []*testdata.Group{testdata.DoctorsGroup}, nil) doctorsAndNotTestersID := testdata.InsertKeywordTrigger(db, testdata.Org1, testdata.SingleMessage, "resist", models.MatchOnly, []*testdata.Group{testdata.DoctorsGroup}, []*testdata.Group{testdata.TestersGroup}) doctorsCatchallID := testdata.InsertCatchallTrigger(db, testdata.Org1, testdata.SingleMessage, []*testdata.Group{testdata.DoctorsGroup}, nil) @@ -147,13 +148,16 @@ func TestFindMatchingMsgTrigger(t *testing.T) { contact *flows.Contact expectedTriggerID models.TriggerID }{ - {"join", cathy, joinID}, + {" join ", cathy, joinID}, {"JOIN", cathy, joinID}, {"join this", cathy, joinID}, {"resist", george, resistID}, {"resist", bob, doctorsID}, {"resist", cathy, doctorsAndNotTestersID}, {"resist this", cathy, doctorsCatchallID}, + {" 👍 ", george, emojiID}, + {"👍🏾", george, emojiID}, // is 👍 + 🏾 + {"😀👍", george, othersAllID}, {"other", cathy, doctorsCatchallID}, {"other", george, othersAllID}, {"", george, othersAllID}, diff --git a/core/runner/runner.go b/core/runner/runner.go index 7bfed2942..3f259327c 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -494,7 +494,7 @@ func StartFlow( } // load our locked contacts - contacts, err := models.LoadContacts(ctx, rt.DB, oa, locked) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, locked) if err != nil { return nil, errors.Wrapf(err, "error loading contacts to start") } diff --git a/core/tasks/handler/worker.go b/core/tasks/handler/worker.go index 2cd8341b4..00087ac83 100644 --- a/core/tasks/handler/worker.go +++ b/core/tasks/handler/worker.go @@ -208,7 +208,7 @@ func handleTimedEvent(ctx context.Context, rt *runtime.Runtime, eventType string } // load our contact - contacts, err := models.LoadContacts(ctx, rt.DB, oa, []models.ContactID{event.ContactID}) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, []models.ContactID{event.ContactID}) if err != nil { return errors.Wrapf(err, "error loading contact") } @@ -308,7 +308,7 @@ func HandleChannelEvent(ctx context.Context, rt *runtime.Runtime, eventType mode } // load our contact - contacts, err := models.LoadContacts(ctx, rt.DB, oa, []models.ContactID{event.ContactID()}) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, []models.ContactID{event.ContactID()}) if err != nil { return nil, errors.Wrapf(err, "error loading contact") } @@ -491,7 +491,7 @@ func handleMsgEvent(ctx context.Context, rt *runtime.Runtime, event *MsgEvent) e } // load our contact - contacts, err := models.LoadContacts(ctx, rt.DB, oa, []models.ContactID{event.ContactID}) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, []models.ContactID{event.ContactID}) if err != nil { return errors.Wrapf(err, "error loading contact") } @@ -671,7 +671,7 @@ func handleTicketEvent(ctx context.Context, rt *runtime.Runtime, event *models.T modelTicket := tickets[0] // load our contact - contacts, err := models.LoadContacts(ctx, rt.DB, oa, []models.ContactID{modelTicket.ContactID()}) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, []models.ContactID{modelTicket.ContactID()}) if err != nil { return errors.Wrapf(err, "error loading contact") } diff --git a/core/tasks/ivr/worker.go b/core/tasks/ivr/worker.go index 8b9147471..57e118b26 100644 --- a/core/tasks/ivr/worker.go +++ b/core/tasks/ivr/worker.go @@ -79,7 +79,7 @@ func HandleFlowStartBatch(bg context.Context, rt *runtime.Runtime, batch *models } // ok, we can initiate calls for the remaining contacts - contacts, err := models.LoadContacts(ctx, rt.DB, oa, contactIDs) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, contactIDs) if err != nil { return errors.Wrapf(err, "error loading contacts") } diff --git a/docker/Dockerfile b/docker/Dockerfile index c9feda2bd..87658be9b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16.6-alpine3.14 +FROM golang:1.17.3-alpine3.14 WORKDIR /app diff --git a/go.mod b/go.mod index 0f8bff2a2..046d213f0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/nyaruka/mailroom require ( github.com/Masterminds/semver v1.5.0 github.com/apex/log v1.1.4 - github.com/aws/aws-sdk-go v1.35.20 + github.com/aws/aws-sdk-go v1.40.56 github.com/buger/jsonparser v1.0.0 github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect github.com/edganiukov/fcm v0.4.0 @@ -18,8 +18,8 @@ require ( github.com/lib/pq v1.4.0 github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/nyaruka/ezconf v0.2.1 - github.com/nyaruka/gocommon v1.14.0 - github.com/nyaruka/goflow v0.136.4 + github.com/nyaruka/gocommon v1.14.1 + github.com/nyaruka/goflow v0.140.1 github.com/nyaruka/librato v1.0.0 github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d github.com/nyaruka/null v1.2.0 @@ -34,4 +34,32 @@ require ( gopkg.in/go-playground/validator.v9 v9.31.0 ) -go 1.16 +require ( + github.com/antlr/antlr4 v0.0.0-20200701161529-3d9351f61e0f // indirect + github.com/blevesearch/segment v0.9.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/structs v1.0.0 // indirect + github.com/go-mail/mail v2.3.1+incompatible // indirect + github.com/go-playground/locales v0.13.0 // indirect + github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/gofrs/uuid v3.3.0+incompatible // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/leodido/go-urn v1.2.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/naoina/go-stringutil v0.1.0 // indirect + github.com/naoina/toml v0.1.1 // indirect + github.com/nyaruka/phonenumbers v1.0.71 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect + golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect + golang.org/x/text v0.3.6 // indirect + google.golang.org/protobuf v1.21.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) + +go 1.17 diff --git a/go.sum b/go.sum index 47498da95..a15cf5a30 100644 --- a/go.sum +++ b/go.sum @@ -14,9 +14,9 @@ github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDw github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.34.31/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= -github.com/aws/aws-sdk-go v1.35.20 h1:Hs7x9Czh+MMPnZLQqHhsuZKeNFA3Vuf7pdy2r5QlVb0= github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= +github.com/aws/aws-sdk-go v1.40.56 h1:FM2yjR0UUYFzDTMx+mH9Vyw1k1EUUxsAFzk+BjkzANA= +github.com/aws/aws-sdk-go v1.40.56/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -132,10 +132,10 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.2.1 h1:TDXWoqjqYya1uhou1mAJZg7rgFYL98EB0Tb3+BWtUh0= github.com/nyaruka/ezconf v0.2.1/go.mod h1:ey182kYkw2MIi4XiWe1FR/mzI33WCmTWuceDYYxgnQw= -github.com/nyaruka/gocommon v1.14.0 h1:sZ0rsKy52GAHfEB6H74thj0cf7VIW9uRYpuFBe1iJ9Q= -github.com/nyaruka/gocommon v1.14.0/go.mod h1:J0BvHSsj8gjMp0oPW+PEb4x25oStupkNpHm7Y5OfNPo= -github.com/nyaruka/goflow v0.136.4 h1:Tl5oe1yLMXFFTSIRSHcpd5llLs63lAIATSogEZor8As= -github.com/nyaruka/goflow v0.136.4/go.mod h1:kqvs8GzFkLjogLqLmNJmpuvEHFvlKsALlDWgC25AtGk= +github.com/nyaruka/gocommon v1.14.1 h1:/ScvLmg4zzVAuZ78TaENrvSEvW3WnUdqRd/t9hX7z7E= +github.com/nyaruka/gocommon v1.14.1/go.mod h1:R1Vr7PwrYCSu+vcU0t8t/5C4TsCwcWoqiuIQCxcMqxs= +github.com/nyaruka/goflow v0.140.1 h1:B/ikb/eOgqzEIoKWYjTSQtb5h3AHpnf/xrTS0H2lJLA= +github.com/nyaruka/goflow v0.140.1/go.mod h1:s3f7q2k6IKZicOcu2mu2EcuKgK3hava43Zb3cagtpVM= github.com/nyaruka/librato v1.0.0 h1:Vznj9WCeC1yZXbBYyYp40KnbmXLbEkjKmHesV/v2SR0= github.com/nyaruka/librato v1.0.0/go.mod h1:pkRNLFhFurOz0QqBz6/DuTFhHHxAubWxs4Jx+J7yUgg= github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d h1:hyp9u36KIwbTCo2JAJ+TuJcJBc+UZzEig7RI/S5Dvkc= @@ -199,7 +199,6 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -214,8 +213,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321 h1:lleNcKRbcaC8MqgLwghIkzZ2JBQAb7QQ9MiwRt1BisA= -golang.org/x/net v0.0.0-20200925080053-05aa5d4ee321/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -230,12 +229,15 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/mailroom.go b/mailroom.go index cbb2d6891..967075f41 100644 --- a/mailroom.go +++ b/mailroom.go @@ -113,6 +113,7 @@ func (mr *Mailroom) Start() error { Region: c.S3Region, DisableSSL: c.S3DisableSSL, ForcePathStyle: c.S3ForcePathStyle, + MaxRetries: 3, }) if err != nil { return err diff --git a/mailroom_test.dump b/mailroom_test.dump index a3ddfb51a..849956616 100644 Binary files a/mailroom_test.dump and b/mailroom_test.dump differ diff --git a/runtime/config.go b/runtime/config.go index 339dc60ec..1c502a9fb 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -37,6 +37,7 @@ type Config struct { SMTPServer string `help:"the smtp configuration for sending emails ex: smtp://user%40password@server:port/?from=foo%40gmail.com"` DisallowedNetworks string `help:"comma separated list of IP addresses and networks which engine can't make HTTP calls to"` MaxStepsPerSprint int `help:"the maximum number of steps allowed per engine sprint"` + MaxResumesPerSession int `help:"the maximum number of resumes allowed per engine session"` MaxValueLength int `help:"the maximum size in characters for contact field values and run result values"` S3Endpoint string `help:"the S3 endpoint we will write attachments to"` @@ -87,6 +88,7 @@ func NewDefaultConfig() *Config { SMTPServer: "", DisallowedNetworks: `127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,fe80::/10`, MaxStepsPerSprint: 100, + MaxResumesPerSession: 250, MaxValueLength: 640, S3Endpoint: "https://s3.amazonaws.com", diff --git a/services/ivr/twiml/service.go b/services/ivr/twiml/service.go index 3178ffe26..d9bcd3060 100644 --- a/services/ivr/twiml/service.go +++ b/services/ivr/twiml/service.go @@ -93,8 +93,6 @@ var validLanguageCodes = map[string]bool{ "zh-TW": true, } -var indentMarshal = true - type service struct { httpClient *http.Client channel *models.Channel @@ -173,7 +171,10 @@ func (s *service) URNForRequest(r *http.Request) (urns.URN, error) { r.ParseForm() tel := r.Form.Get("Caller") if tel == "" { - return "", errors.Errorf("no Caller parameter found in URL: %s", r.URL) + tel = r.Form.Get("From") + } + if tel == "" { + return "", errors.New("no Caller or From parameter found in request") } return urns.NewTelURNForCountry(tel, "") } @@ -364,7 +365,7 @@ func (s *service) WriteSessionResponse(ctx context.Context, rt *runtime.Runtime, } // get our response - response, err := responseForSprint(rt.Config, number, resumeURL, session.Wait(), sprint.Events()) + response, err := ResponseForSprint(rt.Config, number, resumeURL, session.Wait(), sprint.Events(), true) if err != nil { return errors.Wrap(err, "unable to build response for IVR call") } @@ -444,7 +445,7 @@ func twCalculateSignature(url string, form url.Values, authToken string) ([]byte // TWIML building utilities -func responseForSprint(cfg *runtime.Config, number urns.URN, resumeURL string, w flows.ActivatedWait, es []flows.Event) (string, error) { +func ResponseForSprint(cfg *runtime.Config, number urns.URN, resumeURL string, w flows.ActivatedWait, es []flows.Event, indent bool) (string, error) { r := &Response{} commands := make([]interface{}, 0) @@ -517,7 +518,7 @@ func responseForSprint(cfg *runtime.Config, number urns.URN, resumeURL string, w var body []byte var err error - if indentMarshal { + if indent { body, err = xml.MarshalIndent(r, "", " ") } else { body, err = xml.Marshal(r) diff --git a/services/ivr/twiml/service_test.go b/services/ivr/twiml/service_test.go index 1684f9c7d..e1f8bbd1a 100644 --- a/services/ivr/twiml/service_test.go +++ b/services/ivr/twiml/service_test.go @@ -1,7 +1,10 @@ -package twiml +package twiml_test import ( "encoding/xml" + "net/http" + "strconv" + "strings" "testing" "github.com/nyaruka/gocommon/urns" @@ -11,6 +14,7 @@ import ( "github.com/nyaruka/goflow/flows/routers/waits" "github.com/nyaruka/goflow/flows/routers/waits/hints" "github.com/nyaruka/goflow/utils" + "github.com/nyaruka/mailroom/services/ivr/twiml" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/goflow/flows" @@ -20,9 +24,6 @@ import ( func TestResponseForSprint(t *testing.T) { _, rt, _, _ := testsuite.Get() - // for tests it is more convenient to not have formatted output - indentMarshal = false - urn := urns.URN("tel:+12067799294") channelRef := assets.NewChannelReference(assets.ChannelUUID(uuids.New()), "Twilio Channel") @@ -88,8 +89,31 @@ func TestResponseForSprint(t *testing.T) { } for i, tc := range tcs { - response, err := responseForSprint(rt.Config, urn, resumeURL, tc.Wait, tc.Events) + response, err := twiml.ResponseForSprint(rt.Config, urn, resumeURL, tc.Wait, tc.Events, false) assert.NoError(t, err, "%d: unexpected error") assert.Equal(t, xml.Header+tc.Expected, response, "%d: unexpected response", i) } } + +func TestURNForRequest(t *testing.T) { + s := twiml.NewService(http.DefaultClient, "12345", "sesame") + + makeRequest := func(body string) *http.Request { + r, _ := http.NewRequest("POST", "http://nyaruka.com/12345/incoming", strings.NewReader(body)) + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + r.Header.Add("Content-Length", strconv.Itoa(len(body))) + return r + } + + urn, err := s.URNForRequest(makeRequest(`CallSid=12345&AccountSid=23456&Caller=%2B12064871234&To=%2B12029795079&Called=%2B12029795079&CallStatus=queued&ApiVersion=2010-04-01&Direction=inbound`)) + assert.NoError(t, err) + assert.Equal(t, urns.URN(`tel:+12064871234`), urn) + + // SignalWire uses From instead of Caller + urn, err = s.URNForRequest(makeRequest(`CallSid=12345&AccountSid=23456&From=%2B12064871234&To=%2B12029795079&Called=%2B12029795079&CallStatus=queued&ApiVersion=2010-04-01&Direction=inbound`)) + assert.NoError(t, err) + assert.Equal(t, urns.URN(`tel:+12064871234`), urn) + + _, err = s.URNForRequest(makeRequest(`CallSid=12345&AccountSid=23456&To=%2B12029795079&Called=%2B12029795079&CallStatus=queued&ApiVersion=2010-04-01&Direction=inbound`)) + assert.EqualError(t, err, "no Caller or From parameter found in request") +} diff --git a/web/ivr/ivr.go b/web/ivr/ivr.go index 541050378..7120f8a6a 100644 --- a/web/ivr/ivr.go +++ b/web/ivr/ivr.go @@ -254,7 +254,7 @@ func handleFlow(ctx context.Context, rt *runtime.Runtime, r *http.Request, w htt } // load our contact - contacts, err := models.LoadContacts(ctx, rt.DB, oa, []models.ContactID{conn.ContactID()}) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, []models.ContactID{conn.ContactID()}) if err != nil { return channel, conn, provider.WriteErrorResponse(w, errors.Wrapf(err, "no such contact")) }