Skip to content

Commit b534091

Browse files
AchoArnoldCopilot
andcommitted
refactor: use timestamp+filename for bulk message request ID
- Generate request ID as bulk-{base62_timestamp}-{truncated_filename} - Encode unix timestamp as base62 for minimal length (~6 chars) - Truncate filename to max 32 chars preserving extension - Remove fileType return value from ValidateStore - Simplify frontend cleanName() to just strip 'bulk-' prefix - Stay on bulk-messages page after upload instead of redirecting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 808c0e1 commit b534091

5 files changed

Lines changed: 50 additions & 44 deletions

File tree

api/go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ require (
3333
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
3434
github.com/jszwec/csvutil v1.10.0
3535
github.com/lib/pq v1.12.3
36-
github.com/matoous/go-nanoid/v2 v2.1.0
3736
github.com/nyaruka/phonenumbers v1.7.2
3837
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177
3938
github.com/patrickmn/go-cache v2.1.0+incompatible

api/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
237237
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
238238
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
239239
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
240-
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
241-
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
242240
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
243241
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
244242
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=

api/pkg/handlers/bulk_message_handler.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package handlers
22

33
import (
4-
"crypto/rand"
54
"fmt"
5+
"path/filepath"
66
"sync"
77
"sync/atomic"
8+
"time"
89

910
"github.com/NdoleStudio/httpsms/pkg/requests"
1011
"github.com/NdoleStudio/httpsms/pkg/services"
1112
"github.com/NdoleStudio/httpsms/pkg/telemetry"
1213
"github.com/NdoleStudio/httpsms/pkg/validators"
1314
"github.com/davecgh/go-spew/spew"
1415
"github.com/gofiber/fiber/v2"
15-
gonanoid "github.com/matoous/go-nanoid/v2"
1616
"github.com/palantir/stacktrace"
1717
)
1818

@@ -99,7 +99,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error {
9999
return h.responseBadRequest(c, err)
100100
}
101101

102-
messages, fileType, userLocation, validationErrors := h.validator.ValidateStore(ctx, h.userIDFomContext(c), file)
102+
messages, 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))
@@ -111,7 +111,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error {
111111
return h.responsePaymentRequired(c, *msg)
112112
}
113113

114-
requestID := h.generateRequestID(fileType, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
114+
requestID := h.generateRequestID(file.Filename)
115115
wg := sync.WaitGroup{}
116116
count := atomic.Int64{}
117117

@@ -145,21 +145,39 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error {
145145
return h.responseAccepted(c, fmt.Sprintf("Added %d out of %d messages to the queue", count.Load(), len(messages)))
146146
}
147147

148-
func (h *BulkMessageHandler) generateRequestID(fileType string, alphabet string) string {
149-
id, err := gonanoid.Generate(alphabet, 10)
150-
if err != nil {
151-
id = h.randomAlphaNum(10, alphabet)
148+
func (h *BulkMessageHandler) generateRequestID(filename string) string {
149+
timestamp := encodeBase62(time.Now().Unix())
150+
truncated := truncateFilename(filename, 32)
151+
return fmt.Sprintf("bulk-%s-%s", timestamp, truncated)
152+
}
153+
154+
func encodeBase62(n int64) string {
155+
const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
156+
if n == 0 {
157+
return "0"
158+
}
159+
result := make([]byte, 0, 8)
160+
for n > 0 {
161+
result = append(result, charset[n%62])
162+
n /= 62
163+
}
164+
// reverse
165+
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
166+
result[i], result[j] = result[j], result[i]
152167
}
153-
return fmt.Sprintf("bulk-%s-%s", fileType, id)
168+
return string(result)
154169
}
155170

156-
func (h *BulkMessageHandler) randomAlphaNum(length int, alphabet string) string {
157-
b := make([]byte, length)
158-
if _, err := rand.Read(b); err != nil {
159-
return alphabet[:length]
171+
func truncateFilename(filename string, maxLen int) string {
172+
if len(filename) <= maxLen {
173+
return filename
160174
}
161-
for i := range b {
162-
b[i] = alphabet[int(b[i])%len(alphabet)]
175+
ext := filepath.Ext(filename)
176+
name := filename[:len(filename)-len(ext)]
177+
available := maxLen - len(ext)
178+
if available <= 0 {
179+
return filename[:maxLen]
163180
}
164-
return string(b)
181+
half := available / 2
182+
return name[:half] + name[len(name)-(available-half):] + ext
165183
}

api/pkg/validators/bulk_message_handler_validator.go

Lines changed: 13 additions & 15 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, *time.Location, url.Values) {
55+
func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID entities.UserID, header *multipart.FileHeader) ([]*requests.BulkMessage, *time.Location, url.Values) {
5656
ctx, span, ctxLogger := v.tracer.StartWithLogger(ctx, v.logger)
5757
defer span.End()
5858

@@ -61,22 +61,22 @@ 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, "", nil, result
64+
return nil, nil, result
6565
}
6666

67-
messages, fileType, result := v.parseFile(ctxLogger, user, header)
67+
messages, result := v.parseFile(ctxLogger, user, header)
6868
if len(result) != 0 {
69-
return messages, fileType, user.Location(), result
69+
return messages, 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, user.Location(), result
74+
return messages, 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, user.Location(), result
79+
return messages, user.Location(), result
8080
}
8181

8282
for index, message := range messages {
@@ -85,32 +85,30 @@ func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID
8585

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

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

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

99-
func (v *BulkMessageHandlerValidator) parseFile(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, string, url.Values) {
99+
func (v *BulkMessageHandlerValidator) parseFile(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, url.Values) {
100100
if header.Header.Get("Content-Type") == "text/csv" || strings.HasSuffix(header.Filename, ".csv") {
101-
messages, result := v.parseCSV(ctxLogger, user, header)
102-
return messages, "csv", result
101+
return v.parseCSV(ctxLogger, user, header)
103102
}
104103
if header.Header.Get("Content-Type") == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || strings.HasSuffix(header.Filename, ".xlsx") {
105-
messages, result := v.parseXlsx(ctxLogger, user, header)
106-
return messages, "xls", result
104+
return v.parseXlsx(ctxLogger, user, header)
107105
}
108106

109107
ctxLogger.Error(stacktrace.NewError(fmt.Sprintf("cannot parse file [%s] for user [%s] with content type [%s]", header.Filename, user.ID, header.Header.Get("Content-Type"))))
110108

111109
result := url.Values{}
112110
result.Add("document", fmt.Sprintf("The file [%s] is not a valid CSV or Excel file.", header.Filename))
113-
return nil, "", result
111+
return nil, result
114112
}
115113

116114
func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, url.Values) {

web/pages/bulk-messages/index.vue

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,6 @@ export default Vue.extend({
221221
},
222222
methods: {
223223
cleanName(requestId: string): string {
224-
if (requestId.startsWith('bulk-csv-')) {
225-
return requestId.replace(/^bulk-csv-/, '') + '.csv'
226-
}
227-
if (requestId.startsWith('bulk-xls-')) {
228-
return requestId.replace(/^bulk-xls-/, '') + '.xlsx'
229-
}
230224
return requestId.replace(/^bulk-/, '')
231225
},
232226
fetchBulkOrders() {
@@ -251,10 +245,9 @@ export default Vue.extend({
251245
this.$store
252246
.dispatch('sendBulkMessages', this.formFile)
253247
.then(() => {
254-
setTimeout(() => {
255-
this.loading = false
256-
this.$router.push({ name: 'threads' })
257-
}, 2000)
248+
this.loading = false
249+
this.formFile = null
250+
this.fetchBulkOrders()
258251
})
259252
.catch((error: AxiosError<ResponsesUnprocessableEntity>) => {
260253
this.errorTitle = capitalize(

0 commit comments

Comments
 (0)