Skip to content

Merge test into main#551

Merged
sweetmantech merged 3 commits into
mainfrom
test
May 11, 2026
Merged

Merge test into main#551
sweetmantech merged 3 commits into
mainfrom
test

Conversation

@sweetmantech
Copy link
Copy Markdown
Contributor

@sweetmantech sweetmantech commented May 11, 2026

Batched promotion of test → main.

Included PRs

🤖 Generated with Claude Code


Summary by cubic

Add POST /api/sandboxes/staged-file for Vercel Blob client-upload token handshakes and upload-completed callbacks, replacing /api/sandbox/upload. Includes CORS preflight and auth aligned with other API routes.

  • New Features

    • New route with OPTIONS preflight using getCorsHeaders().
    • Handshake auth via validateAuthContext using Bearer Authorization for blob.generate-client-token; skips auth for blob.upload-completed.
    • Enforces 100MB max size and random filename suffix via @vercel/blob/client.handleUpload.
    • Generic 500 on unexpected errors with server-side logging; all responses include CORS headers.
  • Migration

    • Point client handleUploadUrl to /api/sandboxes/staged-file.
    • Send a Bearer Authorization header on the handshake; no header is required for the upload-completed callback.

Written for commit 329c589. Summary will update on new commits.

sweetmantech and others added 3 commits May 11, 2026 16:16
Brand-new accounts used to receive exactly 25 credits regardless of
plan, because insertCreditsUsage had a hard-coded DEFAULT_CREDITS=25
fallback that both call sites (agent signup + account create) relied
on. The new credits-balance endpoint exposes this as "25 / 333 used 308"
the moment an account is provisioned.

Fix at the API layer, reusing what PR #547 already gave us:

- New `lib/credits/getAccountSubscriptionState.ts` — single source of
  truth for "is this account pro?". Extracts the parallel
  getActiveSubscriptionDetails + getOrgSubscription lookup that
  checkAndResetCredits already did inline.

- `checkAndResetCredits` now delegates to that helper. Behavior
  unchanged; 7 lines collapse to 2.

- New `lib/credits/initializeAccountCredits.ts` — plan-aware seeder.
  Looks up the subscription state via the new helper, then calls
  insertCreditsUsage with PRO_CREDITS=1000 or DEFAULT_CREDITS=333
  (the constants we already exported in PR #547).

- Both call sites swap insertCreditsUsage(id) for
  initializeAccountCredits(id):
  - lib/agents/createAccountWithEmail.ts
  - lib/accounts/createAccountHandler.ts

- Remove the booby-trap default from insertCreditsUsage. The
  remainingCredits parameter is now required, so any new caller that
  forgets to pick a plan-aware value gets a type error.

TDD: 4 new tests for getAccountSubscriptionState, 3 for
initializeAccountCredits, full checkAndResetCredits suite migrated to
mock the new helper instead of three Stripe functions. 234 tests
green across 39 files. lint clean. No typecheck regressions in
changed files (pre-existing AI-SDK type drift in getCreditUsage.test
and handleChatCredits.test is unchanged).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…files (#541)

* feat(api): add sandbox staged-files token handshake endpoint

Adds POST /api/sandboxes/staged-files — the Vercel Blob client-upload
handshake half of the sandbox upload flow. Mirrors chat's existing
/api/sandbox/upload handler so the chat side can flip its
handleUploadUrl to api and delete the local route.

Auth follows the minimum-port shape: validates clientPayload.token
because @vercel/blob/client.upload() does not allow setting an
Authorization header on the handshake POST. The downstream commit
(POST /api/sandboxes/files) re-authenticates with a real Bearer token.

OPTIONS preflight is wired with getCorsHeaders() so cross-origin
calls from chat.recoupable.com work the same way the existing
/api/sandboxes/files route does.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(api): rename /api/sandboxes/staged-files to /api/sandboxes/stage-files

Verb form matches /api/sandboxes/files (commit) and /api/sandboxes/file (get).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(api): rename /api/sandboxes/stage-files to /api/sandboxes/staged-file

Noun-shaped resource matching /api/sandboxes/file (singular getter)
and /api/sandboxes/files (collection commit). Each call generates a
token to stage one file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(api): authenticate staged-file via Authorization header

Drops clientPayload.token in favor of validateAuthContext() so the
endpoint matches the auth surface of every other api route. The
chat-side upload() now forwards an Authorization Bearer header via
the library's headers option (which I missed earlier — that field
is documented for exactly this use case).

Branches on body.type to skip auth on the upload-completed callback;
that POST comes from Vercel Blob's backend without the user's auth
header, and handleUpload() verifies its signature internally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(api): hide raw exception details on staged-file 500s

Log unexpected handleUpload errors server-side via console.error and
return a generic 500 — matches the createSandboxHandler pattern and
avoids leaking internal context in error responses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(api): trim JSDoc on staged-file handler and route

Drop the multi-paragraph commentary; keep a one-liner on the handler
explaining the asymmetric auth, and minimal JSDoc on the route per
project convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(api): satisfy jsdoc/require-returns + require-param on staged-file route

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 11, 2026

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

Project Deployment Actions Updated (UTC)
api Ready Ready Preview May 11, 2026 11:07pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 45 minutes and 33 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f8fdaa19-a476-43bb-815a-c7233fa6ee77

📥 Commits

Reviewing files that changed from the base of the PR and between 710f789 and 329c589.

⛔ Files ignored due to path filters (1)
  • lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts is excluded by !**/*.test.*, !**/__tests__/** and included by lib/**
📒 Files selected for processing (2)
  • app/api/sandboxes/staged-file/route.ts
  • lib/sandbox/postSandboxesUploadTokensHandler.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test

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.

4 issues found across 3 files

Confidence score: 2/5

  • There is a high user-impact risk in app/api/sandboxes/staged-file/route.ts: the singular staged-file path appears inconsistent with the documented plural migration target (staged-files), which could break endpoint routing.
  • lib/sandbox/postSandboxesUploadTokensHandler.ts currently treats malformed JSON as a 500 path, so client-side bad input is misclassified as server failure instead of returning a 400 before handleUpload, which can cause incorrect API behavior.
  • The remaining issues are lower severity (standardized 500 message and test-file length), but combined with the routing and request-validation concerns, this is not yet a low-risk merge.
  • Pay close attention to app/api/sandboxes/staged-file/route.ts and lib/sandbox/postSandboxesUploadTokensHandler.ts - route naming and JSON error handling are the main functional risks.
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/sandbox/postSandboxesUploadTokensHandler.ts">

<violation number="1" location="lib/sandbox/postSandboxesUploadTokensHandler.ts:14">
P2: Invalid/malformed request bodies are handled as 500s because JSON parsing isn’t validated separately. Return a 400 for bad JSON before calling `handleUpload`.</violation>

<violation number="2" location="lib/sandbox/postSandboxesUploadTokensHandler.ts:37">
P2: Use the standardized 500 error message `"Internal server error"` in API responses.

(Based on your team's feedback about standardizing 500 responses to avoid leaking internal details.) [FEEDBACK_USED]</violation>
</file>

<file name="app/api/sandboxes/staged-file/route.ts">

<violation number="1" location="app/api/sandboxes/staged-file/route.ts:21">
P1: Route path appears to be singular (`staged-file`) while the migration target is documented as plural (`staged-files`), which can break the intended endpoint.</violation>
</file>

<file name="lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts">

<violation number="1" location="lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts:1">
P3: Custom agent: **Enforce Clear Code Style and Maintainability Practices**

This new test file exceeds the repository’s 100-line maintainability limit.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as Client Browser
    participant Next as Next.js API Route
    participant Handler as postSandboxesUploadTokensHandler
    participant Auth as validateAuthContext
    participant Blob as @vercel/blob/client (handleUpload)
    participant BlobSvc as Vercel Blob Service

    Note over Client,BlobSvc: NEW: Staged-file upload flow

    Client->>Next: OPTIONS /api/sandboxes/staged-file
    Next-->>Client: 200 CORS preflight (getCorsHeaders)

    alt Handshake (blob.generate-client-token)
        Client->>Next: POST /api/sandboxes/staged-file<br/>{type:"blob.generate-client-token", ...}<br/>Authorization: Bearer <token>
        Next->>Handler: postSandboxesUploadTokensHandler(request)
        Handler->>Handler: Parse body as HandleUploadBody
        Handler->>Auth: validateAuthContext(request)
        alt Auth succeeds
            Auth-->>Handler: { accountId, orgId, authToken }
            Handler->>Blob: handleUpload({ body, request,<br/>onBeforeGenerateToken,<br/>onUploadCompleted })
            Blob->>BlobSvc: Generate client upload token
            Note over Blob: onBeforeGenerateToken returns<br/>constraints: 100MB max, random suffix
            BlobSvc-->>Blob: clientToken
            Blob-->>Handler: {type:"blob.generate-client-token", clientToken}
            Handler->>Handler: NextResponse.json + getCorsHeaders()
            Handler-->>Next: 200 JSON with clientToken
            Next-->>Client: 200 + CORS headers
        else Auth fails
            Auth-->>Handler: NextResponse (401)
            Handler-->>Next: 401 Unauthorized
            Next-->>Client: 401 + CORS headers
        end
    else Upload completed callback (blob.upload-completed)
        Client->>Next: POST /api/sandboxes/staged-file<br/>{type:"blob.upload-completed", ...}
        Next->>Handler: postSandboxesUploadTokensHandler(request)
        Handler->>Handler: body.type !== "blob.generate-client-token"
        Note over Handler: Auth SKIPPED — callback is signature-verified<br/>by handleUpload() internally
        Handler->>Blob: handleUpload({ body, request,<br/>onBeforeGenerateToken,<br/>onUploadCompleted })
        Blob-->>Handler: {type:"blob.upload-completed"}
        Handler->>Handler: NextResponse.json + getCorsHeaders()
        Handler-->>Next: 200 + CORS headers
        Next-->>Client: 200 + CORS headers
    end

    Note over Handler,BlobSvc: ERROR HANDLING
    alt handleUpload throws
        Handler->>Handler: console.error(...)
        Handler->>Handler: NextResponse.json({status:"error",<br/>error:"Failed to issue upload token"}, 500)
        Handler-->>Next: 500 + CORS headers
        Next-->>Client: 500 + CORS headers
    end
Loading

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

* @param request - The request object.
* @returns A NextResponse with the handshake result or error.
*/
export async function POST(request: NextRequest): Promise<Response> {
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: Route path appears to be singular (staged-file) while the migration target is documented as plural (staged-files), which can break the intended endpoint.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/api/sandboxes/staged-file/route.ts, line 21:

<comment>Route path appears to be singular (`staged-file`) while the migration target is documented as plural (`staged-files`), which can break the intended endpoint.</comment>

<file context>
@@ -0,0 +1,23 @@
+ * @param request - The request object.
+ * @returns A NextResponse with the handshake result or error.
+ */
+export async function POST(request: NextRequest): Promise<Response> {
+  return postSandboxesUploadTokensHandler(request);
+}
</file context>

} catch (error) {
console.error("[postSandboxesUploadTokensHandler] handleUpload failed:", error);
return NextResponse.json(
{ status: "error", error: "Failed to issue upload token" },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Use the standardized 500 error message "Internal server error" in API responses.

(Based on your team's feedback about standardizing 500 responses to avoid leaking internal details.)

View Feedback

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

<comment>Use the standardized 500 error message `"Internal server error"` in API responses.

(Based on your team's feedback about standardizing 500 responses to avoid leaking internal details.) </comment>

<file context>
@@ -0,0 +1,41 @@
+  } catch (error) {
+    console.error("[postSandboxesUploadTokensHandler] handleUpload failed:", error);
+    return NextResponse.json(
+      { status: "error", error: "Failed to issue upload token" },
+      { status: 500, headers: getCorsHeaders() },
+    );
</file context>
Suggested change
{ status: "error", error: "Failed to issue upload token" },
{ status: "error", error: "Internal server error" },

request: NextRequest,
): Promise<NextResponse> {
try {
const body = (await request.json()) as HandleUploadBody;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Invalid/malformed request bodies are handled as 500s because JSON parsing isn’t validated separately. Return a 400 for bad JSON before calling handleUpload.

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

<comment>Invalid/malformed request bodies are handled as 500s because JSON parsing isn’t validated separately. Return a 400 for bad JSON before calling `handleUpload`.</comment>

<file context>
@@ -0,0 +1,41 @@
+  request: NextRequest,
+): Promise<NextResponse> {
+  try {
+    const body = (await request.json()) as HandleUploadBody;
+
+    if (body.type === "blob.generate-client-token") {
</file context>

@@ -0,0 +1,124 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Custom agent: Enforce Clear Code Style and Maintainability Practices

This new test file exceeds the repository’s 100-line maintainability limit.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts, line 1:

<comment>This new test file exceeds the repository’s 100-line maintainability limit.</comment>

<file context>
@@ -0,0 +1,124 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
</file context>

@sweetmantech sweetmantech merged commit d14d59f into main May 11, 2026
7 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.

2 participants