Skip to content

Commit 02be425

Browse files
committed
Add events for delivered and failed messages
1 parent 5fadf6f commit 02be425

File tree

10 files changed

+356
-11
lines changed

10 files changed

+356
-11
lines changed

api/pkg/entities/message.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const (
3535

3636
// MessageStatusFailed means the mobile phone could not send the message
3737
MessageStatusFailed = "failed"
38+
39+
// MessageStatusDelivered means the mobile phone has delivered the message
40+
MessageStatusDelivered = "delivered"
3841
)
3942

4043
// MessageEventName is the type of event generated by the mobile phone for a message
@@ -43,6 +46,12 @@ type MessageEventName string
4346
const (
4447
// MessageEventNameSent is emitted when a message is sent by the mobile phone
4548
MessageEventNameSent = "SENT"
49+
50+
// MessageEventNameDelivered is emitted when a message is delivered by the mobile phone
51+
MessageEventNameDelivered = "DELIVERED"
52+
53+
// MessageEventNameFailed is emitted when a message is failed by the mobile phone
54+
MessageEventNameFailed = "FAILED"
4655
)
4756

4857
// Message represents a message sent between 2 phone numbers
@@ -72,6 +81,11 @@ func (message Message) IsSending() bool {
7281
return message.Status == MessageStatusSending
7382
}
7483

84+
// IsSent determines if a message has been sent
85+
func (message Message) IsSent() bool {
86+
return message.Status == MessageStatusSent
87+
}
88+
7589
// Sent registers a message as sent
7690
func (message *Message) Sent(timestamp time.Time) *Message {
7791
sendDuration := timestamp.UnixNano() - message.RequestReceivedAt.UnixNano()
@@ -82,6 +96,22 @@ func (message *Message) Sent(timestamp time.Time) *Message {
8296
return message
8397
}
8498

99+
// Failed registers a message as failed
100+
func (message *Message) Failed(timestamp time.Time) *Message {
101+
message.SentAt = &timestamp
102+
message.Status = MessageStatusFailed
103+
message.OrderTimestamp = timestamp
104+
return message
105+
}
106+
107+
// Delivered registers a message as delivered
108+
func (message *Message) Delivered(timestamp time.Time) *Message {
109+
message.SentAt = &timestamp
110+
message.Status = MessageStatusDelivered
111+
message.OrderTimestamp = timestamp
112+
return message
113+
}
114+
85115
// AddSendAttempt configures a Message for sending
86116
func (message *Message) AddSendAttempt(timestamp time.Time) *Message {
87117
message.Status = MessageStatusSending

api/pkg/entities/message_thread.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type MessageThread struct {
1111
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703ca"`
1212
Owner string `json:"owner" example:"+18005550199"`
1313
Contact string `json:"contact" example:"+18005550100"`
14+
Color string `json:"color" example:"indigo"`
1415
LastMessageContent string `json:"last_message_content" example:"This is a sample message content"`
1516
LastMessageID uuid.UUID `json:"last_message_id" example:"32343a19-da5e-4b1b-a767-3298a73703ca"`
1617
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:09.527976+03:00"`
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package events
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
// EventTypeMessagePhoneDelivered is emitted when the phone delivers a message
10+
const EventTypeMessagePhoneDelivered = "message.phone.delivered"
11+
12+
// MessagePhoneDeliveredPayload is the payload of the EventTypeMessagePhoneDelivered event
13+
type MessagePhoneDeliveredPayload struct {
14+
ID uuid.UUID `json:"id"`
15+
Owner string `json:"owner"`
16+
Contact string `json:"contact"`
17+
Timestamp time.Time `json:"timestamp"`
18+
Content string `json:"content"`
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package events
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
// EventTypeMessagePhoneFailed is emitted when the phone could not send
10+
const EventTypeMessagePhoneFailed = "message.phone.failed"
11+
12+
// MessagePhoneFailedPayload is the payload of the EventTypeMessagePhoneFailed event
13+
type MessagePhoneFailedPayload struct {
14+
ID uuid.UUID `json:"id"`
15+
Owner string `json:"owner"`
16+
Contact string `json:"contact"`
17+
Timestamp time.Time `json:"timestamp"`
18+
Content string `json:"content"`
19+
}

api/pkg/listeners/message_listener.go

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ func NewMessageListener(
3737
}
3838

3939
return l, map[string]events.EventListener{
40-
events.EventTypeMessageAPISent: l.OnMessageAPISent,
41-
events.EventTypeMessagePhoneSending: l.OnMessagePhoneSending,
42-
events.EventTypeMessagePhoneSent: l.OnMessagePhoneSent,
43-
events.EventTypeMessagePhoneReceived: l.OnMessagePhoneReceived,
40+
events.EventTypeMessageAPISent: l.OnMessageAPISent,
41+
events.EventTypeMessagePhoneSending: l.OnMessagePhoneSending,
42+
events.EventTypeMessagePhoneSent: l.OnMessagePhoneSent,
43+
events.EventTypeMessagePhoneDelivered: l.OnMessagePhoneDelivered,
44+
events.EventTypeMessagePhoneFailed: l.OnMessagePhoneFailed,
45+
events.EventTypeMessagePhoneReceived: l.OnMessagePhoneReceived,
4446
}
4547
}
4648

@@ -158,6 +160,80 @@ func (listener *MessageListener) OnMessagePhoneSent(ctx context.Context, event c
158160
return listener.storeEventListenerLog(ctx, listener.signature(event), event)
159161
}
160162

163+
// OnMessagePhoneDelivered handles the events.EventTypeMessagePhoneDelivered event
164+
func (listener *MessageListener) OnMessagePhoneDelivered(ctx context.Context, event cloudevents.Event) error {
165+
ctx, span := listener.tracer.Start(ctx)
166+
defer span.End()
167+
168+
handled, err := listener.repository.Has(ctx, event.ID(), listener.signature(event))
169+
if err != nil {
170+
msg := fmt.Sprintf("cannot verify if event [%s] has been handled by [%T]", event.ID(), listener.signature(event))
171+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
172+
}
173+
174+
ctxLogger := listener.tracer.CtxLogger(listener.logger, span)
175+
176+
if handled {
177+
ctxLogger.Info(fmt.Sprintf("event [%s] has already been handled by [%s]", event.ID(), listener.signature(event)))
178+
return nil
179+
}
180+
181+
var payload events.MessagePhoneDeliveredPayload
182+
if err = event.DataAs(&payload); err != nil {
183+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
184+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
185+
}
186+
187+
handleParams := services.HandleMessageParams{
188+
ID: payload.ID,
189+
Timestamp: payload.Timestamp,
190+
}
191+
192+
if err = listener.service.HandleMessageDelivered(ctx, handleParams); err != nil {
193+
msg := fmt.Sprintf("cannot handle [%s] for message with ID [%s] for event with ID [%s]", event.Type(), handleParams.ID, event.ID())
194+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
195+
}
196+
197+
return listener.storeEventListenerLog(ctx, listener.signature(event), event)
198+
}
199+
200+
// OnMessagePhoneFailed handles the events.EventTypeMessagePhoneFailed event
201+
func (listener *MessageListener) OnMessagePhoneFailed(ctx context.Context, event cloudevents.Event) error {
202+
ctx, span := listener.tracer.Start(ctx)
203+
defer span.End()
204+
205+
handled, err := listener.repository.Has(ctx, event.ID(), listener.signature(event))
206+
if err != nil {
207+
msg := fmt.Sprintf("cannot verify if event [%s] has been handled by [%T]", event.ID(), listener.signature(event))
208+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
209+
}
210+
211+
ctxLogger := listener.tracer.CtxLogger(listener.logger, span)
212+
213+
if handled {
214+
ctxLogger.Info(fmt.Sprintf("event [%s] has already been handled by [%s]", event.ID(), listener.signature(event)))
215+
return nil
216+
}
217+
218+
var payload events.MessagePhoneFailedPayload
219+
if err = event.DataAs(&payload); err != nil {
220+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
221+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
222+
}
223+
224+
handleParams := services.HandleMessageParams{
225+
ID: payload.ID,
226+
Timestamp: payload.Timestamp,
227+
}
228+
229+
if err = listener.service.HandleMessageFailed(ctx, handleParams); err != nil {
230+
msg := fmt.Sprintf("cannot handle [%s] for message with ID [%s] for event with ID [%s]", event.Type(), handleParams.ID, event.ID())
231+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
232+
}
233+
234+
return listener.storeEventListenerLog(ctx, listener.signature(event), event)
235+
}
236+
161237
// OnMessagePhoneReceived handles the events.EventTypeMessageAPISent event
162238
func (listener *MessageListener) OnMessagePhoneReceived(ctx context.Context, event cloudevents.Event) error {
163239
ctx, span := listener.tracer.Start(ctx)

api/pkg/listeners/message_thread_listener.go

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ func NewMessageThreadListener(
3737
}
3838

3939
return l, map[string]events.EventListener{
40-
events.EventTypeMessageAPISent: l.OnMessageAPISent,
41-
events.EventTypeMessagePhoneSending: l.OnMessagePhoneSending,
42-
events.EventTypeMessagePhoneSent: l.OnMessagePhoneSent,
43-
events.EventTypeMessagePhoneReceived: l.OnMessagePhoneReceived,
40+
events.EventTypeMessageAPISent: l.OnMessageAPISent,
41+
events.EventTypeMessagePhoneSending: l.OnMessagePhoneSending,
42+
events.EventTypeMessagePhoneSent: l.OnMessagePhoneSent,
43+
events.EventTypeMessagePhoneDelivered: l.OnMessagePhoneDelivered,
44+
events.EventTypeMessagePhoneFailed: l.OnMessagePhoneFailed,
45+
events.EventTypeMessagePhoneReceived: l.OnMessagePhoneReceived,
4446
}
4547
}
4648

@@ -125,6 +127,60 @@ func (listener *MessageThreadListener) OnMessagePhoneSent(ctx context.Context, e
125127
return nil
126128
}
127129

130+
// OnMessagePhoneDelivered handles the events.EventTypeMessagePhoneDelivered event
131+
func (listener *MessageThreadListener) OnMessagePhoneDelivered(ctx context.Context, event cloudevents.Event) error {
132+
ctx, span := listener.tracer.Start(ctx)
133+
defer span.End()
134+
135+
var payload events.MessagePhoneDeliveredPayload
136+
if err := event.DataAs(&payload); err != nil {
137+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
138+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
139+
}
140+
141+
updateParams := services.MessageThreadUpdateParams{
142+
Owner: payload.Owner,
143+
Contact: payload.Contact,
144+
Timestamp: payload.Timestamp,
145+
Content: payload.Content,
146+
MessageID: payload.ID,
147+
}
148+
149+
if err := listener.service.UpdateThread(ctx, updateParams); err != nil {
150+
msg := fmt.Sprintf("cannot update thread for message with ID [%s] for event with ID [%s]", updateParams.MessageID, event.ID())
151+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
152+
}
153+
154+
return nil
155+
}
156+
157+
// OnMessagePhoneFailed handles the events.EventTypeMessagePhoneFailed event
158+
func (listener *MessageThreadListener) OnMessagePhoneFailed(ctx context.Context, event cloudevents.Event) error {
159+
ctx, span := listener.tracer.Start(ctx)
160+
defer span.End()
161+
162+
var payload events.MessagePhoneDeliveredPayload
163+
if err := event.DataAs(&payload); err != nil {
164+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
165+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
166+
}
167+
168+
updateParams := services.MessageThreadUpdateParams{
169+
Owner: payload.Owner,
170+
Contact: payload.Contact,
171+
Timestamp: payload.Timestamp,
172+
Content: payload.Content,
173+
MessageID: payload.ID,
174+
}
175+
176+
if err := listener.service.UpdateThread(ctx, updateParams); err != nil {
177+
msg := fmt.Sprintf("cannot update thread for message with ID [%s] for event with ID [%s]", updateParams.MessageID, event.ID())
178+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
179+
}
180+
181+
return nil
182+
}
183+
128184
// OnMessagePhoneReceived handles the events.EventTypeMessagePhoneReceived event
129185
func (listener *MessageThreadListener) OnMessagePhoneReceived(ctx context.Context, event cloudevents.Event) error {
130186
ctx, span := listener.tracer.Start(ctx)

api/pkg/requests/message_event_request.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type MessageEvent struct {
1515
Timestamp time.Time `json:"timestamp" example:"2022-06-05T14:26:09.527976+03:00"`
1616

1717
// EventName is the type of event
18-
// * SENT: is emitted when a message is sent by the mobile phone (only SENT is implemented)
18+
// * SENT: is emitted when a message is sent by the mobile phone
1919
// * FAILED: is event is emitted when the message could not be sent by the mobile phone
2020
// * DELIVERED: is event is emitted when a delivery report has been received by the mobile phone
2121
EventName string `json:"event_name" example:"SENT"`

0 commit comments

Comments
 (0)