Skip to content

Commit a4556ad

Browse files
committed
Fix the reschedule flow of messages
1 parent c3790af commit a4556ad

21 files changed

Lines changed: 376 additions & 101 deletions

api/pkg/di/container.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ func (container *Container) MessageHandlerValidator() (validator *validators.Mes
298298
return validators.NewMessageHandlerValidator(
299299
container.Logger(),
300300
container.Tracer(),
301+
container.PhoneService(),
301302
)
302303
}
303304

@@ -644,6 +645,7 @@ func (container *Container) MessageService() (service *services.MessageService)
644645
container.Tracer(),
645646
container.MessageRepository(),
646647
container.EventDispatcher(),
648+
container.PhoneService(),
647649
)
648650
}
649651

@@ -759,9 +761,13 @@ func (container *Container) initializeUptraceProvider(version string, namespace
759761
uptrace.WithServiceName(namespace),
760762
uptrace.WithServiceVersion(version),
761763
)
764+
762765
// Send buffered spans and free resources.
763766
return func() {
764-
uptrace.Shutdown(context.Background())
767+
err := uptrace.Shutdown(context.Background())
768+
if err != nil {
769+
container.logger.Error(err)
770+
}
765771
}
766772
}
767773

api/pkg/emails/hermes_theme.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ func (dt *hermesTheme) HTMLTemplate() string {
289289
<!-- Logo -->
290290
<tr>
291291
<td class="email-masthead">
292-
<a class="email-masthead_name" href="{{.Hermes.Product.Link}}" target="_blank">
292+
<a class="email-masthead_name" style="color: white;" href="{{.Hermes.Product.Link}}" target="_blank">
293293
{{ if .Hermes.Product.Logo }}
294294
HTTP SMS
295295
{{ else }}

api/pkg/entities/message.go

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,21 @@ type Message struct {
7373
// SendDuration is the number of nanoseconds from when the request was received until when the mobile phone send the message
7474
SendDuration *int64 `json:"send_time" example:"133414"`
7575

76-
RequestReceivedAt time.Time `json:"request_received_at" example:"2022-06-05T14:26:01.520828+03:00"`
77-
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
78-
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
79-
OrderTimestamp time.Time `json:"order_timestamp" gorm:"index:idx_messages_order_timestamp" example:"2022-06-05T14:26:09.527976+03:00"`
80-
LastAttemptedAt *time.Time `json:"last_attempted_at" example:"2022-06-05T14:26:09.527976+03:00"`
81-
ScheduledAt *time.Time `json:"scheduled_at" example:"2022-06-05T14:26:09.527976+03:00"`
82-
SentAt *time.Time `json:"sent_at" example:"2022-06-05T14:26:09.527976+03:00"`
83-
ReceivedAt *time.Time `json:"received_at" example:"2022-06-05T14:26:09.527976+03:00"`
84-
SendAttempts []time.Time `json:"send_attempts"`
85-
FailureReason *string `json:"failure_reason" example:"UNKNOWN"`
76+
RequestReceivedAt time.Time `json:"request_received_at" example:"2022-06-05T14:26:01.520828+03:00"`
77+
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
78+
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
79+
OrderTimestamp time.Time `json:"order_timestamp" gorm:"index:idx_messages_order_timestamp" example:"2022-06-05T14:26:09.527976+03:00"`
80+
LastAttemptedAt *time.Time `json:"last_attempted_at" example:"2022-06-05T14:26:09.527976+03:00"`
81+
NotificationScheduledAt *time.Time `json:"scheduled_at" example:"2022-06-05T14:26:09.527976+03:00"`
82+
SentAt *time.Time `json:"sent_at" example:"2022-06-05T14:26:09.527976+03:00"`
83+
DeliveredAt *time.Time `json:"delivered_at" example:"2022-06-05T14:26:09.527976+03:00"`
84+
ExpiredAt *time.Time `json:"expired_at" example:"2022-06-05T14:26:09.527976+03:00"`
85+
FailedAt *time.Time `json:"failed_at" example:"2022-06-05T14:26:09.527976+03:00"`
86+
CanBePolled bool `json:"can_be_polled" example:"false"`
87+
SendAttemptCount uint `json:"send_attempt_count" example:"0"`
88+
MaxSendAttempts uint `json:"max_send_attempts" example:"1"`
89+
ReceivedAt *time.Time `json:"received_at" example:"2022-06-05T14:26:09.527976+03:00"`
90+
FailureReason *string `json:"failure_reason" example:"UNKNOWN"`
8691
}
8792

8893
// IsSending determines if a message is being sent
@@ -100,11 +105,21 @@ func (message *Message) IsPending() bool {
100105
return message.Status == MessageStatusPending
101106
}
102107

108+
// IsScheduled checks if a message is scheduled
109+
func (message *Message) IsScheduled() bool {
110+
return message.Status == MessageStatusScheduled
111+
}
112+
103113
// IsExpired checks if a message is expired
104114
func (message *Message) IsExpired() bool {
105115
return message.Status == MessageStatusExpired
106116
}
107117

118+
// CanBeRescheduled checks if a message can be rescheduled
119+
func (message *Message) CanBeRescheduled() bool {
120+
return message.SendAttemptCount < message.MaxSendAttempts
121+
}
122+
108123
// IsSent determines if a message has been sent
109124
func (message *Message) IsSent() bool {
110125
return message.Status == MessageStatusSent
@@ -122,7 +137,7 @@ func (message *Message) Sent(timestamp time.Time) *Message {
122137

123138
// Failed registers a message as failed
124139
func (message *Message) Failed(timestamp time.Time, errorMessage string) *Message {
125-
message.SentAt = &timestamp
140+
message.FailedAt = &timestamp
126141
message.Status = MessageStatusFailed
127142
message.FailureReason = &errorMessage
128143
message.updateOrderTimestamp(timestamp)
@@ -131,16 +146,26 @@ func (message *Message) Failed(timestamp time.Time, errorMessage string) *Messag
131146

132147
// Delivered registers a message as delivered
133148
func (message *Message) Delivered(timestamp time.Time) *Message {
134-
message.SentAt = &timestamp
149+
message.DeliveredAt = &timestamp
135150
message.Status = MessageStatusDelivered
136151
message.updateOrderTimestamp(timestamp)
137152
return message
138153
}
139154

140155
// Expired registers a message as expired
141156
func (message *Message) Expired(timestamp time.Time) *Message {
142-
message.SentAt = &timestamp
157+
message.ExpiredAt = &timestamp
143158
message.Status = MessageStatusExpired
159+
message.CanBePolled = true
160+
message.SendAttemptCount++
161+
message.updateOrderTimestamp(timestamp)
162+
return message
163+
}
164+
165+
// NotificationScheduled registers a message as scheduled
166+
func (message *Message) NotificationScheduled(timestamp time.Time) *Message {
167+
message.NotificationScheduledAt = &timestamp
168+
message.Status = MessageStatusScheduled
144169
message.updateOrderTimestamp(timestamp)
145170
return message
146171
}

api/pkg/entities/phone.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ type Phone struct {
1414
PhoneNumber string `json:"phone_number" example:"+18005550199"`
1515
MessagesPerMinute uint `json:"messages_per_minute" example:"1"`
1616

17+
// MaxSendAttempts determines how many times to retry sending an SMS message
18+
MaxSendAttempts uint `json:"max_send_attempts" example:"1"`
19+
1720
// MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired.
1821
MessageExpirationSeconds uint `json:"message_expiration_seconds"`
1922

@@ -25,3 +28,11 @@ type Phone struct {
2528
func (phone *Phone) MessageExpirationDuration() time.Duration {
2629
return time.Duration(phone.MessageExpirationSeconds) * time.Second
2730
}
31+
32+
// MaxSendAttemptsSanitized returns the max send attempts replacing 0 with 1
33+
func (phone *Phone) MaxSendAttemptsSanitized() uint {
34+
if phone.MaxSendAttempts == 0 {
35+
return 1
36+
}
37+
return phone.MaxSendAttempts
38+
}

api/pkg/entities/user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/google/uuid"
77
)
88

9-
// UserID is the MessageID of a user
9+
// UserID is the ID of a user
1010
type UserID string
1111

1212
// User stores information about a user

api/pkg/events/message_notification_scheduled_event.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const EventTypeMessageNotificationScheduled = "message.notification.scheduled"
1414
// MessageNotificationScheduledPayload is the payload of the EventTypeMessageNotificationScheduled event
1515
type MessageNotificationScheduledPayload struct {
1616
MessageID uuid.UUID `json:"id"`
17+
Owner string `json:"owner"`
18+
Contact string `json:"contact"`
19+
Content string `json:"content"`
1720
UserID entities.UserID `json:"user_id"`
1821
PhoneID uuid.UUID `json:"phone_id"`
1922
ScheduledAt time.Time `json:"scheduled_at"`
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
// EventTypeMessageNotificationSend is emitted when we are to send a phone notification
12+
const EventTypeMessageNotificationSend = "message.notification.send"
13+
14+
// MessageNotificationSendPayload is the payload of the EventTypeMessageNotificationSend event
15+
type MessageNotificationSendPayload struct {
16+
MessageID uuid.UUID `json:"id"`
17+
UserID entities.UserID `json:"user_id"`
18+
PhoneID uuid.UUID `json:"phone_id"`
19+
ScheduledAt time.Time `json:"scheduled_at"`
20+
NotificationID uuid.UUID `json:"notification_id"`
21+
}
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+
// EventTypeMessageSendRetry is emitted when the phone a message expires and is being retried
12+
const EventTypeMessageSendRetry = "message.send.retry"
13+
14+
// MessageSendRetryPayload is the payload of the EventTypeMessageSendRetry event
15+
type MessageSendRetryPayload struct {
16+
MessageID uuid.UUID `json:"message_id"`
17+
Owner string `json:"owner"`
18+
Contact string `json:"contact"`
19+
UserID entities.UserID `json:"user_id"`
20+
Timestamp time.Time `json:"timestamp"`
21+
Content string `json:"content"`
22+
}

api/pkg/handlers/events_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (h *EventsHandler) Dispatch(c *fiber.Ctx) error {
6868
return h.responseForbidden(c)
6969
}
7070

71-
err := h.service.Dispatch(ctx, request)
71+
err := h.service.DispatchSync(ctx, request)
7272
if err != nil {
7373
msg := fmt.Sprintf("cannot dispatch event with ID [%s]", request.ID())
7474
ctxLogger.Error(stacktrace.Propagate(err, msg))

api/pkg/handlers/message_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (h *MessageHandler) PostSend(c *fiber.Ctx) error {
7777
return h.responseBadRequest(c, err)
7878
}
7979

80-
if errors := h.validator.ValidateMessageSend(ctx, request.Sanitize()); len(errors) != 0 {
80+
if errors := h.validator.ValidateMessageSend(ctx, h.userIDFomContext(c), request.Sanitize()); len(errors) != 0 {
8181
msg := fmt.Sprintf("validation errors [%s], while sending payload [%s]", spew.Sdump(errors), c.Body())
8282
ctxLogger.Warn(stacktrace.NewError(msg))
8383
return h.responseUnprocessableEntity(c, errors, "validation errors while sending message")

0 commit comments

Comments
 (0)