Skip to content

feat: add github_repo to PATCH /api/sandboxes#208

Merged
sweetmantech merged 11 commits intotestfrom
sweetmantech/myc-4150-api-patch-apisandboxes-github_repo
Feb 5, 2026
Merged

feat: add github_repo to PATCH /api/sandboxes#208
sweetmantech merged 11 commits intotestfrom
sweetmantech/myc-4150-api-patch-apisandboxes-github_repo

Conversation

@sweetmantech
Copy link
Contributor

@sweetmantech sweetmantech commented Feb 5, 2026

Summary

  • Added optional github_repo (URL string) to the PATCH /api/sandboxes Zod schema and SnapshotPatchBody type
  • Updated upsertAccountSnapshot to conditionally include github_repo in the upsert data
  • Updated updateSnapshotPatchHandler to pass githubRepo through to the upsert function
  • Added tests for validation pass-through and handler forwarding

Test plan

  • validateSnapshotPatchBody test: github_repo is passed through in validated body
  • updateSnapshotPatchHandler test: githubRepo is forwarded to upsertAccountSnapshot
  • All 14 existing + new tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Snapshot updates now support identifying snapshots by GitHub repository reference in addition to snapshot ID
    • API response improved to return actual snapshot data instead of wrapper object

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Contributor

vercel bot commented Feb 5, 2026

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

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Feb 5, 2026 9:35pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

This PR refactors the snapshot patch endpoint to support optional snapshotId and githubRepo parameters with conditional payload construction. The handler now fetches the current account snapshot when neither parameter is provided, and the upsert function signature changes to accept a generic table insert type rather than explicit parameters.

Changes

Cohort / File(s) Summary
Snapshot Patch Validation
lib/sandbox/validateSnapshotPatchBody.ts
Made snapshotId optional; added github_repo as an optional URL field in the schema and type definitions. Validation now conditionally includes both fields in the return object only when present.
Snapshot Patch Handler
lib/sandbox/updateSnapshotPatchHandler.ts
Added early exit that fetches and returns the current account snapshot when neither snapshotId nor githubRepo is provided. Refactored payload construction to conditionally include snapshot_id and expires_at (if snapshotId provided) or github_repo (if githubRepo provided). Changed response to return raw database result instead of a wrapper object. Updated error response format to a standardized shape.
Account Snapshot Upsert
lib/supabase/account_snapshots/upsertAccountSnapshot.ts
Updated function signature to accept a single params: TablesInsert<"account_snapshots"> parameter instead of explicit destructured parameters. Removed internal expiration logic; now directly delegates to supabase.upsert() with the provided params. Simplified control flow by eliminating intermediate payload construction.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as updateSnapshotPatchHandler
    participant Validator as validateSnapshotPatchBody
    participant Selector as selectAccountSnapshots
    participant Upsert as upsertAccountSnapshot
    participant Database

    Client->>Handler: PATCH with optional snapshotId/githubRepo
    Handler->>Validator: Validate request body
    Validator->>Validator: Parse & validate schema<br/>(snapshotId?, githubRepo?)
    Validator-->>Handler: Return validated params
    
    alt snapshotId or githubRepo provided
        Handler->>Upsert: Call with conditional payload<br/>(account_id + optional fields)
        Upsert->>Database: upsert(params, onConflict: "account_id")
        Database-->>Upsert: Updated row
        Upsert-->>Handler: Result
    else Neither provided
        Handler->>Selector: selectAccountSnapshots(account_id)
        Selector->>Database: Query current snapshot
        Database-->>Selector: Current snapshot row
        Selector-->>Handler: First result
    end
    
    Handler-->>Client: Return snapshot data (200 + CORS)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #198: Directly extends the PATCH /api/sandboxes/snapshot flow by modifying the same handler, validator, and upsert function with overlapping logic changes.
  • #206: Both PRs modify the account_snapshots table interactions and introduce selectAccountSnapshots usage within the snapshot update pipeline.
  • #200: Modifies the same validateSnapshotPatchBody.ts file, changing the snapshot patch schema and validation return shape.

Poem

🎲 Fields once rigid, now dance with grace,
Optional whispers take their place,
Snapshot or repo—pick your own,
Flexible logic, cleanly shown! ✨

🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Solid & Clean Code ✅ Passed Code exhibits excellent SOLID principle adherence with clear separation of concerns, proper abstraction, comprehensive test coverage (8 test cases), appropriate function sizing (8-40 lines), reasonable nesting depth (3-4 levels), full type safety, and professional error handling.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sweetmantech/myc-4150-api-patch-apisandboxes-github_repo

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

@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

🤖 Fix all issues with AI agents
In `@lib/sandbox/validateSnapshotPatchBody.ts`:
- Around line 8-12: The github_repo field in snapshotPatchBodySchema currently
uses z.string().url() which rejects SSH shorthand remotes like
"git@github.com:org/repo.git"; update snapshotPatchBodySchema to accept both
URL-style remotes and the SSH shorthand by replacing github_repo:
z.string().url(...).optional() with a union or custom validator (e.g.,
z.union([z.string().url("..."),
z.string().regex(/^[\w.-]+@[\w.-]+:[\w./-]+\.git$/, "github_repo must be a valid
URL or SSH shorthand")]) .optional()) or use z.string().refine(...) to validate
either format, keeping the field optional and preserving error messages.
- Around line 14-21: Replace the manual export type SnapshotPatchBody with a Zod
schema that transforms incoming snake_case body keys to camelCase and lets
z.infer produce the exported type; create a schema (e.g., snapshotPatchSchema)
that accepts { snapshot_id: string, github_repo?: string } (do not accept
account_id in the request body) and use .transform(obj => ({ snapshotId:
obj.snapshot_id, githubRepo: obj.github_repo })) so that export type
SnapshotPatchBody = z.infer<typeof snapshotPatchSchema>; ensure the
validation/parse call uses snapshotPatchSchema.parse/parseAsync and derive
accountId from auth context elsewhere.

…ams type

Removes duplicate UpsertAccountSnapshotParams interface in favor of the
Supabase-generated TablesInsert type. Callers now pass snake_case fields
directly, eliminating the camelCase-to-snake_case mapping layer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
As a PATCH endpoint, all fields should be optional so callers can update
github_repo without re-sending snapshotId, or vice versa.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
sweetmantech and others added 2 commits February 5, 2026 14:06
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
expires_at represents the snapshot expiration, so it should only be
set/refreshed when a snapshot_id is being updated.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Returns 200 with { success: true } immediately when neither snapshotId
nor github_repo is provided, avoiding an unnecessary Supabase call that
would fail due to required columns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
UPSERT requires all NOT NULL columns (snapshot_id, expires_at), so
github_repo-only requests failed with a 400. Now the handler uses
updateAccountSnapshot (plain UPDATE) when only github_repo is provided,
and upsertAccountSnapshot when snapshot_id is included.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Returns the complete account_snapshots record (account_id, snapshot_id,
expires_at, github_repo, created_at) instead of just { success, snapshotId }.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@lib/sandbox/updateSnapshotPatchHandler.ts`:
- Line 51: The handler returns inconsistent response shapes: the early return in
updateSnapshotPatchHandler (returns { success: true } at the early-exit branch)
differs from the main path which currently returns result.data (full
account_snapshots row); pick one consistent shape (prefer returning the full
resource per REST guidance) and update the handler so both branches return the
same structure (e.g., { success: true, snapshot: result.data } or simply
result.data consistently), adjust the success/snapshotId fields accordingly, and
ensure the function(s) handling the DB upsert/patch (references: result,
result.data, and the early-exit block that returns { success: true }) produce
and return that unified response format across all code paths.
🧹 Nitpick comments (2)
lib/sandbox/updateSnapshotPatchHandler.ts (2)

25-30: Early exit pattern is clean, though the response payload could be more informative.

The guard clause properly handles the no-op case. Returning { success: true } when no fields were provided is semantically accurate but could be slightly misleading to API consumers. Consider returning a more descriptive message or the current state of the record.

That said, this is a reasonable design choice for idempotency. The Single Responsibility principle is well-served here—the handler validates, determines the appropriate action, and responds accordingly.


37-37: Extract magic number to a named constant for clarity.

The expression 365 * 24 * 60 * 60 * 1000 represents one year in milliseconds but requires mental parsing. Extracting this to a well-named constant improves readability and maintainability (KISS principle).

♻️ Suggested refactor
+const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1000;
+
 export async function updateSnapshotPatchHandler(request: NextRequest): Promise<NextResponse> {

Then use it at line 37:

-          expires_at: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(),
+          expires_at: new Date(Date.now() + ONE_YEAR_MS).toISOString(),

When neither snapshotId nor github_repo is provided, fetches and returns
the existing account_snapshots row instead of { success: true }, so the
response shape is consistent regardless of input.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Now that snapshot_id is nullable in Supabase, the upsert function works
for all cases. Removes the separate updateAccountSnapshot function.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sweetmantech sweetmantech merged commit e057d9e into test Feb 5, 2026
2 of 3 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

Comments