-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,192 @@ | ||
package highconnection | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/nyaruka/courier" | ||
"github.com/nyaruka/courier/handlers" | ||
"github.com/nyaruka/courier/utils" | ||
"github.com/nyaruka/gocommon/urns" | ||
) | ||
|
||
/* | ||
GET /handlers/hcnx/status/uuid?push_id=1164711372&status=6&to=%2B33611441111&ret_id=19128317&text=Msg | ||
POST /handlers/hcnx/receive/uuid?FROM=+33644961111 | ||
ID=1164708294&FROM=%2B33644961111&TO=36105&MESSAGE=Msg&VALIDITY_DATE=2017-05-03T21%3A13%3A13&GET_STATUS=0&CLIENT=LEANCONTACTFAST&CLASS_TYPE=0&RECEPTION_DATE=2017-05-02T21%3A13%3A13&TO_OP_ID=20810&INITIAL_OP_ID=20810&STATUS=POSTING_30179_1410&EMAIL=&BINARY=0&PARAM=%7C%7C%7C%7CP223%2F03%2F03&USER_DATA=LEANCONTACTFAST&USER_DATA_2=jours+pas+r%E9gl%E9&BULK_ID=0&MO_ID=0&APPLICATION_ID=0&ACCOUNT_ID=39&GW_MESSAGE_ID=0&READ_STATUS=0&TARIFF=0&REQUEST_ID=33609002123&TAC=%28null%29&REASON=2017-05-02+23%3A13%3A13&FORMAT=&MVNO=&ORIG_ID=1164708215&ORIG_MESSAGE=Msg&RET_ID=123456&ORIG_DATE=2017-05-02T21%3A11%3A44 | ||
*/ | ||
|
||
var sendURL = "https://highpushfastapi-v2.hcnx.eu/api" | ||
var maxMsgLength = 1500 | ||
|
||
func init() { | ||
courier.RegisterHandler(newHandler()) | ||
} | ||
|
||
type handler struct { | ||
handlers.BaseHandler | ||
} | ||
|
||
func newHandler() courier.ChannelHandler { | ||
return &handler{handlers.NewBaseHandler(courier.ChannelType("HX"), "High Connection")} | ||
} | ||
|
||
// Initialize is called by the engine once everything is loaded | ||
func (h *handler) Initialize(s courier.Server) error { | ||
h.SetServer(s) | ||
err := s.AddHandlerRoute(h, http.MethodGet, "receive", h.ReceiveMessage) | ||
if err != nil { | ||
return err | ||
} | ||
err = s.AddHandlerRoute(h, http.MethodPost, "receive", h.ReceiveMessage) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = s.AddHandlerRoute(h, http.MethodPost, "status", h.StatusMessage) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return s.AddHandlerRoute(h, http.MethodGet, "status", h.StatusMessage) | ||
|
||
} | ||
|
||
type moMsg struct { | ||
To string `name:"TO" validate:"required"` | ||
From string `name:"FROM" validate:"required"` | ||
Message string `name:"MESSAGE" validate:"required"` | ||
ReceiveDate string `name:"RECEPTION_DATE"` | ||
} | ||
|
||
// ReceiveMessage is our HTTP handler function for incoming messages | ||
func (h *handler) ReceiveMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { | ||
hxRequest := &moMsg{} | ||
err := handlers.DecodeAndValidateForm(hxRequest, r) | ||
if err != nil { | ||
return nil, courier.WriteAndLogRequestError(ctx, w, r, channel, err) | ||
} | ||
|
||
date := time.Now() | ||
if hxRequest.ReceiveDate != "" { | ||
date, err = time.Parse("2006-01-02T15:04:05", hxRequest.ReceiveDate) | ||
if err != nil { | ||
return nil, courier.WriteAndLogRequestError(ctx, w, r, channel, err) | ||
} | ||
} | ||
|
||
// create our URN | ||
urn := urns.NewTelURNForCountry(hxRequest.From, channel.Country()) | ||
|
||
// build our infobipMessage | ||
msg := h.Backend().NewIncomingMsg(channel, urn, hxRequest.Message).WithReceivedOn(date.UTC()) | ||
|
||
// and write it | ||
err = h.Backend().WriteMsg(ctx, msg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return []courier.Event{msg}, courier.WriteMsgSuccess(ctx, w, r, []courier.Msg{msg}) | ||
|
||
} | ||
|
||
type moStatus struct { | ||
RetID int64 `name:"ret_id" validate:"required"` | ||
Status int `name:"status" validate:"required"` | ||
} | ||
|
||
var statusMapping = map[int]courier.MsgStatusValue{ | ||
2: courier.MsgFailed, | ||
4: courier.MsgSent, | ||
6: courier.MsgDelivered, | ||
11: courier.MsgFailed, | ||
12: courier.MsgFailed, | ||
13: courier.MsgFailed, | ||
14: courier.MsgFailed, | ||
15: courier.MsgFailed, | ||
16: courier.MsgFailed, | ||
} | ||
|
||
// StatusMessage is our HTTP handler function for status updates | ||
func (h *handler) StatusMessage(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request) ([]courier.Event, error) { | ||
hxRequest := &moStatus{} | ||
err := handlers.DecodeAndValidateForm(hxRequest, r) | ||
if err != nil { | ||
return nil, courier.WriteAndLogRequestError(ctx, w, r, channel, err) | ||
} | ||
|
||
msgStatus, found := statusMapping[hxRequest.Status] | ||
if !found { | ||
return nil, fmt.Errorf("unknown status '%d', must be one of 2, 4, 6, 11, 12, 13, 14, 15 or 16", hxRequest.Status) | ||
} | ||
|
||
// write our status | ||
status := h.Backend().NewMsgStatusForID(channel, courier.NewMsgID(hxRequest.RetID), msgStatus) | ||
err = h.Backend().WriteMsgStatus(ctx, status) | ||
if err == courier.ErrMsgNotFound { | ||
return nil, courier.WriteAndLogStatusMsgNotFound(ctx, w, r, channel) | ||
} | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return []courier.Event{status}, courier.WriteStatusSuccess(ctx, w, r, []courier.MsgStatus{status}) | ||
|
||
} | ||
|
||
// SendMsg sends the passed in message, returning any error | ||
func (h *handler) SendMsg(ctx context.Context, msg courier.Msg) (courier.MsgStatus, error) { | ||
username := msg.Channel().StringConfigForKey(courier.ConfigUsername, "") | ||
if username == "" { | ||
return nil, fmt.Errorf("no username set for HX channel") | ||
} | ||
|
||
password := msg.Channel().StringConfigForKey(courier.ConfigPassword, "") | ||
if password == "" { | ||
return nil, fmt.Errorf("no password set for HX channel") | ||
} | ||
|
||
callbackDomain := msg.Channel().CallbackDomain(h.Server().Config().Domain) | ||
statusURL := fmt.Sprintf("https://%s%s%s/status", callbackDomain, "/c/hx/", msg.Channel().UUID()) | ||
receiveURL := fmt.Sprintf("https://%s%s%s/receive", callbackDomain, "/c/hx/", msg.Channel().UUID()) | ||
|
||
status := h.Backend().NewMsgStatusForID(msg.Channel(), msg.ID(), courier.MsgErrored) | ||
parts := handlers.SplitMsg(handlers.GetTextAndAttachments(msg), maxMsgLength) | ||
for _, part := range parts { | ||
|
||
form := url.Values{ | ||
"accountid": []string{username}, | ||
"password": []string{password}, | ||
"text": []string{part}, | ||
"to": []string{msg.URN().Path()}, | ||
"ret_id": []string{msg.ID().String()}, | ||
"datacoding": []string{"8"}, | ||
"userdata": []string{"textit"}, | ||
"ret_url": []string{statusURL}, | ||
"ret_mo_url": []string{receiveURL}, | ||
} | ||
|
||
msgURL, _ := url.Parse(sendURL) | ||
msgURL.RawQuery = form.Encode() | ||
|
||
req, err := http.NewRequest(http.MethodPost, msgURL.String(), nil) | ||
rr, err := utils.MakeHTTPRequest(req) | ||
|
||
// record our status and log | ||
log := courier.NewChannelLogFromRR("Message Sent", msg.Channel(), msg.ID(), rr).WithError("Message Send Error", err) | ||
status.AddLog(log) | ||
if err != nil { | ||
return status, nil | ||
} | ||
|
||
status.SetStatus(courier.MsgWired) | ||
|
||
} | ||
|
||
return status, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package highconnection | ||
|
||
import ( | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/nyaruka/courier" | ||
. "github.com/nyaruka/courier/handlers" | ||
) | ||
|
||
var testChannels = []courier.Channel{ | ||
courier.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HX", "2020", "US", nil), | ||
} | ||
|
||
var ( | ||
receiveURL = "/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive/" | ||
statusURL = "/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status/" | ||
|
||
validReceive = receiveURL + "?FROM=+33610346460&TO=5151&MESSAGE=Hello+World&RECEPTION_DATE=2015-04-02T14:26:06" | ||
invalidDateReceive = receiveURL + "?FROM=+33610346460&TO=5151&MESSAGE=Hello+World&RECEPTION_DATE=2015-04-02T14:26" | ||
validStatus = statusURL + "?ret_id=12345&status=6" | ||
) | ||
|
||
var testCases = []ChannelHandleTestCase{ | ||
{Label: "Receive Valid Message", URL: validReceive, Status: 200, Response: "Accepted", | ||
Text: Sp("Hello World"), URN: Sp("tel:+33610346460"), | ||
Date: Tp(time.Date(2015, 04, 02, 14, 26, 06, 0, time.UTC))}, | ||
{Label: "Receive Missing Params", URL: receiveURL, Status: 400, Response: "validation for 'Message' failed"}, | ||
{Label: "Receive Invalid Date", URL: invalidDateReceive, Status: 400, Response: "cannot parse"}, | ||
{Label: "Status Missing Params", URL: statusURL, Status: 400, Response: "validation for 'Status' failed"}, | ||
{Label: "Status Delivered", URL: validStatus, Status: 200, Response: `"status":"D"`}, | ||
} | ||
|
||
func TestHandler(t *testing.T) { | ||
RunChannelTestCases(t, testChannels, newHandler(), testCases) | ||
} | ||
|
||
func BenchmarkHandler(b *testing.B) { | ||
RunChannelBenchmarks(b, testChannels, newHandler(), testCases) | ||
} | ||
|
||
// setSend takes care of setting the sendURL to call | ||
func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { | ||
sendURL = s.URL | ||
} | ||
|
||
var defaultSendTestCases = []ChannelSendTestCase{ | ||
{Label: "Plain Send", | ||
Text: "Simple Message", | ||
URN: "tel:+250788383383", | ||
Status: "W", | ||
URLParams: map[string]string{ | ||
"accountid": "Username", | ||
"password": "Password", | ||
"text": "Simple Message", | ||
"to": "+250788383383", | ||
"ret_id": "10", | ||
"datacoding": "8", | ||
"userdata": "textit", | ||
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status", | ||
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive", | ||
}, | ||
ResponseStatus: 200, | ||
SendPrep: setSendURL}, | ||
{Label: "Unicode Send", | ||
Text: "☺", | ||
URN: "tel:+250788383383", | ||
Status: "W", | ||
URLParams: map[string]string{ | ||
"accountid": "Username", | ||
"password": "Password", | ||
"text": "☺", | ||
"to": "+250788383383", | ||
"ret_id": "10", | ||
"datacoding": "8", | ||
"userdata": "textit", | ||
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status", | ||
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive", | ||
}, | ||
ResponseStatus: 200, | ||
SendPrep: setSendURL}, | ||
{Label: "Long Send", | ||
Text: "This is a longer message than 160 characters and will cause us to split it into two separate parts, isn't that right but it is even longer than before I say, I need to keep adding more things to make it work", | ||
URN: "tel:+250788383383", | ||
Status: "W", | ||
URLParams: map[string]string{ | ||
"accountid": "Username", | ||
"password": "Password", | ||
"text": "I need to keep adding more things to make it work", | ||
"to": "+250788383383", | ||
"ret_id": "10", | ||
"datacoding": "8", | ||
"userdata": "textit", | ||
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status", | ||
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive", | ||
}, | ||
ResponseStatus: 200, | ||
SendPrep: setSendURL}, | ||
{Label: "Send Attachement", | ||
Text: "My pic!", | ||
Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, | ||
URN: "tel:+250788383383", | ||
Status: "W", | ||
URLParams: map[string]string{ | ||
"accountid": "Username", | ||
"password": "Password", | ||
"text": "My pic!\nhttps://foo.bar/image.jpg", | ||
"to": "+250788383383", | ||
"ret_id": "10", | ||
"datacoding": "8", | ||
"userdata": "textit", | ||
"ret_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/status", | ||
"ret_mo_url": "https://localhost/c/hx/8eb23e93-5ecb-45ba-b726-3b064e0c56ab/receive", | ||
}, | ||
ResponseStatus: 200, | ||
SendPrep: setSendURL}, | ||
|
||
{Label: "Error Sending", | ||
Text: "Error Sending", URN: "tel:+250788383383", | ||
Status: "E", | ||
ResponseStatus: 403, | ||
SendPrep: setSendURL}, | ||
} | ||
|
||
func TestSending(t *testing.T) { | ||
maxMsgLength = 160 | ||
var defaultChannel = courier.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "HX", "2020", "US", | ||
map[string]interface{}{ | ||
courier.ConfigPassword: "Password", | ||
courier.ConfigUsername: "Username", | ||
}) | ||
|
||
RunChannelSendTestCases(t, defaultChannel, newHandler(), defaultSendTestCases, nil) | ||
} |