Skip to content

Commit

Permalink
Merge branch 'master' into calc_event_fires
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Aug 21, 2020
2 parents 8537b6e + 0a6917e commit 5101b7d
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ docs/*
docs

.DS_Store
_storage/

# Test binary, build with `go test -c`
*.test
Expand Down
2 changes: 1 addition & 1 deletion services/tickets/mailgun/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func handleReceive(ctx context.Context, s *web.Server, r *http.Request, l *model
return errors.Wrapf(err, "error updating ticket: %s", ticket.UUID()), http.StatusInternalServerError, nil
}

msg, err := tickets.SendReply(ctx, s.DB, s.RP, ticket, request.StrippedText)
msg, err := tickets.SendReply(ctx, s.DB, s.RP, s.Storage, s.Config.S3MediaPrefix, ticket, request.StrippedText, nil)
if err != nil {
return err, http.StatusInternalServerError, nil
}
Expand Down
50 changes: 43 additions & 7 deletions services/tickets/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ package tickets

import (
"context"
"net/http"
"path/filepath"
"time"

"github.com/nyaruka/gocommon/httpx"
"github.com/nyaruka/goflow/assets"
"github.com/nyaruka/goflow/envs"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/utils"
"github.com/nyaruka/goflow/utils/uuids"
"github.com/nyaruka/mailroom/courier"
"github.com/nyaruka/mailroom/models"
"github.com/nyaruka/mailroom/utils/storage"

"github.com/gomodule/redigo/redis"
"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -69,22 +76,37 @@ func FromTicketerUUID(ctx context.Context, db *sqlx.DB, uuid assets.TicketerUUID
}

// SendReply sends a message reply from the ticket system user to the contact
func SendReply(ctx context.Context, db *sqlx.DB, rp *redis.Pool, ticket *models.Ticket, text string) (*models.Msg, error) {
func SendReply(ctx context.Context, db *sqlx.DB, rp *redis.Pool, store storage.Storage, mediaPrefix string, ticket *models.Ticket, text string, fileURLs []string) (*models.Msg, error) {
// look up our assets
assets, err := models.GetOrgAssets(ctx, db, ticket.OrgID())
oa, err := models.GetOrgAssets(ctx, db, ticket.OrgID())
if err != nil {
return nil, errors.Wrapf(err, "error looking up org #%d", ticket.OrgID())
}

// build a simple translation
translations := map[envs.Language]*models.BroadcastTranslation{
envs.Language("base"): {Text: text},
// fetch and files and prepare as attachments
attachments := make([]utils.Attachment, len(fileURLs))
for i, fileURL := range fileURLs {
fileBody, err := fetchFile(fileURL)
if err != nil {
return nil, errors.Wrapf(err, "error fetching file %s for ticket reply", fileURL)
}

filename := string(uuids.New()) + filepath.Ext(fileURL)

attachments[i], err = oa.Org().StoreAttachment(store, mediaPrefix, filename, fileBody)
if err != nil {
return nil, errors.Wrapf(err, "error storing attachment %s for ticket reply", fileURL)
}
}

// build a simple translation
base := &models.BroadcastTranslation{Text: text, Attachments: attachments}
translations := map[envs.Language]*models.BroadcastTranslation{envs.Language("base"): base}

// we'll use a broadcast to send this message
bcast := models.NewBroadcast(assets.OrgID(), models.NilBroadcastID, translations, models.TemplateStateEvaluated, envs.Language("base"), nil, nil, nil)
bcast := models.NewBroadcast(oa.OrgID(), models.NilBroadcastID, translations, models.TemplateStateEvaluated, envs.Language("base"), nil, nil, nil)
batch := bcast.CreateBatch([]models.ContactID{ticket.ContactID()})
msgs, err := models.CreateBroadcastMessages(ctx, db, rp, assets, batch)
msgs, err := models.CreateBroadcastMessages(ctx, db, rp, oa, batch)
if err != nil {
return nil, errors.Wrapf(err, "error creating message batch")
}
Expand All @@ -101,3 +123,17 @@ func SendReply(ctx context.Context, db *sqlx.DB, rp *redis.Pool, ticket *models.
}
return msg, nil
}

func fetchFile(url string) ([]byte, error) {
req, _ := httpx.NewRequest("GET", url, nil, nil)

trace, err := httpx.DoTrace(http.DefaultClient, req, httpx.NewFixedRetries(time.Second*5, time.Second*10), nil, 10*1024*1024)
if err != nil {
return nil, err
}
if trace.Response.StatusCode/100 != 2 {
return nil, errors.New("fetch returned non-200 response")
}

return trace.ResponseBody, nil
}
26 changes: 25 additions & 1 deletion services/tickets/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package tickets_test

import (
"io/ioutil"
"testing"

"github.com/nyaruka/gocommon/httpx"
"github.com/nyaruka/goflow/envs"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/utils"
"github.com/nyaruka/goflow/utils/uuids"
"github.com/nyaruka/mailroom/models"
"github.com/nyaruka/mailroom/services/tickets"
_ "github.com/nyaruka/mailroom/services/tickets/mailgun"
Expand Down Expand Up @@ -118,6 +122,20 @@ func TestSendReply(t *testing.T) {
testsuite.ResetDB()
ctx := testsuite.CTX()
db := testsuite.DB()
rp := testsuite.RP()
defer testsuite.ResetStorage()

defer uuids.SetGenerator(uuids.DefaultGenerator)
uuids.SetGenerator(uuids.NewSeededGenerator(12345))

image, err := ioutil.ReadFile("../../models/testdata/test.jpg")
require.NoError(t, err)

defer httpx.SetRequestor(httpx.DefaultRequestor)
httpx.SetRequestor(httpx.NewMockRequestor(map[string][]httpx.MockResponse{
"http://coolfilesfortickets.com/a.jpg": {httpx.MockResponse{Status: 200, Body: image}},
"http://badfiles.com/b.jpg": {httpx.MockResponse{Status: 400, Body: nil}},
}))

ticketUUID := flows.TicketUUID("f7358870-c3dd-450d-b5ae-db2eb50216ba")

Expand All @@ -128,9 +146,15 @@ func TestSendReply(t *testing.T) {
ticket, err := models.LookupTicketByUUID(ctx, db, ticketUUID)
require.NoError(t, err)

msg, err := tickets.SendReply(ctx, db, testsuite.RP(), ticket, "I'll get back to you")
msg, err := tickets.SendReply(ctx, db, rp, testsuite.Storage(), "media", ticket, "I'll get back to you", []string{"http://coolfilesfortickets.com/a.jpg"})
require.NoError(t, err)

assert.Equal(t, "I'll get back to you", msg.Text())
assert.Equal(t, models.CathyID, msg.ContactID())
assert.Equal(t, []utils.Attachment{"image/jpeg:https:///_test_storage/media/1/1ae9/6956/1ae96956-4b34-433e-8d1a-f05fe6923d6d.jpg"}, msg.Attachments())
assert.FileExists(t, "_test_storage/media/1/1ae9/6956/1ae96956-4b34-433e-8d1a-f05fe6923d6d.jpg")

// try with file that can't be fetched
_, err = tickets.SendReply(ctx, db, rp, testsuite.Storage(), "media", ticket, "I'll get back to you", []string{"http://badfiles.com/b.jpg"})
assert.EqualError(t, err, "error fetching file http://badfiles.com/b.jpg for ticket reply: fetch returned non-200 response")
}
19 changes: 13 additions & 6 deletions services/tickets/zendesk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package zendesk

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -224,13 +223,19 @@ func NewPushClient(httpClient *http.Client, httpRetries *httpx.RetryConfig, subd
return &PushClient{baseClient: newBaseClient(httpClient, httpRetries, subdomain, token)}
}

// FieldValue is a value for the named field
type FieldValue struct {
ID string `json:"id"`
Value string `json:"value"`
}

// Author see https://developer.zendesk.com/rest_api/docs/support/channel_framework#author-object
type Author struct {
ExternalID string `json:"external_id"`
Name string `json:"name,omitempty"`
ImageURL string `json:"image_url,omitempty"`
Locale string `json:"locale,omitempty"`
Fields json.RawMessage `json:"fields,omitempty"`
ExternalID string `json:"external_id"`
Name string `json:"name,omitempty"`
ImageURL string `json:"image_url,omitempty"`
Locale string `json:"locale,omitempty"`
Fields []FieldValue `json:"fields,omitempty"`
}

// DisplayInfo see https://developer.zendesk.com/rest_api/docs/support/channel_framework#display_info-object
Expand All @@ -250,6 +255,8 @@ type ExternalResource struct {
Author Author `json:"author"`
DisplayInfo []DisplayInfo `json:"display_info,omitempty"`
AllowChannelback bool `json:"allow_channelback"`
Fields []FieldValue `json:"fields,omitempty"`
FileURLs []string `json:"file_urls,omitempty"`
}

// Status see https://developer.zendesk.com/rest_api/docs/support/channel_framework#status-object
Expand Down
2 changes: 1 addition & 1 deletion services/tickets/zendesk/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func handleChannelback(ctx context.Context, s *web.Server, r *http.Request) (int
return errors.Wrapf(err, "error updating ticket: %s", ticket.UUID()), http.StatusBadRequest, nil
}

msg, err := tickets.SendReply(ctx, s.DB, s.RP, ticket, request.Message)
msg, err := tickets.SendReply(ctx, s.DB, s.RP, s.Storage, s.Config.S3MediaPrefix, ticket, request.Message, request.FileURLs)
if err != nil {
return err, http.StatusBadRequest, nil
}
Expand Down
6 changes: 6 additions & 0 deletions utils/storage/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ func (s *fsStorage) Name() string {
}

func (s *fsStorage) Test() error {
path, err := s.Put("test.txt", "text/plain", []byte(`test`))
if err != nil {
return err
}

os.Remove(path)
return nil
}

Expand Down
7 changes: 7 additions & 0 deletions utils/storage/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ func TestFS(t *testing.T) {
s := storage.NewFS("_testing")
assert.NoError(t, s.Test())

// break our ability to write to that directory
require.NoError(t, os.Chmod("_testing", 0555))

assert.EqualError(t, s.Test(), "open _testing/test.txt: permission denied")

require.NoError(t, os.Chmod("_testing", 0777))

url, err := s.Put("/foo/bar.txt", "text/plain", []byte(`hello world`))
assert.NoError(t, err)
assert.Equal(t, "_testing/foo/bar.txt", url)
Expand Down

0 comments on commit 5101b7d

Please sign in to comment.