Skip to content

Commit 7a9b2f6

Browse files
AchoArnoldCopilot
andcommitted
fix: handle phone numbers without + prefix and fix CSV SendTime parsing
- Add + prefix normalization in validateMessages before phonenumbers.Parse - Change SendTime from *time.Time to string (SendTimeRaw) for CSV unmarshaling - Add GetSendTime() method that parses multiple date formats gracefully - Empty SendTime fields no longer cause 'Cannot read contents' errors - Support RFC3339, YYYY-MM-DDTHH:MM:SS, YYYY-MM-DD HH:MM:SS, YYYY-MM-DD formats Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 959b8e4 commit 7a9b2f6

4 files changed

Lines changed: 52 additions & 15 deletions

File tree

api/pkg/handlers/bulk_message_handler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.SendTime == nil {
124+
if message.GetSendTime() == nil {
125125
perPhoneIndex = phoneIndexCounter[message.FromPhoneNumber]
126126
phoneIndexCounter[message.FromPhoneNumber]++
127127
}

api/pkg/requests/bulk_message_request.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,34 @@ import (
1414
// BulkMessage represents a single message in a bulk SMS request
1515
type BulkMessage struct {
1616
request
17-
FromPhoneNumber string `csv:"FromPhoneNumber"`
18-
ToPhoneNumber string `csv:"ToPhoneNumber"`
19-
Content string `csv:"Content"`
20-
SendTime *time.Time `csv:"SendTime(optional)"`
21-
AttachmentURLs string `csv:"AttachmentURLs(optional)" validate:"optional"` // Comma separated list of URLs
17+
FromPhoneNumber string `csv:"FromPhoneNumber"`
18+
ToPhoneNumber string `csv:"ToPhoneNumber"`
19+
Content string `csv:"Content"`
20+
SendTimeRaw string `csv:"SendTime(optional)"`
21+
AttachmentURLs string `csv:"AttachmentURLs(optional)" validate:"optional"` // Comma separated list of URLs
22+
}
23+
24+
// GetSendTime parses the raw SendTime string into a *time.Time
25+
func (input *BulkMessage) GetSendTime() *time.Time {
26+
raw := strings.TrimSpace(input.SendTimeRaw)
27+
if raw == "" {
28+
return nil
29+
}
30+
31+
formats := []string{
32+
time.RFC3339,
33+
"2006-01-02T15:04:05",
34+
"2006-01-02 15:04:05",
35+
"2006-01-02",
36+
}
37+
38+
for _, format := range formats {
39+
if t, err := time.Parse(format, raw); err == nil {
40+
utc := t.UTC()
41+
return &utc
42+
}
43+
}
44+
return nil
2245
}
2346

2447
// Sanitize sets defaults to BulkMessage
@@ -46,7 +69,7 @@ func (input *BulkMessage) ToMessageSendParams(userID entities.UserID, requestID
4669
Owner: from,
4770
RequestID: input.sanitizeStringPointer(fmt.Sprintf("bulk-%s", requestID.String())),
4871
UserID: userID,
49-
SendAt: input.SendTime,
72+
SendAt: input.GetSendTime(),
5073
RequestReceivedAt: time.Now().UTC(),
5174
Contact: input.sanitizeAddress(input.ToPhoneNumber),
5275
Content: input.Content,

api/pkg/validators/bulk_message_handler_validator.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,15 @@ func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user
138138
continue
139139
}
140140

141-
var sendAt *time.Time
141+
var sendTimeRaw string
142142
if len(row) > 3 && strings.TrimSpace(row[3]) != "" {
143143
ctxLogger.Info(fmt.Sprintf("excel time = [%s]", row[3]))
144-
sendAt, err = v.convertExcelTime(user, row[3])
144+
sendAt, err := v.convertExcelTime(user, row[3])
145145
if err != nil {
146146
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]))
147147
return nil, result
148148
}
149+
sendTimeRaw = sendAt.Format(time.RFC3339)
149150
}
150151

151152
var attachmentURLs string
@@ -157,7 +158,7 @@ func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user
157158
FromPhoneNumber: strings.TrimSpace(row[0]),
158159
ToPhoneNumber: strings.TrimSpace(row[1]),
159160
Content: row[2],
160-
SendTime: sendAt,
161+
SendTimeRaw: sendTimeRaw,
161162
AttachmentURLs: attachmentURLs,
162163
})
163164
}
@@ -253,20 +254,33 @@ func (v *BulkMessageHandlerValidator) validateMessages(_ context.Context, messag
253254
}
254255
}
255256

256-
if _, err := phonenumbers.Parse(message.FromPhoneNumber, phonenumbers.UNKNOWN_REGION); err != nil {
257+
fromNumber := message.FromPhoneNumber
258+
if !strings.HasPrefix(fromNumber, "+") {
259+
fromNumber = "+" + fromNumber
260+
}
261+
if _, err := phonenumbers.Parse(fromNumber, phonenumbers.UNKNOWN_REGION); err != nil {
257262
result.Add("document", fmt.Sprintf("Row [%d]: The FromPhoneNumber [%s] is not a valid E.164 phone number", index+2, message.FromPhoneNumber))
258263
}
259264

260-
if _, err := phonenumbers.Parse(message.ToPhoneNumber, phonenumbers.UNKNOWN_REGION); err != nil {
265+
toNumber := message.ToPhoneNumber
266+
if !strings.HasPrefix(toNumber, "+") {
267+
toNumber = "+" + toNumber
268+
}
269+
if _, err := phonenumbers.Parse(toNumber, phonenumbers.UNKNOWN_REGION); err != nil {
261270
result.Add("document", fmt.Sprintf("Row [%d]: The ToPhoneNumber [%s] is not a valid E.164 phone number", index+2, message.ToPhoneNumber))
262271
}
263272

264273
if len(message.Content) > 1024 {
265274
result.Add("document", fmt.Sprintf("Row [%d]: The message content must be less than 1024 characters.", index+2))
266275
}
267276

268-
if message.SendTime != nil && message.SendTime.After(time.Now().Add(420*time.Hour)) {
269-
result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] cannot be more than 20 days (420 hours) in the future.", index+2, message.SendTime.Format(time.RFC3339)))
277+
if strings.TrimSpace(message.SendTimeRaw) != "" {
278+
sendTime := message.GetSendTime()
279+
if sendTime == nil {
280+
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.SendTimeRaw))
281+
} else if sendTime.After(time.Now().Add(420 * time.Hour)) {
282+
result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] cannot be more than 20 days (420 hours) in the future.", index+2, sendTime.Format(time.RFC3339)))
283+
}
270284
}
271285
}
272286
return result

web/pages/bulk-messages/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
</v-row>
9999
<v-row class="mt-8">
100100
<v-col cols="12">
101-
<h5 class="text-h5 mb-3">Bulk Message History</h5>
101+
<h4 class="text-h4 mb-3">Bulk Message History</h4>
102102
<p class="text--secondary">
103103
Your 10 most recent bulk SMS uploads are listed below with a
104104
breakdown of message delivery status. Click

0 commit comments

Comments
 (0)