Skip to content

Commit 969b4cb

Browse files
committed
Add delete request endpoint that creates a channel event for FBA channels
1 parent b7ac2a9 commit 969b4cb

File tree

6 files changed

+92
-2
lines changed

6 files changed

+92
-2
lines changed

Diff for: backend.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ type Backend interface {
6161
// NewChannelEvent creates a new channel event for the given channel and event type
6262
NewChannelEvent(Channel, ChannelEventType, urns.URN, *ChannelLog) ChannelEvent
6363

64-
// WriteChannelEvent writes the passed in channel even returning any error
64+
// WriteChannelEvent writes the passed in channel event returning any error
6565
WriteChannelEvent(context.Context, ChannelEvent, *ChannelLog) error
6666

6767
// WriteChannelLog writes the passed in channel log to our backend

Diff for: backends/rapidpro/channel_event.go

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func (i ChannelEventID) String() string {
3838
// ChannelEvent represents an event on a channel.. that isn't a new message or status update
3939
type ChannelEvent struct {
4040
ID_ ChannelEventID ` db:"id"`
41+
UUID_ courier.ChannelEventUUID `json:"uuid" db:"uuid"`
4142
OrgID_ OrgID `json:"org_id" db:"org_id"`
4243
ChannelUUID_ courier.ChannelUUID `json:"channel_uuid" db:"channel_uuid"`
4344
ChannelID_ courier.ChannelID `json:"channel_id" db:"channel_id"`
@@ -77,6 +78,7 @@ func newChannelEvent(channel courier.Channel, eventType courier.ChannelEventType
7778
}
7879

7980
func (e *ChannelEvent) EventID() int64 { return int64(e.ID_) }
81+
func (e *ChannelEvent) UUID() courier.ChannelEventUUID { return e.UUID_ }
8082
func (e *ChannelEvent) ChannelID() courier.ChannelID { return e.ChannelID_ }
8183
func (e *ChannelEvent) ChannelUUID() courier.ChannelUUID { return e.ChannelUUID_ }
8284
func (e *ChannelEvent) EventType() courier.ChannelEventType { return e.EventType_ }

Diff for: channel_event.go

+6
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import (
44
"time"
55

66
"github.com/nyaruka/gocommon/urns"
7+
"github.com/nyaruka/gocommon/uuids"
78
)
89

910
// ChannelEventType is the type of channel event this is
1011
type ChannelEventType string
1112

13+
// ChannelEvent is our typing of a channelevent's UUID
14+
type ChannelEventUUID uuids.UUID
15+
1216
// Possible values for ChannelEventTypes
1317
const (
1418
EventTypeNewConversation ChannelEventType = "new_conversation"
@@ -17,6 +21,7 @@ const (
1721
EventTypeWelcomeMessage ChannelEventType = "welcome_message"
1822
EventTypeOptIn ChannelEventType = "optin"
1923
EventTypeOptOut ChannelEventType = "optout"
24+
EventDeletionRequest ChannelEventType = "delete_request"
2025
)
2126

2227
//-----------------------------------------------------------------------------
@@ -27,6 +32,7 @@ const (
2732
type ChannelEvent interface {
2833
Event
2934

35+
UUID() ChannelEventUUID
3036
ChannelUUID() ChannelUUID
3137
URN() urns.URN
3238
EventType() ChannelEventType

Diff for: handlers/meta/facebook_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,38 @@ func TestFacebookDescribeURN(t *testing.T) {
322322
AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"})
323323
}
324324

325+
func TestDeleteRequest(t *testing.T) {
326+
RunIncomingTestCases(t, facebookTestChannels, newHandler("FBA", "Facebook"), []IncomingTestCase{
327+
{
328+
Label: "Receive Delete request FBA",
329+
URL: "/c/fba/delete",
330+
Data: `{"algorithm":"HMAC-SHA256","expires":1291840400,"issued_at":1291836800,"user_id":"218471"}`,
331+
PrepRequest: addValidSignature,
332+
333+
ExpectedRespStatus: 200,
334+
ExpectedBodyContains: "Deletion Request Received",
335+
NoQueueErrorCheck: true,
336+
NoInvalidChannelCheck: true,
337+
NoLogsExpected: true,
338+
ExpectedEvents: []ExpectedEvent{
339+
{Type: courier.EventDeletionRequest, URN: "facebook:218471", Extra: map[string]string{"userID": "218471"}},
340+
},
341+
},
342+
{
343+
Label: "Receive Delete request FBA",
344+
URL: "/c/fba/delete",
345+
Data: `{"algorithm":"HMAC-SHA256","expires":1291840400,"issued_at":1291836800,"user_id":"abc1234"}`,
346+
PrepRequest: addValidSignature,
347+
348+
ExpectedRespStatus: 200,
349+
ExpectedBodyContains: "invalid facebook id",
350+
NoQueueErrorCheck: true,
351+
NoInvalidChannelCheck: true,
352+
NoLogsExpected: true,
353+
},
354+
})
355+
}
356+
325357
func TestFacebookVerify(t *testing.T) {
326358
RunIncomingTestCases(t, facebookTestChannels, newHandler("FBA", "Facebook"), []IncomingTestCase{
327359
{

Diff for: handlers/meta/handlers.go

+49-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"time"
1818

1919
"github.com/buger/jsonparser"
20+
"github.com/getsentry/sentry-go"
2021
"github.com/nyaruka/courier"
2122
"github.com/nyaruka/courier/handlers"
2223
"github.com/nyaruka/courier/handlers/meta/messenger"
@@ -86,6 +87,7 @@ func (h *handler) Initialize(s courier.Server) error {
8687
h.SetServer(s)
8788
s.AddHandlerRoute(h, http.MethodGet, "receive", courier.ChannelLogTypeWebhookVerify, h.receiveVerify)
8889
s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMultiReceive, handlers.JSONPayload(h, h.receiveEvents))
90+
s.AddHandlerRoute(h, http.MethodPost, "delete", courier.ChannelLogTypeEventReceive, handlers.JSONPayload(h, h.deleteEvents))
8991
return nil
9092
}
9193

@@ -131,7 +133,7 @@ func (h *handler) WriteRequestError(ctx context.Context, w http.ResponseWriter,
131133

132134
// GetChannel returns the channel
133135
func (h *handler) GetChannel(ctx context.Context, r *http.Request) (courier.Channel, error) {
134-
if r.Method == http.MethodGet {
136+
if r.Method == http.MethodGet || r.URL.Path == "/c/fba/delete" {
135137
return nil, nil
136138
}
137139

@@ -215,6 +217,52 @@ func (h *handler) resolveMediaURL(mediaID string, token string, clog *courier.Ch
215217
return mediaURL, err
216218
}
217219

220+
type DeletionRequestData struct {
221+
Algorithm string `json:"algorithm"`
222+
Expires int64 `json:"expires"`
223+
IssuedAt int64 `json:"issued_at"`
224+
UserID string `json:"user_id"`
225+
}
226+
227+
type DeleteConfirmationData struct {
228+
URL string `json:"url"`
229+
ConfirmationCode string `json:"confirmation_code"`
230+
}
231+
232+
// deleteEvents is our HTTP handler function for deleting data requests
233+
func (h *handler) deleteEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *DeletionRequestData, clog *courier.ChannelLog) ([]courier.Event, error) {
234+
err := h.validateSignature(r)
235+
if err != nil {
236+
return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err)
237+
}
238+
239+
urn, err := urns.New(urns.Facebook, payload.UserID)
240+
if err != nil {
241+
return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, errors.New("invalid facebook id"))
242+
}
243+
date := parseTimestamp(payload.IssuedAt)
244+
245+
events := make([]courier.Event, 0, 2)
246+
data := make([]any, 0, 2)
247+
248+
payloadJson, _ := json.Marshal(payload)
249+
sentry.CaptureMessage(fmt.Sprintf("Data Deletion Request: %s", payloadJson))
250+
251+
event := h.Backend().NewChannelEvent(channel, courier.EventDeletionRequest, urn, clog).WithOccurredOn(date).WithExtra(map[string]string{"userID": payload.UserID})
252+
253+
err = h.Backend().WriteChannelEvent(ctx, event, clog)
254+
if err != nil {
255+
return nil, err
256+
}
257+
258+
confirmationURL := fmt.Sprintf("https://%s/channels/events/read/%s/", h.Server().Config().Domain, event.UUID())
259+
260+
events = append(events, event)
261+
data = append(data, DeleteConfirmationData{URL: confirmationURL, ConfirmationCode: string(event.UUID())})
262+
263+
return events, courier.WriteDataResponse(w, http.StatusOK, "Deletion Request Received", data)
264+
}
265+
218266
// receiveEvents is our HTTP handler function for incoming messages and status updates
219267
func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *Notifications, clog *courier.ChannelLog) ([]courier.Event, error) {
220268
err := h.validateSignature(r)

Diff for: test/channel_event.go

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
)
99

1010
type mockChannelEvent struct {
11+
uuid courier.ChannelEventUUID
1112
channel courier.Channel
1213
eventType courier.ChannelEventType
1314
urn urns.URN
@@ -20,6 +21,7 @@ type mockChannelEvent struct {
2021
}
2122

2223
func (e *mockChannelEvent) EventID() int64 { return 0 }
24+
func (e *mockChannelEvent) UUID() courier.ChannelEventUUID { return e.uuid }
2325
func (e *mockChannelEvent) ChannelUUID() courier.ChannelUUID { return e.channel.UUID() }
2426
func (e *mockChannelEvent) EventType() courier.ChannelEventType { return e.eventType }
2527
func (e *mockChannelEvent) CreatedOn() time.Time { return e.createdOn }

0 commit comments

Comments
 (0)