Skip to content

Commit 2701ee3

Browse files
committed
Add expired message status
1 parent d192d8f commit 2701ee3

7 files changed

Lines changed: 165 additions & 23 deletions

api/pkg/entities/message.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ const (
3838

3939
// MessageStatusDelivered means the mobile phone has delivered the message
4040
MessageStatusDelivered = "delivered"
41+
42+
// MessageStatusExpired means the message could not be sent by the mobile phone after 5 minutes
43+
MessageStatusExpired = "expired"
4144
)
4245

4346
// MessageEventName is the type of event generated by the mobile phone for a message
@@ -78,12 +81,17 @@ type Message struct {
7881
}
7982

8083
// IsSending determines if a message is being sent
81-
func (message Message) IsSending() bool {
84+
func (message *Message) IsSending() bool {
8285
return message.Status == MessageStatusSending
8386
}
8487

88+
// IsExpired checks if a message is expired
89+
func (message *Message) IsExpired() bool {
90+
return message.Status == MessageStatusExpired
91+
}
92+
8593
// IsSent determines if a message has been sent
86-
func (message Message) IsSent() bool {
94+
func (message *Message) IsSent() bool {
8795
return message.Status == MessageStatusSent
8896
}
8997

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package events
2+
3+
import (
4+
"time"
5+
6+
"github.com/NdoleStudio/httpsms/pkg/entities"
7+
8+
"github.com/google/uuid"
9+
)
10+
11+
// EventTypeMessageNotificationFailed is emitted when a new message notification is failed
12+
const EventTypeMessageNotificationFailed = "message.notification.failed"
13+
14+
// MessageNotificationFailedPayload is the payload of the EventTypeMessageNotificationFailed event
15+
type MessageNotificationFailedPayload struct {
16+
MessageID uuid.UUID `json:"message_id"`
17+
UserID entities.UserID `json:"user_id"`
18+
NotificationID uuid.UUID `json:"notification_id"`
19+
PhoneID uuid.UUID `json:"phone_id"`
20+
ErrorMessage string `json:"error_message"`
21+
NotificationFailedAt time.Time `json:"notification_failed_at"`
22+
}

api/pkg/events/message_notification_scheduled_event.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
// EventTypeMessageNotificationScheduled is emitted when a new message notification is scheduled
1212
const EventTypeMessageNotificationScheduled = "message.notification.scheduled"
1313

14-
// MessageNotificationScheduledPayload is the payload of the MessageNotificationScheduledPayload event
14+
// MessageNotificationScheduledPayload is the payload of the EventTypeMessageNotificationScheduled event
1515
type MessageNotificationScheduledPayload struct {
1616
MessageID uuid.UUID `json:"id"`
1717
UserID entities.UserID `json:"user_id"`
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package events
2+
3+
import (
4+
"time"
5+
6+
"github.com/NdoleStudio/httpsms/pkg/entities"
7+
8+
"github.com/google/uuid"
9+
)
10+
11+
// EventTypeMessageNotificationSent is emitted when a new message notification is scheduled
12+
const EventTypeMessageNotificationSent = "message.notification.sent"
13+
14+
// MessageNotificationSentPayload is the payload of the EventTypeMessageNotificationSent event
15+
type MessageNotificationSentPayload struct {
16+
MessageID uuid.UUID `json:"message_id"`
17+
UserID entities.UserID `json:"user_id"`
18+
PhoneID uuid.UUID `json:"phone_id"`
19+
ScheduledAt time.Time `json:"scheduled_at"`
20+
FcmMessageID string `json:"fcm_message_id"`
21+
NotificationSentAt time.Time `json:"notification_sent_at"`
22+
NotificationID uuid.UUID `json:"notification_id"`
23+
}

api/pkg/listeners/notification_listener.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func (listener *NotificationListener) onMessageNotificationScheduled(ctx context
7878
scheduleParams := &services.NotificationSendParams{
7979
UserID: payload.UserID,
8080
PhoneID: payload.PhoneID,
81+
Source: event.Source(),
82+
ScheduledAt: payload.ScheduledAt,
8183
PhoneNotificationID: payload.NotificationID,
8284
MessageID: payload.MessageID,
8385
}

api/pkg/services/message_service.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ func (service *MessageService) HandleMessageSent(ctx context.Context, params Han
462462
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
463463
}
464464

465-
if !message.IsSending() {
466-
msg := fmt.Sprintf("message has wrong status [%s]. expected %s", message.Status, entities.MessageStatusSending)
465+
if !message.IsSending() && !message.IsExpired() {
466+
msg := fmt.Sprintf("message has wrong status [%s]. expected [%s, %s]", message.Status, entities.MessageStatusSending, entities.MessageStatusExpired)
467467
return service.tracer.WrapErrorSpan(span, stacktrace.NewError(msg))
468468
}
469469

@@ -489,8 +489,8 @@ func (service *MessageService) HandleMessageFailed(ctx context.Context, params H
489489
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
490490
}
491491

492-
if !message.IsSent() && !message.IsSending() {
493-
msg := fmt.Sprintf("message has wrong status [%s]. expected [%s,%s]", message.Status, entities.MessageStatusSending, entities.MessageStatusSent)
492+
if !message.IsSent() && !message.IsSending() && !message.IsExpired() {
493+
msg := fmt.Sprintf("message has wrong status [%s]. expected [%s, %s, %s]", message.Status, entities.MessageStatusSending, entities.MessageStatusSent, entities.MessageStatusExpired)
494494
return service.tracer.WrapErrorSpan(span, stacktrace.NewError(msg))
495495
}
496496

@@ -516,8 +516,8 @@ func (service *MessageService) HandleMessageDelivered(ctx context.Context, param
516516
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
517517
}
518518

519-
if !message.IsSent() && !message.IsSending() {
520-
msg := fmt.Sprintf("message has wrong status [%s]. expected [%s, %s]", message.Status, entities.MessageStatusSent, entities.MessageStatusSending)
519+
if !message.IsSent() && !message.IsSending() && !message.IsExpired() {
520+
msg := fmt.Sprintf("message has wrong status [%s]. expected [%s, %s, %s]", message.Status, entities.MessageStatusSent, entities.MessageStatusSending, entities.MessageStatusExpired)
521521
return service.tracer.WrapErrorSpan(span, stacktrace.NewError(msg))
522522
}
523523

api/pkg/services/notification_service.go

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package services
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"time"
78

@@ -50,6 +51,8 @@ type NotificationSendParams struct {
5051
UserID entities.UserID
5152
PhoneID uuid.UUID
5253
PhoneNotificationID uuid.UUID
54+
Source string
55+
ScheduledAt time.Time
5356
MessageID uuid.UUID
5457
}
5558

@@ -58,19 +61,15 @@ func (service *NotificationService) Send(ctx context.Context, params *Notificati
5861
ctx, span := service.tracer.Start(ctx)
5962
defer span.End()
6063

61-
ctxLogger := service.tracer.CtxLogger(service.logger, span)
62-
6364
phone, err := service.phoneRepository.LoadByID(ctx, params.PhoneID)
6465
if err != nil {
65-
service.updateStatus(ctx, params.PhoneNotificationID, entities.PhoneNotificationStatusFailed)
6666
msg := fmt.Sprintf("cannot load phone with userID [%s] and phoneID [%s]", params.UserID, params.PhoneID)
67-
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
67+
return service.handleNotificationFailed(ctx, errors.New(msg), params)
6868
}
6969

7070
if phone.FcmToken == nil {
71-
service.updateStatus(ctx, params.PhoneNotificationID, entities.PhoneNotificationStatusFailed)
7271
msg := fmt.Sprintf("phone with id [%s] has no FCM token", phone.ID)
73-
return service.tracer.WrapErrorSpan(span, stacktrace.NewError(msg))
72+
return service.handleNotificationFailed(ctx, errors.New(msg), params)
7473
}
7574

7675
result, err := service.messagingClient.Send(ctx, &messaging.Message{
@@ -80,14 +79,9 @@ func (service *NotificationService) Send(ctx context.Context, params *Notificati
8079
Token: *phone.FcmToken,
8180
})
8281
if err != nil {
83-
service.updateStatus(ctx, params.PhoneNotificationID, entities.PhoneNotificationStatusFailed)
84-
msg := fmt.Sprintf("cannot send notification for message [%s] to phone [%s]", params.MessageID, phone.ID)
85-
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
82+
return service.handleNotificationFailed(ctx, err, params)
8683
}
87-
88-
ctxLogger.Info(fmt.Sprintf("sent notification [%s] for message [%s] to phone [%s]", result, params.MessageID, phone.ID))
89-
service.updateStatus(ctx, params.PhoneNotificationID, entities.PhoneNotificationStatusSent)
90-
return nil
84+
return service.handleNotificationSent(ctx, result, params)
9185
}
9286

9387
// NotificationScheduleParams are parameters for sending a notification
@@ -140,6 +134,49 @@ func (service *NotificationService) Schedule(ctx context.Context, params *Notifi
140134
return nil
141135
}
142136

137+
func (service *NotificationService) handleNotificationFailed(ctx context.Context, err error, params *NotificationSendParams) error {
138+
ctx, span := service.tracer.Start(ctx)
139+
defer span.End()
140+
141+
ctxLogger := service.tracer.CtxLogger(service.logger, span)
142+
143+
msg := fmt.Sprintf("cannot send notification for message [%s] to phone [%s]", params.MessageID, params.PhoneNotificationID)
144+
ctxLogger.Warn(stacktrace.Propagate(err, msg))
145+
146+
event, err := service.createMessageNotificationFailedEvent(params.Source, err.Error(), params)
147+
if err != nil {
148+
return stacktrace.Propagate(err, fmt.Sprintf("cannot create [%s] event for notification [%s]", events.EventTypeMessageNotificationFailed, params.PhoneNotificationID))
149+
}
150+
151+
if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
152+
return stacktrace.Propagate(err, fmt.Sprintf("cannot dispatch event [%s] for notification [%s]", event.Type(), params.PhoneNotificationID))
153+
}
154+
155+
service.updateStatus(ctx, params.PhoneNotificationID, entities.PhoneNotificationStatusFailed)
156+
return nil
157+
}
158+
159+
func (service *NotificationService) handleNotificationSent(ctx context.Context, result string, params *NotificationSendParams) error {
160+
ctx, span := service.tracer.Start(ctx)
161+
defer span.End()
162+
163+
ctxLogger := service.tracer.CtxLogger(service.logger, span)
164+
165+
ctxLogger.Info(fmt.Sprintf("sent notification [%s] for message [%s] to phone [%s]", result, params.MessageID, params.PhoneID))
166+
167+
event, err := service.createMessageNotificationSentEvent(params.Source, result, params)
168+
if err != nil {
169+
return stacktrace.Propagate(err, fmt.Sprintf("cannot create [%s] event for notification [%s]", events.EventTypeMessageNotificationSent, params.PhoneNotificationID))
170+
}
171+
172+
if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
173+
return stacktrace.Propagate(err, fmt.Sprintf("cannot dispatch event [%s] for notification [%s]", event.Type(), params.PhoneNotificationID))
174+
}
175+
176+
service.updateStatus(ctx, params.PhoneNotificationID, entities.PhoneNotificationStatusSent)
177+
return nil
178+
}
179+
143180
func (service *NotificationService) createEvent(source string, notification *entities.PhoneNotification) (cloudevents.Event, error) {
144181
event := cloudevents.NewEvent()
145182

@@ -164,6 +201,57 @@ func (service *NotificationService) createEvent(source string, notification *ent
164201
return event, nil
165202
}
166203

204+
func (service *NotificationService) createMessageNotificationSentEvent(source string, fcmMessageID string, params *NotificationSendParams) (cloudevents.Event, error) {
205+
event := cloudevents.NewEvent()
206+
207+
event.SetSource(source)
208+
event.SetType(events.EventTypeMessageNotificationSent)
209+
event.SetTime(time.Now().UTC())
210+
event.SetID(uuid.New().String())
211+
212+
payload := events.MessageNotificationSentPayload{
213+
MessageID: params.MessageID,
214+
UserID: params.UserID,
215+
PhoneID: params.PhoneID,
216+
ScheduledAt: params.ScheduledAt,
217+
FcmMessageID: fcmMessageID,
218+
NotificationSentAt: time.Now().UTC(),
219+
NotificationID: params.PhoneNotificationID,
220+
}
221+
222+
if err := event.SetData(cloudevents.ApplicationJSON, payload); err != nil {
223+
msg := fmt.Sprintf("cannot encode %T [%#+v] as JSON", payload, payload)
224+
return event, stacktrace.Propagate(err, msg)
225+
}
226+
227+
return event, nil
228+
}
229+
230+
func (service *NotificationService) createMessageNotificationFailedEvent(source string, errorMessage string, params *NotificationSendParams) (cloudevents.Event, error) {
231+
event := cloudevents.NewEvent()
232+
233+
event.SetSource(source)
234+
event.SetType(events.EventTypeMessageNotificationFailed)
235+
event.SetTime(time.Now().UTC())
236+
event.SetID(uuid.New().String())
237+
238+
payload := events.MessageNotificationFailedPayload{
239+
MessageID: params.MessageID,
240+
UserID: params.UserID,
241+
PhoneID: params.PhoneID,
242+
ErrorMessage: errorMessage,
243+
NotificationFailedAt: time.Now().UTC(),
244+
NotificationID: params.PhoneNotificationID,
245+
}
246+
247+
if err := event.SetData(cloudevents.ApplicationJSON, payload); err != nil {
248+
msg := fmt.Sprintf("cannot encode %T [%#+v] as JSON", payload, payload)
249+
return event, stacktrace.Propagate(err, msg)
250+
}
251+
252+
return event, nil
253+
}
254+
167255
func (service *NotificationService) updateStatus(ctx context.Context, notificationID uuid.UUID, status entities.PhoneNotificationStatus) {
168256
ctx, span := service.tracer.Start(ctx)
169257
defer span.End()
@@ -174,7 +262,6 @@ func (service *NotificationService) updateStatus(ctx context.Context, notificati
174262
if err != nil {
175263
msg := fmt.Sprintf("cannot update status of notificaiton with id [%s] to [%s]", notificationID, status)
176264
ctxLogger.Error(stacktrace.Propagate(err, msg))
177-
return
178265
}
179266

180267
ctxLogger.Info(fmt.Sprintf("updated status of notificaiton with id [%s] to [%s]", notificationID, status))

0 commit comments

Comments
 (0)