Skip to content

refactor: use timestamp+filename for bulk message request ID#903

Merged
AchoArnold merged 3 commits into
mainfrom
refactor/bulk-message-request-id
May 24, 2026
Merged

refactor: use timestamp+filename for bulk message request ID#903
AchoArnold merged 3 commits into
mainfrom
refactor/bulk-message-request-id

Conversation

@AchoArnold
Copy link
Copy Markdown
Member

Changes

  • Generate request ID as \�ulk-{base62_timestamp}-{truncated_filename}\ instead of \�ulk-{filetype}-{nanoid}\
  • Encode unix timestamp as base62 for minimal string length (~6 chars)
  • Truncate filename to max 32 characters, preserving the file extension by cutting the middle of the stem
  • Remove \ ileType\ return value from \ValidateStore\ and \parseFile\ (no longer needed)
  • Simplify frontend \cleanName()\ to just strip the \�ulk-\ prefix
  • Stay on bulk-messages page after successful upload instead of redirecting to threads

- 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>
@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 24, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 4 complexity · 0 duplication

Metric Results
Complexity 4
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 24, 2026

Greptile Summary

This PR changes how bulk message request IDs are generated — from bulk-{fileType}-{nanoid} to bulk-{base62_timestamp}-{truncated_filename} — and updates the frontend to stay on the bulk-messages page after a successful upload instead of redirecting to threads.

  • ID generation (bulk_message_handler.go): Drops go-nanoid, adds a custom encodeBase62 helper and a truncateFilename function that preserves the file extension while cutting the stem to fit within 32 characters.
  • Validator cleanup (bulk_message_handler_validator.go): Removes the fileType return value from ValidateStore and parseFile since it is no longer needed by the handler.
  • Frontend UX (index.vue): cleanName is simplified to just strip the bulk- prefix; on successful upload the form is cleared and the history table is refreshed in place rather than navigating away.

Confidence Score: 3/5

The core ID-generation change introduces a real collision window: two uploads of the same filename within the same second produce identical request IDs, merging their message batches in the database and the history view.

The timestamp has only second-level precision, so rapid or concurrent uploads of identically-named files silently share a request ID — every message from both batches becomes indistinguishable. Raw user-supplied filenames are also embedded without sanitization, and the simplified cleanName function breaks display of all pre-existing database entries.

Focus on api/pkg/handlers/bulk_message_handler.go — specifically the generateRequestID function and its lack of a uniqueness guarantee — and web/pages/bulk-messages/index.vue for the backward-compatibility gap in cleanName.

Important Files Changed

Filename Overview
api/pkg/handlers/bulk_message_handler.go Replaces nanoid-based requestID with timestamp+filename; timestamp has second-level granularity, risking ID collisions for same-second uploads of the same file; also embeds raw user-supplied filename into the ID without sanitization.
api/pkg/validators/bulk_message_handler_validator.go Removes fileType return value from ValidateStore and parseFile; clean mechanical refactor with no logic changes.
web/pages/bulk-messages/index.vue Simplifies cleanName to strip only bulk- prefix (breaking display of historical entries) and removes post-upload redirect in favour of refreshing the history table in place.
api/go.mod Removes go-nanoid dependency; straightforward dependency cleanup.

Sequence Diagram

sequenceDiagram
    participant U as User (Browser)
    participant V as index.vue
    participant A as BulkMessageHandler
    participant Val as BulkMessageHandlerValidator
    participant S as MessageService

    U->>V: Upload CSV/XLSX file
    V->>A: POST /v1/bulk-messages (multipart)
    A->>Val: ValidateStore(ctx, userID, file)
    Val-->>A: messages, userLocation, errors
    A->>A: generateRequestID(file.Filename)
    note over A: bulk-{base62(now.Unix())}-{truncate(filename,32)}
    loop For each message (goroutine)
        A->>S: SendMessage(requestID, ...)
    end
    A-->>V: 202 Accepted
    V->>V: "formFile = null"
    V->>A: GET /v1/bulk-messages
    A-->>V: bulk order history
    V-->>U: Refresh history table
Loading

Reviews (1): Last reviewed commit: "refactor: use timestamp+filename for bul..." | Re-trigger Greptile

Comment on lines +148 to +152
func (h *BulkMessageHandler) generateRequestID(filename string) string {
timestamp := encodeBase62(time.Now().Unix())
truncated := truncateFilename(filename, 32)
return fmt.Sprintf("bulk-%s-%s", timestamp, truncated)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Timestamp-only uniqueness — concurrent uploads collide

time.Now().Unix() has one-second granularity, so two uploads of the same filename that arrive within the same second produce an identical requestID. All messages from both batches would be tagged with the same ID and become indistinguishable in search and in the history table. The previous nanoid-based scheme avoided this by adding 10 random characters. Adding even 4 random alphanumeric characters after the timestamp (e.g. bulk-{ts}-{file}-{rand4}) would make collisions astronomically unlikely while keeping the human-readable filename benefit.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added a 4-character random base62 suffix after the timestamp, making the format \�ulk-{base62_ts}{rand4}-{filename}. This makes same-second collisions astronomically unlikely while keeping the human-readable filename.

Comment on lines 223 to 225
cleanName(requestId: string): string {
if (requestId.startsWith('bulk-csv-')) {
return requestId.replace(/^bulk-csv-/, '') + '.csv'
}
if (requestId.startsWith('bulk-xls-')) {
return requestId.replace(/^bulk-xls-/, '') + '.xlsx'
}
return requestId.replace(/^bulk-/, '')
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 cleanName breaks display of historical entries

The simplified regex strips only the bulk- prefix, so existing database entries with the old bulk-csv-NANOID or bulk-xls-NANOID format will now render as csv-NANOID / xls-NANOID in the history table. Previously they rendered as NANOID.csv / NANOID.xlsx. A backward-compatible implementation would retain the old prefix checks as fallback branches for entries that don't match the new bulk-{ts}-{filename} pattern.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Restored the old prefix checks as fallback branches for legacy entries (bulk-csv-/bulk-xls-). New entries using the timestamp format just strip the bulk- prefix.

Comment on lines +171 to 183
func truncateFilename(filename string, maxLen int) string {
if len(filename) <= maxLen {
return filename
}
for i := range b {
b[i] = alphabet[int(b[i])%len(alphabet)]
ext := filepath.Ext(filename)
name := filename[:len(filename)-len(ext)]
available := maxLen - len(ext)
if available <= 0 {
return filename[:maxLen]
}
return string(b)
half := available / 2
return name[:half] + name[len(name)-(available-half):] + ext
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unsanitized user-supplied filename embedded in requestID

truncateFilename preserves the raw filename characters (spaces, Unicode, dashes, dots, %, etc.) from the client's upload. The previous scheme used only a controlled string ("csv" or "xls"). If requestID is ever used in a log query, a URL parameter, or any downstream context that doesn't expect arbitrary text, the unsanitized portion could cause unexpected behavior. Consider stripping or replacing non-alphanumeric characters (excluding . and -) before embedding the filename in the ID.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added a sanitizeFilename function that strips all characters except alphanumeric, dots, and hyphens before embedding the filename in the request ID.

AchoArnold and others added 2 commits May 24, 2026 18:16
- Add 4-char random base62 suffix to prevent same-second collisions
- Sanitize filename by stripping non-alphanumeric chars (except . and -)
- Restore backward-compat in cleanName for old bulk-csv-/bulk-xls- entries

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@AchoArnold AchoArnold merged commit 9a8a681 into main May 24, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant