Skip to content

Commit 808c0e1

Browse files
committed
Fix: parse time in user's location in CSV file
1 parent 56a9a44 commit 808c0e1

3 files changed

Lines changed: 38 additions & 35 deletions

File tree

api/pkg/handlers/bulk_message_handler.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error {
9999
return h.responseBadRequest(c, err)
100100
}
101101

102-
messages, fileType, validationErrors := h.validator.ValidateStore(ctx, h.userIDFomContext(c), file)
102+
messages, fileType, userLocation, validationErrors := h.validator.ValidateStore(ctx, h.userIDFomContext(c), file)
103103
if len(validationErrors) != 0 {
104104
msg := fmt.Sprintf("validation errors [%s], while sending bulk sms from CSV file [%s] for [%s]", spew.Sdump(validationErrors), file.Filename, h.userIDFomContext(c))
105105
ctxLogger.Warn(stacktrace.NewError(msg))
@@ -121,7 +121,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error {
121121
for _, message := range messages {
122122
wg.Add(1)
123123
var perPhoneIndex int
124-
if message.GetSendTime() == nil {
124+
if message.GetSendTime(userLocation) == nil {
125125
perPhoneIndex = phoneIndexCounter[message.FromPhoneNumber]
126126
phoneIndexCounter[message.FromPhoneNumber]++
127127
}
@@ -130,11 +130,11 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error {
130130
count.Add(1)
131131
_, err = h.messageService.SendMessage(
132132
ctx,
133-
message.ToMessageSendParams(h.userIDFomContext(c), requestID, c.OriginalURL(), index),
133+
message.ToMessageSendParams(h.userIDFomContext(c), requestID, c.OriginalURL(), index, userLocation),
134134
)
135135
if err != nil {
136136
count.Add(-1)
137-
msg := fmt.Sprintf("cannot send message with paylod [%s] at index [%d]", spew.Sdump(message), index)
137+
msg := fmt.Sprintf("cannot send message with payload [%s] at index [%d]", spew.Sdump(message), index)
138138
ctxLogger.Error(stacktrace.Propagate(err, msg))
139139
}
140140
wg.Done()

api/pkg/requests/bulk_message_request.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,33 @@ type BulkMessage struct {
2020
AttachmentURLs string `csv:"AttachmentURLs(optional)" validate:"optional"` // Comma separated list of URLs
2121
}
2222

23-
// GetSendTime parses the raw SendTime string into a *time.Time
24-
func (input *BulkMessage) GetSendTime() *time.Time {
23+
// GetSendTime parses the raw SendTime string into a *time.Time.
24+
// For timezone-naive formats, the time is interpreted in the given location.
25+
// For RFC3339 (which includes an offset), the embedded offset is used.
26+
func (input *BulkMessage) GetSendTime(location *time.Location) *time.Time {
2527
raw := strings.TrimSpace(input.SendTime)
2628
if raw == "" {
2729
return nil
2830
}
2931

30-
formats := []string{
31-
time.RFC3339,
32+
if location == nil {
33+
location = time.UTC
34+
}
35+
36+
// RFC3339 already contains timezone offset, parse without location
37+
if t, err := time.Parse(time.RFC3339, raw); err == nil {
38+
utc := t.UTC()
39+
return &utc
40+
}
41+
42+
// Naive formats: interpret in the user's location
43+
naiveFormats := []string{
3244
"2006-01-02T15:04:05",
3345
"2006-01-02 15:04:05",
34-
"2006-01-02",
3546
}
3647

37-
for _, format := range formats {
38-
if t, err := time.Parse(format, raw); err == nil {
48+
for _, format := range naiveFormats {
49+
if t, err := time.ParseInLocation(format, raw, location); err == nil {
3950
utc := t.UTC()
4051
return &utc
4152
}
@@ -60,15 +71,15 @@ func (input *BulkMessage) Sanitize() *BulkMessage {
6071
}
6172

6273
// ToMessageSendParams converts BulkMessage to services.MessageSendParams
63-
func (input *BulkMessage) ToMessageSendParams(userID entities.UserID, requestID string, source string, index int) services.MessageSendParams {
74+
func (input *BulkMessage) ToMessageSendParams(userID entities.UserID, requestID string, source string, index int, location *time.Location) services.MessageSendParams {
6475
from, _ := phonenumbers.Parse(input.FromPhoneNumber, phonenumbers.UNKNOWN_REGION)
6576

6677
return services.MessageSendParams{
6778
Source: source,
6879
Owner: from,
6980
RequestID: input.sanitizeStringPointer(requestID),
7081
UserID: userID,
71-
SendAt: input.GetSendTime(),
82+
SendAt: input.GetSendTime(location),
7283
RequestReceivedAt: time.Now().UTC(),
7384
Contact: input.sanitizeAddress(input.ToPhoneNumber),
7485
Content: input.Content,

api/pkg/validators/bulk_message_handler_validator.go

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func NewBulkMessageHandlerValidator(
5252
}
5353

5454
// ValidateStore validates the requests.BillingUsageHistory request
55-
func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID entities.UserID, header *multipart.FileHeader) ([]*requests.BulkMessage, string, url.Values) {
55+
func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID entities.UserID, header *multipart.FileHeader) ([]*requests.BulkMessage, string, *time.Location, url.Values) {
5656
ctx, span, ctxLogger := v.tracer.StartWithLogger(ctx, v.logger)
5757
defer span.End()
5858

@@ -61,39 +61,39 @@ func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID
6161
result := url.Values{}
6262
result.Add("document", "Cannot load your account. Please try again later or contact support.")
6363
ctxLogger.Error(v.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot load user [%s]", userID))))
64-
return nil, "", result
64+
return nil, "", nil, result
6565
}
6666

6767
messages, fileType, result := v.parseFile(ctxLogger, user, header)
6868
if len(result) != 0 {
69-
return messages, fileType, result
69+
return messages, fileType, user.Location(), result
7070
}
7171

7272
if len(messages) == 0 {
7373
result.Add("document", "The uploaded file doesn't contain any valid records. Make sure you are using the official httpSMS template.")
74-
return messages, fileType, result
74+
return messages, fileType, user.Location(), result
7575
}
7676

7777
if len(messages) > 1000 {
7878
result.Add("document", "The uploaded file must contain less than 1000 records.")
79-
return messages, fileType, result
79+
return messages, fileType, user.Location(), result
8080
}
8181

8282
for index, message := range messages {
8383
messages[index] = message.Sanitize()
8484
}
8585

86-
result = v.validateMessages(ctx, messages)
86+
result = v.validateMessages(ctx, messages, user.Location())
8787
if len(result) != 0 {
88-
return messages, fileType, result
88+
return messages, fileType, user.Location(), result
8989
}
9090

9191
result = v.validateOwners(ctx, userID, messages)
9292
if len(result) != 0 {
93-
return messages, fileType, result
93+
return messages, fileType, user.Location(), result
9494
}
9595

96-
return messages, fileType, result
96+
return messages, fileType, user.Location(), result
9797
}
9898

9999
func (v *BulkMessageHandlerValidator) parseFile(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, string, url.Values) {
@@ -143,8 +143,9 @@ func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user
143143
var sendTimeRaw string
144144
if len(row) > 3 && strings.TrimSpace(row[3]) != "" {
145145
ctxLogger.Info(fmt.Sprintf("excel time = [%s]", row[3]))
146-
sendAt, err := v.convertExcelTime(user, row[3])
147-
if err != nil {
146+
msg := &requests.BulkMessage{SendTime: strings.TrimSpace(row[3])}
147+
sendAt := msg.GetSendTime(user.Location())
148+
if sendAt == nil {
148149
result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] is not in the correct format e.g [2006-01-02T15:04:05] where 2006 is the year, 01 is January, 02 is the second day of the month and the time is 15:04:05", index+1, row[3]))
149150
return nil, result
150151
}
@@ -168,15 +169,6 @@ func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user
168169
return messages, url.Values{}
169170
}
170171

171-
func (v *BulkMessageHandlerValidator) convertExcelTime(user *entities.User, value string) (*time.Time, error) {
172-
t, err := time.ParseInLocation("2006-01-02T15:04:05", value, user.Location())
173-
if err != nil {
174-
return nil, stacktrace.Propagate(err, fmt.Sprintf("cannot parse excel time [%s] as [%T]", value, t))
175-
}
176-
177-
return &t, nil
178-
}
179-
180172
func (v *BulkMessageHandlerValidator) parseBytes(ctxLogger telemetry.Logger, userID entities.UserID, header *multipart.FileHeader) ([]byte, url.Values) {
181173
result := url.Values{}
182174

@@ -223,7 +215,7 @@ func (v *BulkMessageHandlerValidator) parseCSV(ctxLogger telemetry.Logger, user
223215
return messages, url.Values{}
224216
}
225217

226-
func (v *BulkMessageHandlerValidator) validateMessages(_ context.Context, messages []*requests.BulkMessage) url.Values {
218+
func (v *BulkMessageHandlerValidator) validateMessages(_ context.Context, messages []*requests.BulkMessage, location *time.Location) url.Values {
227219
result := url.Values{}
228220
for index, message := range messages {
229221

@@ -269,7 +261,7 @@ func (v *BulkMessageHandlerValidator) validateMessages(_ context.Context, messag
269261
}
270262

271263
if strings.TrimSpace(message.SendTime) != "" {
272-
sendTime := message.GetSendTime()
264+
sendTime := message.GetSendTime(location)
273265
if sendTime == nil {
274266
result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] is not a valid date format. Use RFC3339 (e.g. 2023-11-11T02:10:01Z) or YYYY-MM-DDTHH:MM:SS.", index+2, message.SendTime))
275267
} else if sendTime.After(time.Now().Add(420 * time.Hour)) {

0 commit comments

Comments
 (0)