Skip to content

feat(api): migrate POST /api/upload from chat (Arweave bytes proxy)#492

Open
arpitgupta1214 wants to merge 2 commits intotestfrom
migrate/upload-group2
Open

feat(api): migrate POST /api/upload from chat (Arweave bytes proxy)#492
arpitgupta1214 wants to merge 2 commits intotestfrom
migrate/upload-group2

Conversation

@arpitgupta1214
Copy link
Copy Markdown
Collaborator

@arpitgupta1214 arpitgupta1214 commented Apr 28, 2026

Adds dedicated POST /api/upload parity for chat's /api/upload (group 2 of the chat→api migration). Multipart file → Arweave upload → returns { success, fileName, fileType, fileSize, url } (200) or { success: false, error } (500). Reuses existing uploadToArweave() and getFetchableUrl() libs.

Test plan

  • Vercel preview: curl -F file=@/tmp/img.png https://<preview>/api/upload returns 200 + Arweave gateway URL
  • curl -X POST https://<preview>/api/upload (no file) returns 500 + { success: false, error: "No file provided" }
  • Vitest: 4 new cases in lib/arweave/__tests__/uploadFileHandler.test.ts green; full suite still 2308/2308

Summary by cubic

Adds POST /api/upload to proxy multipart file uploads to Arweave and return a gateway URL, matching chat’s /api/upload for a drop-in base-URL swap. Includes CORS preflight and CORS headers on all responses, with caching disabled for this route.

  • New Features

    • POST /api/upload accepts multipart/form-data with file, uploads bytes to Arweave, and responds with { success, fileName, fileType, fileSize, url } (200) or { success: false, error } (500).
    • OPTIONS handler adds CORS headers via getCorsHeaders(); success and error responses also include CORS headers.
    • Route disables caching: dynamic = "force-dynamic", fetchCache = "force-no-store", revalidate = 0.
  • Migration

    • Response shape mirrors chat’s /api/upload; switch clients by changing the base URL only.

Written for commit 812717f. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added file upload functionality allowing users to upload files with automatic metadata handling and receive shareable URLs for uploaded content.

Mirrors chat-side `/api/upload` shape so chat callers can migrate with a
base-URL swap. Multipart `file` field → Arweave upload → returns
`{ success, fileName, fileType, fileSize, url }` (200) or
`{ success: false, error }` (500). Reuses existing `uploadToArweave()`
and `getFetchableUrl()` libs.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview May 7, 2026 5:53am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a file upload API endpoint integrated with Arweave storage. The implementation includes a POST handler that processes multipart form data, validates and uploads files to Arweave, a CORS-enabled OPTIONS preflight handler, error handling with logging, and route configuration for dynamic execution and no-cache behavior.

Changes

Arweave File Upload Endpoint

Layer / File(s) Summary
Upload Handler Core Logic
lib/arweave/uploadFileHandler.ts
Implements uploadFileHandler that parses multipart form data, validates the file field, converts to Buffer, extracts file metadata (size, type, name), uploads to Arweave, and returns JSON response with gateway URL and CORS headers.
Handler Dependencies
lib/arweave/uploadFileHandler.ts
Imports NextRequest, NextResponse, getCorsHeaders(), uploadToArweave(), and getFetchableUrl() utilities.
Error Handling & Logging
lib/arweave/uploadFileHandler.ts
On error, logs failure, returns standardized { success: false, error } JSON with HTTP 500 and CORS headers.
POST Route Handler
app/api/upload/route.ts
Creates /api/upload POST endpoint that forwards NextRequest to uploadFileHandler and returns its response.
CORS Preflight Support
app/api/upload/route.ts
Adds OPTIONS handler returning HTTP 200 with getCorsHeaders() to satisfy browser preflight requests.
Route Configuration
app/api/upload/route.ts
Sets dynamic = "force-dynamic", fetchCache = "force-no-store", and revalidate = 0 to disable caching and ensure fresh uploads.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

The changes introduce straightforward file upload handling with clear separation of concerns: a handler module containing business logic and a route module wiring HTTP methods. The code follows standard Next.js patterns without complex control flow or intricate dependencies. Two distinct files with minimal interdependency and no refactoring or conditional logic.

Poem

📤 Files fly to Arweave's distributed shore,
Each upload blessed with CORS and more,
No cache to linger, dynamic and free,
Gateway URLs returned with glee! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning uploadFileHandler violates SRP with multiple responsibilities. Two review comments unaddressed: missing auth validation and no file size checks. Add validateAuthContext() in route.ts. Add MAX_UPLOAD_BYTES check in uploadFileHandler before buffering. Extract form parsing into separate utility functions.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate/upload-group2

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files

Confidence score: 3/5

  • There is a concrete security/privacy risk in lib/arweave/uploadFileHandler.ts: 500 responses currently expose raw exception messages, which can leak internal implementation details to clients.
  • Because this is severity 7/10 with high confidence, the change carries some merge risk until the handler returns a generic error string for server failures.
  • Pay close attention to lib/arweave/uploadFileHandler.ts - replace raw error text in 500 responses with a sanitized generic message.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/arweave/uploadFileHandler.ts">

<violation number="1" location="lib/arweave/uploadFileHandler.ts:50">
P1: Do not expose raw exception messages in 500 responses; return a generic error string instead.

(Based on your team's feedback about preventing internal error detail leaks in API 500 responses.) [FEEDBACK_USED]</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client
    participant Route as API Route (app/api/upload)
    participant Handler as uploadFileHandler
    participant Utils as Arweave Utilities
    participant Arweave as Arweave Network

    Note over Client, Arweave: CORS Preflight
    Client->>Route: OPTIONS /api/upload
    Route-->>Client: 200 OK (CORS Headers)

    Note over Client, Arweave: File Upload Flow
    Client->>Route: POST /api/upload (multipart/form-data)
    Route->>Handler: NEW: uploadFileHandler(request)
    
    Handler->>Handler: Parse FormData (field: "file")

    alt No file provided
        Handler-->>Route: 500 Internal Server Error
        Route-->>Client: { success: false, error: "No file provided" }
    else File exists
        Handler->>Handler: Convert to Buffer & Extract Metadata
        Handler->>Utils: uploadToArweave(buffer, contentType)
        Utils->>Arweave: Dispatch Transaction
        Arweave-->>Utils: Transaction ID (tx_id)
        Utils-->>Handler: { id: tx_id }

        alt Upload Success
            Handler->>Utils: getFetchableUrl(ar://tx_id)
            Utils-->>Handler: https://arweave.net/tx_id
            Handler-->>Route: 200 OK + JSON Payload
            Route-->>Client: { success: true, fileName, fileType, fileSize, url }
        else Upload Failure
            Utils-->>Handler: Throw Error
            Handler-->>Route: 500 Internal Server Error
            Route-->>Client: { success: false, error: "message" }
        end
    end
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

return NextResponse.json(
{
success: false,
error: error instanceof Error ? error.message : "Unknown error",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Do not expose raw exception messages in 500 responses; return a generic error string instead.

(Based on your team's feedback about preventing internal error detail leaks in API 500 responses.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/arweave/uploadFileHandler.ts, line 50:

<comment>Do not expose raw exception messages in 500 responses; return a generic error string instead.

(Based on your team's feedback about preventing internal error detail leaks in API 500 responses.) </comment>

<file context>
@@ -0,0 +1,58 @@
+    return NextResponse.json(
+      {
+        success: false,
+        error: error instanceof Error ? error.message : "Unknown error",
+      },
+      {
</file context>

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
lib/arweave/uploadFileHandler.ts (2)

18-23: ⚡ Quick win

Use a Zod-based validate function for upload input shape.

Manual checks work, but this route should use a validate function with Zod for consistent parsing/error behavior across API endpoints.

As per coding guidelines, "All API endpoints should use a validate function for input parsing using Zod for schema validation".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/arweave/uploadFileHandler.ts` around lines 18 - 23, Replace the manual
file presence/type checks in uploadFileHandler with a Zod-based validate call:
define a Zod schema (e.g., uploadInputSchema) that validates the expected upload
shape (file presence, correct File-like type and any allowed mime/size rules),
extract fields from request.formData() into a plain object, call
validate(uploadInputSchema, parsedInput) and use the returned value (instead of
manual checks) inside uploadFileHandler; remove the throw new Error("No file
provided") branch and rely on validate to produce consistent validation errors
across endpoints.

16-58: ⚡ Quick win

Split uploadFileHandler into smaller focused helpers.

This function is doing parsing, validation, upload orchestration, success mapping, and error mapping in one block and exceeds the 20-line guideline. Please extract at least file parsing/validation and response construction helpers.

As per coding guidelines, "Flag functions longer than 20 lines" and "Keep functions small and focused".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/arweave/uploadFileHandler.ts` around lines 16 - 58, Split
uploadFileHandler into smaller helpers: extract request parsing/validation into
a parseAndValidateFile helper that accepts the NextRequest, calls
request.formData(), validates the "file" field, converts to Buffer and returns
{fileBuffer, fileSize, fileType, fileName} (use the same Buffer/from(await
file.arrayBuffer()) logic and the same type/name fallbacks); extract response
mapping into a buildJsonResponse helper that wraps NextResponse.json with
getCorsHeaders and accepts payload + status to produce the final response; then
refactor uploadFileHandler to call parseAndValidateFile, call
uploadToArweave(fileBuffer, fileType), map the transaction into the success
payload using getFetchableUrl(`ar://${transaction.id}`) and file metadata, and
return buildJsonResponse for both success and error paths so uploadFileHandler
stays focused and under ~20 lines while still using getCorsHeaders,
uploadToArweave, and getFetchableUrl.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/upload/route.ts`:
- Around line 27-29: The POST handler currently delegates straight to
uploadFileHandler without authentication; call await
validateAuthContext(request) at the start of the exported POST function
(importing it if necessary), handle failures by returning an appropriate 401/403
response, and only then call uploadFileHandler (optionally passing the returned
auth context) so uploads require valid x-api-key or Authorization: Bearer
credentials; keep the function signature (POST(request: NextRequest)) and
preserve uploadFileHandler usage.

In `@lib/arweave/uploadFileHandler.ts`:
- Around line 25-30: Before calling file.arrayBuffer() and Buffer.from(...) in
uploadFileHandler (which currently creates fileBuffer/fileSize and then calls
uploadToArweave), check the incoming file.size against a configured
MAX_FILE_SIZE constant and reject/throw an error (or return an HTTP 413)
immediately if file.size exceeds the limit; only if file.size is within bounds
proceed to await file.arrayBuffer(), create fileBuffer, set fileType/fileName,
and call uploadToArweave. Ensure the MAX_FILE_SIZE constant is documented and
used where the size check happens to prevent buffering oversized uploads.

---

Nitpick comments:
In `@lib/arweave/uploadFileHandler.ts`:
- Around line 18-23: Replace the manual file presence/type checks in
uploadFileHandler with a Zod-based validate call: define a Zod schema (e.g.,
uploadInputSchema) that validates the expected upload shape (file presence,
correct File-like type and any allowed mime/size rules), extract fields from
request.formData() into a plain object, call validate(uploadInputSchema,
parsedInput) and use the returned value (instead of manual checks) inside
uploadFileHandler; remove the throw new Error("No file provided") branch and
rely on validate to produce consistent validation errors across endpoints.
- Around line 16-58: Split uploadFileHandler into smaller helpers: extract
request parsing/validation into a parseAndValidateFile helper that accepts the
NextRequest, calls request.formData(), validates the "file" field, converts to
Buffer and returns {fileBuffer, fileSize, fileType, fileName} (use the same
Buffer/from(await file.arrayBuffer()) logic and the same type/name fallbacks);
extract response mapping into a buildJsonResponse helper that wraps
NextResponse.json with getCorsHeaders and accepts payload + status to produce
the final response; then refactor uploadFileHandler to call
parseAndValidateFile, call uploadToArweave(fileBuffer, fileType), map the
transaction into the success payload using
getFetchableUrl(`ar://${transaction.id}`) and file metadata, and return
buildJsonResponse for both success and error paths so uploadFileHandler stays
focused and under ~20 lines while still using getCorsHeaders, uploadToArweave,
and getFetchableUrl.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 42cfe620-47e1-49aa-b682-c3712b46c31c

📥 Commits

Reviewing files that changed from the base of the PR and between 2c83920 and 812717f.

⛔ Files ignored due to path filters (1)
  • lib/arweave/__tests__/uploadFileHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (2)
  • app/api/upload/route.ts
  • lib/arweave/uploadFileHandler.ts

Comment thread app/api/upload/route.ts
Comment on lines +27 to +29
export async function POST(request: NextRequest) {
return uploadFileHandler(request);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add auth validation before delegating to the upload handler.

POST currently accepts uploads without calling validateAuthContext(). That leaves the endpoint open for unauthenticated writes.

Suggested fix
 import { NextRequest, NextResponse } from "next/server";
 import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
 import { uploadFileHandler } from "@/lib/arweave/uploadFileHandler";
+import { validateAuthContext } from "@/lib/auth/validateAuthContext";

 ...
 export async function POST(request: NextRequest) {
+  const auth = await validateAuthContext(request);
+  if (!auth.valid) {
+    return NextResponse.json(
+      { success: false, error: "Unauthorized" },
+      { status: 401, headers: getCorsHeaders() },
+    );
+  }
   return uploadFileHandler(request);
 }

As per coding guidelines, "Always use validateAuthContext() for authentication in API routes; it supports both x-api-key header and Authorization: Bearer token authentication".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function POST(request: NextRequest) {
return uploadFileHandler(request);
}
export async function POST(request: NextRequest) {
const auth = await validateAuthContext(request);
if (!auth.valid) {
return NextResponse.json(
{ success: false, error: "Unauthorized" },
{ status: 401, headers: getCorsHeaders() },
);
}
return uploadFileHandler(request);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/upload/route.ts` around lines 27 - 29, The POST handler currently
delegates straight to uploadFileHandler without authentication; call await
validateAuthContext(request) at the start of the exported POST function
(importing it if necessary), handle failures by returning an appropriate 401/403
response, and only then call uploadFileHandler (optionally passing the returned
auth context) so uploads require valid x-api-key or Authorization: Bearer
credentials; keep the function signature (POST(request: NextRequest)) and
preserve uploadFileHandler usage.

Comment on lines +25 to +30
const fileBuffer = Buffer.from(await file.arrayBuffer());
const fileSize = fileBuffer.length;
const fileType = file.type || "application/octet-stream";
const fileName = file.name;

const transaction = await uploadToArweave(fileBuffer, fileType);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce a maximum file size before buffering bytes.

arrayBuffer() + Buffer.from(...) loads the full payload into memory with no upper bound. Add a strict file.size cap and reject oversized uploads early to avoid memory exhaustion.

Suggested fix
+const MAX_UPLOAD_BYTES = 25 * 1024 * 1024; // 25MB
...
-const fileBuffer = Buffer.from(await file.arrayBuffer());
+if (file.size > MAX_UPLOAD_BYTES) {
+  throw new Error("File too large");
+}
+const fileBuffer = Buffer.from(await file.arrayBuffer());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fileBuffer = Buffer.from(await file.arrayBuffer());
const fileSize = fileBuffer.length;
const fileType = file.type || "application/octet-stream";
const fileName = file.name;
const transaction = await uploadToArweave(fileBuffer, fileType);
const MAX_UPLOAD_BYTES = 25 * 1024 * 1024; // 25MB
// ... (other code)
if (file.size > MAX_UPLOAD_BYTES) {
throw new Error("File too large");
}
const fileBuffer = Buffer.from(await file.arrayBuffer());
const fileSize = fileBuffer.length;
const fileType = file.type || "application/octet-stream";
const fileName = file.name;
const transaction = await uploadToArweave(fileBuffer, fileType);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/arweave/uploadFileHandler.ts` around lines 25 - 30, Before calling
file.arrayBuffer() and Buffer.from(...) in uploadFileHandler (which currently
creates fileBuffer/fileSize and then calls uploadToArweave), check the incoming
file.size against a configured MAX_FILE_SIZE constant and reject/throw an error
(or return an HTTP 413) immediately if file.size exceeds the limit; only if
file.size is within bounds proceed to await file.arrayBuffer(), create
fileBuffer, set fileType/fileName, and call uploadToArweave. Ensure the
MAX_FILE_SIZE constant is documented and used where the size check happens to
prevent buffering oversized uploads.

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