Skip to content

feat: add generateUploadUrl to api#4601

Merged
chronark merged 9 commits intomainfrom
generate-upload-url-api
Jan 14, 2026
Merged

feat: add generateUploadUrl to api#4601
chronark merged 9 commits intomainfrom
generate-upload-url-api

Conversation

@ogzhanolguncu
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu commented Jan 13, 2026

What does this PR do?

This PR creates an endpoint for generateUploadUrl process to decouple CLI and CTRL. That way CLI can talk to API instead of CTRL directly.

Note:

  • Currently /v2/deploy.generateUploadUrl doesn't do any permission check for deployment related stuff. Will be added soon.
  • We now use HandleError in svc/api/internal/ctrlclient/errors.go to convert Connect RPC errors to fault errors. This will also be used in getDeployment endpoint. We had this block in createDeployment, its using this helper instead to prevent duplication.
  • Added missing projectId check to ctrl/services/deployment/create_deployment.go. And DB look up to verify project exists before we actually generate the upload URL ctrl/services/build/backend/[docker|depot]/generate_upload_url.go

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Go to dashboard
  • Open, http://localhost:3000/[workspace]/settings/root-keys
  • Create a root key and copy our rootKey
  • Update your DB to see projects tab
  • Open, http://localhost:3000/[workspace]/projects
  • Create a project and copy your project_id
  • Use your root key for Authorization: Bearer xxx
curl -X POST http://localhost:7070/v2/deploy.generateUploadUrl \
    -H "Authorization: Bearer xxx" \
    -H "Content-Type: application/json" \
    -d '{"projectId": "proj_5TTe2bveB1wHMVa4"}'

Expected: 200 OK with context, expiresIn and uploadUrl to call in the CLI

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Ran make fmt on /go directory
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

@vercel
Copy link

vercel bot commented Jan 13, 2026

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

Project Deployment Review Updated (UTC)
dashboard Ready Ready Preview, Comment Jan 14, 2026 10:20am
1 Skipped Deployment
Project Deployment Review Updated (UTC)
engineering Ignored Ignored Preview Jan 14, 2026 10:20am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Adds POST /v2/deploy.generateUploadUrl (OpenAPI, types, route, handler, tests); wires a CTRL BuildServiceClient through runtime and routes; centralizes Connect→fault error translation in ctrlclient.HandleError; updates create-deployment to use that helper; and adds CTRL backend checks ensuring projects exist before generating upload URLs.

Changes

Cohort / File(s) Summary
Error handling utility
\svc/api/internal/ctrlclient/BUILD.bazel`, `svc/api/internal/ctrlclient/errors.go``
New Bazel go_library and exported HandleError(err, context) to translate Connect RPC errors into standardized fault errors and user-facing messages.
OpenAPI schemas & generated types
\svc/api/openapi/gen.go`, `svc/api/openapi/openapi-generated.yaml`, `svc/api/openapi/openapi-split.yaml`, `svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/*``
Add request/response schemas and generated Go types for generateUploadUrl (request: projectId; response data: uploadUrl, context); update createDeployment operationId.
Route registration & services wiring
\svc/api/routes/BUILD.bazel`, `svc/api/routes/register.go`, `svc/api/routes/services.go`, `svc/api/run.go``
Register new route, add CtrlBuildClient to Services, initialize BuildServiceClient when CTRL URL configured, and pass it into routes.
Generate-upload-url handler & build files
\svc/api/routes/v2_deploy_generate_upload_url/handler.go`, `svc/api/routes/v2_deploy_generate_upload_url/BUILD.bazel``
New Handler for POST /v2/deploy.generateUploadUrl: binds request, calls BuildServiceClient.GenerateUploadURL, uses ctrlclient.HandleError, and returns upload metadata; Bazel targets added.
Route tests
\svc/api/routes/v2_deploy_generate_upload_url/200_test.go`, `400_test.go`, `401_test.go`, `404_test.go`**`
Adds tests covering success, validation errors, unauthorized, and not-found scenarios for the new route.
Create-deployment handler refactor
\svc/api/routes/v2_deploy_create_deployment/handler.go`, `svc/api/routes/v2_deploy_create_deployment/BUILD.bazel`, `svc/api/routes/v2_deploy_create_deployment/404_test.go`**`
Replace handler-local Connect error translation with ctrlclient.HandleError; update Bazel deps to //svc/api/internal/ctrlclient; adjust test expected error detail.
CTRL backend validation
\svc/ctrl/services/build/backend/depot/generate_upload_url.go`, `svc/ctrl/services/build/backend/docker/generate_upload_url.go`**`
Add DB pre-check (FindProjectById) to return NotFound if project absent before generating build context and presigned upload URL.
Deployment RPC validation
\svc/ctrl/services/deployment/create_deployment.go`**`
Add early validation returning InvalidArgument when project_id is empty.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API_Router as API Routes
    participant Handler
    participant CtrlClient
    participant Database
    participant S3

    Client->>API_Router: POST /v2/deploy.generateUploadUrl
    API_Router->>Handler: invoke Handler
    Handler->>Handler: bind & validate projectId
    Handler->>CtrlClient: GenerateUploadURL(request)
    CtrlClient->>Database: FindProjectById(projectId)
    alt project not found
        Database-->>CtrlClient: NotFound error
        CtrlClient-->>Handler: NotFound error
        Handler->>Handler: ctrlclient.HandleError(err,"generate upload url")
        Handler-->>Client: 404 "Project not found."
    else project exists
        Database-->>CtrlClient: Project found
        CtrlClient->>S3: create presigned upload URL & context
        S3-->>CtrlClient: uploadUrl, context
        CtrlClient-->>Handler: upload metadata
        Handler-->>Client: 200 {uploadUrl, context}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add generateUploadUrl to api' accurately and concisely summarizes the main change: adding a new API endpoint for generateUploadUrl functionality.
Description check ✅ Passed The PR description comprehensively covers the main purpose, type of change, testing instructions, and completed checklist items matching the required template structure.

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78e72dc and bd79991.

📒 Files selected for processing (5)
  • svc/api/openapi/gen.go
  • svc/api/openapi/openapi-generated.yaml
  • svc/api/openapi/spec/paths/v2/deploy/createDeployment/index.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/index.yaml
  • svc/api/routes/register.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • svc/api/openapi/openapi-generated.yaml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*.go: Organize Go imports in groups separated by blank lines: (1) Standard library, (2) External/third-party packages, (3) Internal packages (github.com/unkeyed/unkey/internal/...), (4) Package-level (github.com/unkeyed/unkey/pkg/...), (5) Service-level (github.com/unkeyed/unkey/svc/...), (6) Generated code (github.com/unkeyed/unkey/gen/...)
Use the fault package for structured error handling with fault.Wrap(), fault.Code(), fault.Internal(), and fault.Public() methods
Use snake_case.go for Go filenames and *_test.go for test files
Use PascalCase for exported Go functions and types, camelCase for unexported functions and types
Use short receiver names like (s *Service) or (h *Handler) in Go methods
Use SCREAMING_SNAKE_CASE for Go constants
Run make bazel after adding new Go files to sync BUILD.bazel files
Run make fmt before committing Go changes to format and lint code

Files:

  • svc/api/openapi/gen.go
  • svc/api/routes/register.go
svc/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Use the pkg/zen custom HTTP framework for Go HTTP handlers with the pattern of binding request bodies, handling business logic, and returning JSON responses

Files:

  • svc/api/openapi/gen.go
  • svc/api/routes/register.go
🧠 Learnings (8)
📓 Common learnings
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3785
File: go/apps/api/routes/v2_keys_reroll_key/401_test.go:52-61
Timestamp: 2025-08-14T16:25:48.167Z
Learning: User Flo4604 requested creation of a GitHub issue to track converting all test files to use table-driven test patterns as a broader codebase improvement, following the suggestion made during review of go/apps/api/routes/v2_keys_reroll_key/401_test.go.
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3755
File: .github/workflows/job_detect_changes.yaml:111-114
Timestamp: 2025-08-08T16:10:00.224Z
Learning: Repo: unkeyed/unkey — When imeyer writes “make me an issue” on a PR thread, automatically open a thorough GitHub issue (Summary, Impact, Where, Repro, Observed vs Expected, Acceptance Criteria, Validation Plan, Out of Scope, References), include backlinks to the PR and comment, and assign to imeyer.
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: apps/dashboard/lib/trpc/routers/deploy/rollback.ts:23-24
Timestamp: 2025-09-12T18:11:33.481Z
Learning: In apps/dashboard/lib/trpc/routers/deploy/rollback.ts, the CTRL_URL environment variable should fail fast with a clear error message if missing in non-development environments, rather than defaulting to localhost which can mask production configuration issues.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
📚 Learning: 2025-08-08T15:09:01.312Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3753
File: go/apps/api/openapi/config.yaml:9-10
Timestamp: 2025-08-08T15:09:01.312Z
Learning: Repo unkeyed/unkey: In go/apps/api/openapi, oapi-codegen doesn’t support OpenAPI 3.1 union nullability; overlay.yaml must be applied before codegen. The overlay key in oapi-codegen config isn’t supported—use a pre-step (programmatic or CLI) to merge overlay into the bundled spec, then run oapi-codegen.

Applied to files:

  • svc/api/openapi/gen.go
  • svc/api/routes/register.go
📚 Learning: 2025-08-08T15:09:01.312Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3753
File: go/apps/api/openapi/config.yaml:9-10
Timestamp: 2025-08-08T15:09:01.312Z
Learning: Repo unkeyed/unkey: In go/apps/api/openapi, overlays are required to downconvert 3.1 union nullability to 3.0-style nullable before running oapi-codegen; config.yaml’s output-options.overlay is not recognized by oapi-codegen, so overlays must be applied in a pre-step (programmatic or CLI) prior to codegen.

Applied to files:

  • svc/api/openapi/gen.go
📚 Learning: 2025-09-01T15:10:44.959Z
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3899
File: go/proto/metald/v1/deployment.proto:7-15
Timestamp: 2025-09-01T15:10:44.959Z
Learning: In the unkey/unkey repository, the metald service receives deployment_id values from upstream services rather than generating them internally. The deployment_id field in DeploymentRequest is part of a service-to-service communication pattern.

Applied to files:

  • svc/api/routes/register.go
📚 Learning: 2025-09-11T08:17:21.423Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3944
File: go/apps/ctrl/services/deployment/fallbacks/k8s.go:236-247
Timestamp: 2025-09-11T08:17:21.423Z
Learning: The K8s fallback backend in go/apps/ctrl/services/deployment/fallbacks/k8s.go is specifically designed for scenarios where the ctrl service runs inside a Kubernetes cluster. It should not be used when ctrl runs outside the cluster - in those cases, the Docker fallback should be used instead.

Applied to files:

  • svc/api/routes/register.go
📚 Learning: 2025-09-15T18:12:01.503Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: go/apps/ctrl/run.go:212-213
Timestamp: 2025-09-15T18:12:01.503Z
Learning: In Go connect-go generated handlers, mux.Handle can accept New*ServiceHandler functions directly even though they return (string, http.Handler) tuples. The pattern mux.Handle(ctrlv1connect.New*ServiceHandler(...)) is valid and compiles successfully in unkey codebase.

Applied to files:

  • svc/api/routes/register.go
📚 Learning: 2026-01-13T17:46:51.335Z
Learnt from: CR
Repo: unkeyed/unkey PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T17:46:51.335Z
Learning: Applies to svc/**/*.go : Use the `pkg/zen` custom HTTP framework for Go HTTP handlers with the pattern of binding request bodies, handling business logic, and returning JSON responses

Applied to files:

  • svc/api/routes/register.go
📚 Learning: 2025-08-27T11:02:17.155Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 3860
File: go/pkg/zen/middleware_metrics_test.go:297-313
Timestamp: 2025-08-27T11:02:17.155Z
Learning: The redact function in go/pkg/zen/middleware_metrics.go is not expected to be called concurrently, so concurrency tests are unnecessary for this function.

Applied to files:

  • svc/api/routes/register.go
🧬 Code graph analysis (1)
svc/api/routes/register.go (2)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (1)
  • Handler (23-28)
svc/api/routes/v2_deploy_create_deployment/handler.go (1)
  • Handler (23-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Bazel / Test
  • GitHub Check: Bazel / Build
🔇 Additional comments (4)
svc/api/openapi/spec/paths/v2/deploy/createDeployment/index.yaml (1)

9-10: LGTM — operationId naming convention is consistent across deploy endpoints.

The deploy. prefix for the operationId is a reasonable namespacing convention, and the x-speakeasy-name-override correctly preserves the clean SDK method name. The new generateUploadUrl endpoint properly follows the same convention with deploy.generateUploadUrl, maintaining consistency across all deploy endpoints.

svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/index.yaml (1)

1-49: LGTM!

The OpenAPI spec is well-structured with proper security requirements, request/response schema references, and comprehensive error response definitions. The operationId: deploy.generateUploadUrl follows the naming convention established for unique operation IDs per the previous review feedback.

svc/api/openapi/gen.go (1)

1-4: Skipping review of generated code.

This file is auto-generated by oapi-codegen (as indicated by the header). The generated types (V2DeployGenerateUploadUrlRequestBody, V2DeployGenerateUploadUrlResponseBody, V2DeployGenerateUploadUrlResponseData) and aliases (DeployGenerateUploadUrlJSONRequestBody) correctly reflect the OpenAPI spec changes.

svc/api/routes/register.go (1)

340-349: LGTM!

The new route registration for v2/deploy.generateUploadUrl follows the established pattern and correctly wires the BuildServiceClient to the handler. The handler fields (Logger, DB, Keys, CtrlClient) match the expected Handler struct definition.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

@vercel vercel bot temporarily deployed to Preview – engineering January 13, 2026 12:07 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard January 13, 2026 12:10 Inactive
@ogzhanolguncu ogzhanolguncu marked this pull request as ready for review January 13, 2026 12:35
Copy link
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
svc/api/routes/v2_deploy_create_deployment/404_test.go (1)

93-98: Semantic mismatch: "environment not found" test asserts "Project not found." message.

The test scenario at line 85 explicitly sets a non-existent environment ("nonexistent-env"), yet the assertion at line 98 expects "Project not found.". This conflates two distinct error conditions and could mask environment-related bugs.

Also, line 94 has a truncated format string: "expected , received: %s" is missing the expected status.

Suggested fix for the format string
-		require.Equal(t, http.StatusInternalServerError, res.Status, "expected , received: %s", res.RawBody)
+		require.Equal(t, http.StatusInternalServerError, res.Status, "expected 500, received: %s", res.RawBody)
🤖 Fix all issues with AI agents
In @svc/api/openapi/openapi-generated.yaml:
- Around line 4247-4296: Update the OpenAPI operation for
/v2/deploy.generateUploadUrl (operationId: generateUploadUrl) to not claim
permission enforcement in the description and to add a 403 response entry;
specifically, remove or reword “Requires a valid root key with appropriate
permissions.” to indicate authentication only (or that permission checks are not
yet implemented), and add a "403" response with the existing error schema (e.g.,
Unauthorized/NotAllowed style or BadRequestErrorResponse pattern) so the
contract includes forbidden responses once checks are added.

In @svc/api/routes/v2_deploy_generate_upload_url/404_test.go:
- Around line 54-59: The test in 404_test.go is asserting a 500
InternalServerError using openapi.InternalServerErrorResponse while the file
name and OpenAPI spec expect a 404; either rename the test file to reflect a 500
case or change the assertions to expect a 404 and the NotFound response type:
replace openapi.InternalServerErrorResponse with the appropriate
openapi.NotFoundResponse (or similar) and update require.Equal checks to
http.StatusNotFound and the correct error.Type/Detail; also ensure the
handler/route under test returns 404 for missing projects if you choose the
RESTful fix.
🧹 Nitpick comments (8)
svc/ctrl/services/build/backend/depot/generate_upload_url.go (1)

40-53: Consider extracting expiration duration to a constant.

The expiration is expressed as 15*time.Minute on line 41 and as 900 (seconds) on line 53. If either value changes, they could fall out of sync.

♻️ Suggested refactor
+const uploadURLExpiration = 15 * time.Minute
+
 func (s *Depot) GenerateUploadURL(
 	ctx context.Context,
 	req *connect.Request[ctrlv1.GenerateUploadURLRequest],
 ) (*connect.Response[ctrlv1.GenerateUploadURLResponse], error) {
 	// ... existing code ...
 
 	// Generate presigned URL (15 minutes expiration)
-	uploadURL, err := s.storage.GenerateUploadURL(ctx, buildContextPath, 15*time.Minute)
+	uploadURL, err := s.storage.GenerateUploadURL(ctx, buildContextPath, uploadURLExpiration)
 	// ... existing code ...
 
 	return connect.NewResponse(&ctrlv1.GenerateUploadURLResponse{
 		UploadUrl:        uploadURL,
 		BuildContextPath: buildContextPath,
-		ExpiresIn:        900, // 15 minutes
+		ExpiresIn:        int64(uploadURLExpiration.Seconds()),
 	}), nil
 }
svc/ctrl/services/build/backend/docker/generate_upload_url.go (1)

36-38: Consider using a more collision-resistant identifier.

While UnixNano provides millisecond-level uniqueness, under high concurrency on a single machine, there's a theoretical (though unlikely) collision risk. A UUID or similar would be more robust.

Optional: Use UUID for build context path
+import "github.com/google/uuid"
+
 // Generate unique S3 key for this build context
-buildContextPath := fmt.Sprintf("%s/%d.tar.gz",
-    unkeyProjectID,
-    time.Now().UnixNano())
+buildContextPath := fmt.Sprintf("%s/%s.tar.gz",
+    unkeyProjectID,
+    uuid.New().String())
svc/api/internal/ctrlclient/errors.go (1)

30-34: Consider making the NotFound message context-aware.

The CodeNotFound case hardcodes "Project not found." but this helper may be reused for other resource types in the future. Unlike InvalidArgument and the default case, it doesn't use the context parameter.

If this is intentional because the current usage is limited to project-related operations, this is fine. Otherwise, consider:

♻️ Suggested improvement for flexibility
 	case connect.CodeNotFound:
 		return fault.Wrap(err,
 			fault.Code(codes.Data.Project.NotFound.URN()),
-			fault.Public("Project not found."),
+			fault.Public(fmt.Sprintf("Resource not found for %s.", context)),
 		)
svc/api/openapi/openapi-generated.yaml (2)

432-451: Tighten the new request/response schemas (additionalProperties: false) for consistency and stricter contracts.
Most v2 request/response bodies in this spec disallow unknown fields; these new ones currently allow them implicitly.

Proposed change (apply in the source OpenAPI spec, then re-generate)
 V2DeployGenerateUploadUrlRequestBody:
     type: object
     required:
         - projectId
+    additionalProperties: false
     properties:
         projectId:
             type: string
             minLength: 1
             description: Unkey project ID for which to generate the upload URL
             example: "proj_123abc"

 V2DeployGenerateUploadUrlResponseBody:
     type: object
     required:
         - meta
         - data
+    additionalProperties: false
     properties:
         meta:
             $ref: "#/components/schemas/Meta"
         data:
             $ref: "#/components/schemas/V2DeployGenerateUploadUrlResponseData"

2634-2653: Add basic validation hints (format: uri, minimum) to reduce client-side ambiguity.
This improves generated SDK typing and validation without changing behavior.

Proposed change (apply in the source OpenAPI spec, then re-generate)
 V2DeployGenerateUploadUrlResponseData:
     type: object
     required:
         - uploadUrl
         - context
         - expiresIn
+    additionalProperties: false
     properties:
         uploadUrl:
             type: string
+            format: uri
             description: Presigned PUT URL for uploading the build context tar file
             example: "https://s3.amazonaws.com/bucket/path?signature=..."
         context:
             type: string
             description: S3 path to use in the createDeployment request when building from source
             example: "proj_123abc/ctx_456def.tar.gz"
         expiresIn:
             type: integer
             format: int64
+            minimum: 1
             description: Number of seconds until the upload URL expires
             example: 3600
svc/api/routes/v2_deploy_generate_upload_url/401_test.go (1)

63-65: Use UnauthorizedErrorResponse for proper error response validation.

The test uses handler.Response (success body type) instead of the appropriate error response type. Compare with 404_test.go which correctly uses openapi.InternalServerErrorResponse. This limits the test's ability to validate the actual error structure.

♻️ Suggested fix
+	"github.com/unkeyed/unkey/svc/api/openapi"
...
-		res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req)
+		res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](h, route, headers, req)
 		require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, received: %s", res.RawBody)
 		require.NotNil(t, res.Body)
+		require.NotEmpty(t, res.Body.Error.Type)
+		require.Equal(t, http.StatusUnauthorized, res.Body.Error.Status)
svc/api/routes/v2_deploy_generate_upload_url/400_test.go (1)

85-113: Consider using consistent error response type for auth-related 400 tests.

The "missing authorization header" and "malformed authorization header" tests use handler.Response instead of openapi.BadRequestErrorResponse, which limits the ability to validate the error structure. Consider aligning with the first two test cases for consistency.

♻️ Suggested improvement
 	t.Run("missing authorization header", func(t *testing.T) {
 		headers := http.Header{
 			"Content-Type": {"application/json"},
 			// No Authorization header
 		}

 		req := handler.Request{
 			ProjectId: projectID,
 		}

-		res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req)
+		res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req)
 		require.Equal(t, http.StatusBadRequest, res.Status)
 		require.NotNil(t, res.Body)
+		require.NotEmpty(t, res.Body.Meta.RequestId)
 	})

 	t.Run("malformed authorization header", func(t *testing.T) {
 		headers := http.Header{
 			"Content-Type":  {"application/json"},
 			"Authorization": {"malformed_header"},
 		}

 		req := handler.Request{
 			ProjectId: projectID,
 		}

-		res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req)
+		res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req)
 		require.Equal(t, http.StatusBadRequest, res.Status)
 		require.NotNil(t, res.Body)
+		require.NotEmpty(t, res.Body.Meta.RequestId)
 	})
svc/api/routes/v2_deploy_generate_upload_url/handler.go (1)

25-30: Consider removing unused struct fields.

Logger and DB are declared but not used in this handler. If they're not needed for future permission checks mentioned in the PR, consider removing them to reduce noise.

♻️ Optional cleanup
 type Handler struct {
-	Logger     logging.Logger
-	DB         db.Database
 	Keys       keys.KeyService
 	CtrlClient ctrlv1connect.BuildServiceClient
 }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30eb6fd and e3f01bb.

📒 Files selected for processing (24)
  • svc/api/internal/ctrlclient/BUILD.bazel
  • svc/api/internal/ctrlclient/errors.go
  • svc/api/openapi/gen.go
  • svc/api/openapi/openapi-generated.yaml
  • svc/api/openapi/openapi-split.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlRequestBody.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseBody.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/index.yaml
  • svc/api/routes/BUILD.bazel
  • svc/api/routes/register.go
  • svc/api/routes/services.go
  • svc/api/routes/v2_deploy_create_deployment/404_test.go
  • svc/api/routes/v2_deploy_create_deployment/BUILD.bazel
  • svc/api/routes/v2_deploy_create_deployment/handler.go
  • svc/api/routes/v2_deploy_generate_upload_url/200_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/400_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/401_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/404_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/BUILD.bazel
  • svc/api/routes/v2_deploy_generate_upload_url/handler.go
  • svc/api/run.go
  • svc/ctrl/services/build/backend/depot/generate_upload_url.go
  • svc/ctrl/services/build/backend/docker/generate_upload_url.go
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
📚 Learning: 2025-08-14T16:25:48.167Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3785
File: go/apps/api/routes/v2_keys_reroll_key/401_test.go:52-61
Timestamp: 2025-08-14T16:25:48.167Z
Learning: User Flo4604 requested creation of a GitHub issue to track converting all test files to use table-driven test patterns as a broader codebase improvement, following the suggestion made during review of go/apps/api/routes/v2_keys_reroll_key/401_test.go.

Applied to files:

  • svc/api/routes/v2_deploy_generate_upload_url/404_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/401_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/200_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/400_test.go
📚 Learning: 2025-09-25T18:49:28.948Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 4010
File: go/apps/ctrl/run.go:224-233
Timestamp: 2025-09-25T18:49:28.948Z
Learning: In go/apps/ctrl/run.go, mcstepp prefers to keep ACME service handlers wrapped with auth interceptor during the demo phase for simplicity, even though typically ACME routes would be exempt from authentication. This is part of the temporary demo authentication system.

Applied to files:

  • svc/api/routes/v2_deploy_generate_upload_url/401_test.go
  • svc/api/routes/register.go
📚 Learning: 2026-01-09T12:56:40.667Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4575
File: svc/preflight/config.go:12-19
Timestamp: 2026-01-09T12:56:40.667Z
Learning: In the unkeyed/unkey repository, when cmd/preflight/main.go uses cmd.RequireString() for TLSCertFile and TLSKeyFile flags, it panics if these values are not provided, ensuring validation at the CLI parsing stage before config.Validate() is called. Therefore, additional validation in svc/preflight/config.go Validate() method for these fields is unnecessary.

Applied to files:

  • svc/ctrl/services/build/backend/depot/generate_upload_url.go
📚 Learning: 2025-10-15T10:12:40.810Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4098
File: go/proto/ctrl/v1/deployment.proto:33-36
Timestamp: 2025-10-15T10:12:40.810Z
Learning: In the Unkey codebase proto files (ctrl/v1/build.proto, ctrl/v1/deployment.proto, hydra/v1/deployment.proto), use `dockerfile_path` (not `docker_file_path`) for consistency in generated Go field names.

Applied to files:

  • svc/ctrl/services/build/backend/depot/generate_upload_url.go
📚 Learning: 2025-09-15T18:12:01.503Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: go/apps/ctrl/run.go:212-213
Timestamp: 2025-09-15T18:12:01.503Z
Learning: In Go connect-go generated handlers, mux.Handle can accept New*ServiceHandler functions directly even though they return (string, http.Handler) tuples. The pattern mux.Handle(ctrlv1connect.New*ServiceHandler(...)) is valid and compiles successfully in unkey codebase.

Applied to files:

  • svc/api/internal/ctrlclient/errors.go
  • svc/api/routes/v2_deploy_generate_upload_url/handler.go
  • svc/api/routes/v2_deploy_create_deployment/handler.go
  • svc/api/routes/register.go
📚 Learning: 2025-08-08T15:09:01.312Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3753
File: go/apps/api/openapi/config.yaml:9-10
Timestamp: 2025-08-08T15:09:01.312Z
Learning: Repo unkeyed/unkey: For go/apps/api/openapi, oapi-codegen is used and does not support OpenAPI 3.1 union types like [T, "null"]; an overlay step is required to downconvert to 3.0-style nullable before code generation.

Applied to files:

  • svc/api/routes/v2_deploy_create_deployment/handler.go
📚 Learning: 2025-08-08T15:09:01.312Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3753
File: go/apps/api/openapi/config.yaml:9-10
Timestamp: 2025-08-08T15:09:01.312Z
Learning: Repo unkeyed/unkey: In go/apps/api/openapi, oapi-codegen doesn’t support OpenAPI 3.1 union nullability; overlay.yaml must be applied before codegen. The overlay key in oapi-codegen config isn’t supported—use a pre-step (programmatic or CLI) to merge overlay into the bundled spec, then run oapi-codegen.

Applied to files:

  • svc/api/routes/register.go
📚 Learning: 2025-09-01T15:10:44.959Z
Learnt from: imeyer
Repo: unkeyed/unkey PR: 3899
File: go/proto/metald/v1/deployment.proto:7-15
Timestamp: 2025-09-01T15:10:44.959Z
Learning: In the unkey/unkey repository, the metald service receives deployment_id values from upstream services rather than generating them internally. The deployment_id field in DeploymentRequest is part of a service-to-service communication pattern.

Applied to files:

  • svc/api/routes/register.go
🧬 Code graph analysis (9)
svc/api/routes/v2_deploy_generate_upload_url/404_test.go (5)
pkg/testutil/http.go (1)
  • CallRoute (413-447)
pkg/rpc/interceptor/header.go (1)
  • NewHeaderInjector (22-24)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (2)
  • Handler (25-30)
  • Request (21-21)
svc/api/routes/register.go (1)
  • Register (70-666)
svc/api/openapi/gen.go (1)
  • InternalServerErrorResponse (184-190)
svc/api/routes/v2_deploy_generate_upload_url/401_test.go (5)
pkg/testutil/http.go (1)
  • CallRoute (413-447)
pkg/rpc/interceptor/header.go (1)
  • NewHeaderInjector (22-24)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (3)
  • Handler (25-30)
  • Request (21-21)
  • Response (22-22)
svc/api/routes/register.go (1)
  • Register (70-666)
pkg/testutil/seed/seed.go (1)
  • CreateProjectRequest (124-132)
svc/api/internal/ctrlclient/errors.go (2)
pkg/codes/constants_gen.go (1)
  • URN (5-5)
pkg/codes/unkey_data.go (1)
  • Data (126-187)
svc/api/routes/v2_deploy_generate_upload_url/200_test.go (4)
pkg/testutil/http.go (1)
  • CallRoute (413-447)
pkg/rpc/interceptor/header.go (1)
  • NewHeaderInjector (22-24)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (3)
  • Handler (25-30)
  • Request (21-21)
  • Response (22-22)
pkg/testutil/seed/seed.go (1)
  • CreateProjectRequest (124-132)
svc/api/run.go (1)
pkg/rpc/interceptor/header.go (1)
  • NewHeaderInjector (22-24)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (3)
svc/api/openapi/gen.go (4)
  • V2DeployGenerateUploadUrlRequestBody (747-750)
  • V2DeployGenerateUploadUrlResponseBody (753-758)
  • Meta (282-285)
  • V2DeployGenerateUploadUrlResponseData (761-770)
svc/api/routes/v2_deploy_create_deployment/handler.go (3)
  • Handler (25-30)
  • Request (21-21)
  • Response (22-22)
svc/api/internal/ctrlclient/errors.go (1)
  • HandleError (17-52)
svc/api/routes/v2_deploy_create_deployment/handler.go (1)
svc/api/internal/ctrlclient/errors.go (1)
  • HandleError (17-52)
svc/api/routes/register.go (2)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (1)
  • Handler (25-30)
svc/api/routes/v2_deploy_create_deployment/handler.go (1)
  • Handler (25-30)
svc/api/routes/v2_deploy_generate_upload_url/400_test.go (5)
pkg/rpc/interceptor/header.go (1)
  • NewHeaderInjector (22-24)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (3)
  • Handler (25-30)
  • Request (21-21)
  • Response (22-22)
svc/api/routes/register.go (1)
  • Register (70-666)
pkg/testutil/seed/seed.go (1)
  • CreateProjectRequest (124-132)
svc/api/openapi/gen.go (2)
  • BadRequestErrorResponse (68-74)
  • Meta (282-285)
🪛 GitHub Actions: PR
svc/api/internal/ctrlclient/BUILD.bazel

[error] 1-1: Command failed: bazel test //... --test_output=errors

svc/api/routes/v2_deploy_create_deployment/BUILD.bazel

[error] 1-1: Command failed: bazel test //... --test_output=errors

svc/api/routes/v2_deploy_generate_upload_url/BUILD.bazel

[error] 1-1: Command failed: bazel test //... --test_output=errors

svc/api/routes/BUILD.bazel

[error] 1-1: Command failed: bazel test //... --test_output=errors

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Lint Go Code / Lint
  • GitHub Check: Bazel / Build
  • GitHub Check: Bazel / Test
🔇 Additional comments (27)
svc/ctrl/services/build/backend/depot/generate_upload_url.go (1)

24-33: Good addition: Project existence validation prevents URL generation for arbitrary project IDs.

The security rationale is well-documented in the comment, and the error handling correctly distinguishes between not-found and internal errors.

svc/api/routes/v2_deploy_create_deployment/404_test.go (1)

68-68: LGTM on the error message update.

The updated error detail aligns with the centralized error handling in ctrlclient.HandleError.

svc/ctrl/services/build/backend/docker/generate_upload_url.go (1)

24-33: Good security practice: project existence validation before URL generation.

The pre-check prevents unauthorized generation of upload URLs for arbitrary project IDs. Error handling correctly distinguishes between not-found and internal errors.

svc/api/routes/services.go (1)

29-30: LGTM!

The new CtrlBuildClient field follows the established naming convention and is appropriately placed alongside the existing CtrlDeploymentClient.

svc/api/routes/v2_deploy_create_deployment/BUILD.bazel (2)

17-17: LGTM on the new ctrlclient dependency.

The dependency addition enables the handler to use the centralized HandleError utility for Connect RPC error translation.


23-43: No pipeline issues were identified in the test configuration or implementation. The BUILD.bazel dependencies are correct, test assertions match the intended error handling behavior, and the handler code properly utilizes all declared dependencies. If a pipeline failure is occurring, provide the specific error output for targeted investigation.

svc/api/internal/ctrlclient/BUILD.bazel (1)

1-13: BUILD file structure is correct.

The library definition with appropriate visibility scoping and dependencies is well-structured. The errors.go source file exists, and both consuming targets (v2_deploy_create_deployment and v2_deploy_generate_upload_url) properly declare the //svc/api/internal/ctrlclient dependency. No missing dependencies were found in dependent targets.

Likely an incorrect or invalid review comment.

svc/api/internal/ctrlclient/errors.go (1)

1-52: LGTM overall - clean centralized error handling.

The HandleError function provides a well-structured approach for converting Connect RPC errors to user-facing fault errors. The use of //nolint:exhaustive with a sensible default case is appropriate.

svc/api/routes/v2_deploy_create_deployment/handler.go (1)

16-16: LGTM - Clean refactor to use centralized error handling.

The switch from inline error handling to ctrlclient.HandleError(err, "create deployment") is a good improvement that reduces code duplication and maintains consistency with other handlers.

Also applies to: 120-122

svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlRequestBody.yaml (1)

1-9: LGTM - Well-defined request schema.

The schema correctly specifies projectId as required with minLength: 1 validation, preventing empty strings. The description and example are clear.

svc/api/routes/BUILD.bazel (1)

38-38: LGTM - Dependency correctly added.

The new route dependency follows the established pattern and is correctly placed alongside the other deploy-related routes.

svc/api/openapi/gen.go (2)

746-770: Generated types look correct.

The generated request/response types for the generateUploadUrl endpoint are properly structured:

  • Request body requires projectId
  • Response data includes context (S3 path), expiresIn (seconds as int64), and uploadUrl (presigned URL)

2367-2368: LGTM - Type alias correctly generated.

svc/api/routes/v2_deploy_generate_upload_url/BUILD.bazel (1)

1-43: Build configuration looks correct, but pipeline failure needs investigation.

The Bazel targets are well-structured with appropriate dependencies. However, the pipeline shows bazel test //... failed. Since the handler.go and test files (200_test.go, 400_test.go, etc.) are not included in this review, the root cause cannot be determined from the provided context.

#!/bin/bash
# Check if all referenced source files exist and examine any compilation/test errors
echo "=== Checking source files ==="
fd -t f "handler.go|200_test.go|400_test.go|401_test.go|404_test.go" svc/api/routes/v2_deploy_generate_upload_url

echo "=== Checking handler.go content ==="
cat svc/api/routes/v2_deploy_generate_upload_url/handler.go 2>/dev/null || echo "handler.go not found"

echo "=== Checking for any BUILD issues in deps ==="
rg -l "v2_deploy_generate_upload_url" --type bazel
svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/index.yaml (1)

1-49: LGTM!

The OpenAPI specification is well-structured and follows the existing patterns for other v2 endpoints. Security, request/response schemas, and error responses are properly defined.

svc/api/openapi/openapi-split.yaml (1)

140-141: LGTM!

The new endpoint reference is correctly placed under the Deploy Endpoints section and follows the established naming and reference patterns.

svc/api/routes/v2_deploy_generate_upload_url/200_test.go (1)

19-76: LGTM!

The test is well-structured with comprehensive assertions covering the success path. It properly validates all key response fields (UploadUrl, Context, ExpiresIn) and follows the established test patterns in the codebase.

svc/api/routes/register.go (2)

30-30: LGTM!

The import alias follows the established naming convention used by other route packages in this file.


341-352: LGTM!

The route registration follows the established pattern for CTRL-dependent endpoints, with proper nil-check guarding and correct handler field wiring matching the Handler struct definition.

svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml (1)

1-19: LGTM!

The OpenAPI schema correctly defines the response data structure with appropriate types, formats, and clear descriptions. The int64 format for expiresIn is suitable for representing seconds, and the examples are realistic.

svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseBody.yaml (1)

1-9: LGTM!

The response body schema follows the standard API response structure with meta and data properties, and correctly references the shared Meta schema and the specific response data schema.

svc/api/routes/v2_deploy_generate_upload_url/400_test.go (1)

20-58: LGTM!

The test setup correctly initializes the harness, CTRL client with authorization, and seeds the necessary test data. The pattern is consistent with other route tests in the codebase.

svc/api/run.go (2)

302-321: LGTM!

The BuildServiceClient initialization correctly mirrors the existing DeploymentServiceClient pattern. While there's some duplication in the interceptor setup, this keeps each client self-contained and explicit, which aligns with the codebase patterns. The log messages appropriately reflect that both deployment and build endpoints depend on the CTRL URL configuration.


335-335: LGTM!

The CtrlBuildClient is correctly wired to the routes services, enabling the conditional registration of the generateUploadUrl endpoint.

svc/api/routes/v2_deploy_generate_upload_url/handler.go (3)

40-45: Root key validation without usage is intentional per PR description.

The root key is obtained but discarded since permission checks will be added later. Consider adding a brief TODO comment to clarify the intent for future maintainers.

// TODO: Add deployment-related permission checks using the root key
_, emit, err := h.Keys.GetRootKey(ctx, s)

47-68: LGTM!

Request binding, input validation, and error handling follow established patterns. The use of ctrlclient.HandleError properly maps Connect RPC errors to fault errors for consistent API responses.


70-79: LGTM!

Response structure correctly maps the control plane response fields (UploadUrl, BuildContextPathContext, ExpiresIn) to the OpenAPI-defined response format with proper metadata.

Copy link
Contributor

@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 @svc/api/routes/v2_deploy_create_deployment/handler.go:
- Around line 111-113: The route change to use ctrlclient.HandleError is fine
but you must ensure ProjectId validation semantics remain correct: update the
CTRL service CreateDeployment
(svc/ctrl/services/deployment/create_deployment.go, function CreateDeployment)
to explicitly check for an empty ProjectId before performing the DB lookup and,
if empty, return a connect error with CodeInvalidArgument (with a clear message)
instead of allowing the DB lookup to produce CodeNotFound; this preserves the
original invalid-input vs missing-resource semantics so the handler's call to
ctrlclient.HandleError maps to the intended error code/message.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e3f01bb and 286ef83.

📒 Files selected for processing (4)
  • svc/api/routes/v2_deploy_create_deployment/BUILD.bazel
  • svc/api/routes/v2_deploy_create_deployment/handler.go
  • svc/api/routes/v2_deploy_generate_upload_url/BUILD.bazel
  • svc/api/routes/v2_deploy_generate_upload_url/handler.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • svc/api/routes/v2_deploy_generate_upload_url/BUILD.bazel
  • svc/api/routes/v2_deploy_generate_upload_url/handler.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
📚 Learning: 2025-09-15T18:12:01.503Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: go/apps/ctrl/run.go:212-213
Timestamp: 2025-09-15T18:12:01.503Z
Learning: In Go connect-go generated handlers, mux.Handle can accept New*ServiceHandler functions directly even though they return (string, http.Handler) tuples. The pattern mux.Handle(ctrlv1connect.New*ServiceHandler(...)) is valid and compiles successfully in unkey codebase.

Applied to files:

  • svc/api/routes/v2_deploy_create_deployment/handler.go
🧬 Code graph analysis (1)
svc/api/routes/v2_deploy_create_deployment/handler.go (1)
svc/api/internal/ctrlclient/errors.go (1)
  • HandleError (17-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint Go Code / Lint
  • GitHub Check: Bazel / Build
  • GitHub Check: Bazel / Test
  • GitHub Check: autofix
🔇 Additional comments (1)
svc/api/routes/v2_deploy_create_deployment/BUILD.bazel (1)

15-15: LGTM!

The dependency update correctly adds ctrlclient to support the centralized error handling, replacing the previously direct dependencies on codes and fault packages.

Copy link
Contributor

@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 @svc/api/openapi/openapi-generated.yaml:
- Around line 2634-2648: The schema V2DeployGenerateUploadUrlResponseData lists
expiresIn in its required array but the expiresIn property is missing; either
remove "expiresIn" from the required list on
V2DeployGenerateUploadUrlResponseData or re-add a matching expiresIn property
under properties (e.g., type: integer/string with description and example) in
the OpenAPI source, then regenerate the openapi YAML and any client/server code
so validation/codegen is consistent.

In
@svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml:
- Around line 2-5: The OpenAPI spec lists expiresIn in the required array but
its property was removed; update the V2DeployGenerateUploadUrlResponseData
schema by removing "expiresIn" from the required list (leaving "uploadUrl" and
"context") so the required array matches the defined properties, or
alternatively restore a proper expiresIn property if it was intended to
remain—refer to the required array and the properties block (uploadUrl, context)
and make them consistent.
🧹 Nitpick comments (2)
svc/api/openapi/openapi-generated.yaml (1)

4242-4291: Docs mismatch: endpoint claims “appropriate permissions” while checks are explicitly “later”.

The endpoint description says it requires “appropriate permissions” (Line 4244-4248), but the PR objectives state deployment-related permission checks are not implemented yet. Consider explicitly documenting the current behavior (e.g., “requires a valid root key; permission checks TBD”) to avoid misleading integrators.

svc/api/routes/v2_deploy_generate_upload_url/handler.go (1)

23-28: Consider removing unused struct fields or adding TODO comments.

The DB and Logger fields are declared but not used in the Handle method. If these are intentionally kept for future permission checks (as noted in the PR objectives), consider adding a brief comment. Otherwise, removing them reduces confusion.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25c4037 and f950c51.

📒 Files selected for processing (5)
  • svc/api/openapi/gen.go
  • svc/api/openapi/openapi-generated.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml
  • svc/api/routes/v2_deploy_generate_upload_url/200_test.go
  • svc/api/routes/v2_deploy_generate_upload_url/handler.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • svc/api/routes/v2_deploy_generate_upload_url/200_test.go
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: apps/dashboard/lib/trpc/routers/deploy/rollback.ts:23-24
Timestamp: 2025-09-12T18:11:33.481Z
Learning: In apps/dashboard/lib/trpc/routers/deploy/rollback.ts, the CTRL_URL environment variable should fail fast with a clear error message if missing in non-development environments, rather than defaulting to localhost which can mask production configuration issues.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
📚 Learning: 2025-07-03T05:58:10.699Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as `description`) alongside `$ref` in schema objects. Do not flag this as an error in future reviews.

Applied to files:

  • svc/api/openapi/openapi-generated.yaml
📚 Learning: 2025-09-15T18:12:01.503Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: go/apps/ctrl/run.go:212-213
Timestamp: 2025-09-15T18:12:01.503Z
Learning: In Go connect-go generated handlers, mux.Handle can accept New*ServiceHandler functions directly even though they return (string, http.Handler) tuples. The pattern mux.Handle(ctrlv1connect.New*ServiceHandler(...)) is valid and compiles successfully in unkey codebase.

Applied to files:

  • svc/api/routes/v2_deploy_generate_upload_url/handler.go
🧬 Code graph analysis (1)
svc/api/routes/v2_deploy_generate_upload_url/handler.go (2)
web/apps/dashboard/gen/proto/ctrl/v1/build_pb.ts (1)
  • GenerateUploadURLRequest (94-101)
svc/api/internal/ctrlclient/errors.go (1)
  • HandleError (17-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Lint Go Code / Lint
  • GitHub Check: Bazel / Build
  • GitHub Check: Bazel / Test
  • GitHub Check: autofix
🔇 Additional comments (5)
svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml (1)

7-14: Property definitions look good.

The uploadUrl and context fields are properly typed with clear descriptions and appropriate examples.

svc/api/openapi/gen.go (2)

746-767: Generated types look correct.

These types are auto-generated from the OpenAPI spec. The V2DeployGenerateUploadUrlResponseData struct correctly reflects only the defined properties (Context and UploadUrl). The mismatch with the required array in the YAML spec should be fixed there (as noted in the spec file review).


2364-2365: Type alias correctly defined.

svc/api/openapi/openapi-generated.yaml (1)

4248-4254: No action needed—operationId is unique.

The generateUploadUrl operationId appears only once in the specification with no duplicate operationIds present in the file, eliminating the risk of SDK naming collisions.

Likely an incorrect or invalid review comment.

svc/api/routes/v2_deploy_generate_upload_url/handler.go (1)

38-69: Implementation looks good; root key discarding is intentional.

The handler correctly:

  • Validates authentication via GetRootKey (permission checks deferred per PR objectives)
  • Binds and validates the request body
  • Uses centralized error handling via ctrlclient.HandleError
  • Maps the response fields appropriately

One minor observation: the root key value (first return) is discarded on line 39. Per PR objectives, this is intentional since permission checks will be added later. When those are implemented, you'll likely need to use the returned root key data.

@vercel vercel bot temporarily deployed to Preview – engineering January 13, 2026 18:03 Inactive
Copy link
Contributor

@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

🧹 Nitpick comments (1)
svc/api/openapi/openapi-generated.yaml (1)

432-452: Tighten schema validation: add additionalProperties: false and consider format: uri for uploadUrl.
Many existing request/response bodies in this spec are strict; these new ones being permissive can allow client typos to slip through unnoticed, and format: uri improves generated client types/validation.

Proposed spec tweak (apply in source openapi-split.yaml)
 V2DeployGenerateUploadUrlRequestBody:
   type: object
+  additionalProperties: false
   required:
     - projectId
   properties:
     projectId:
       type: string
       minLength: 1

 V2DeployGenerateUploadUrlResponseBody:
   type: object
+  additionalProperties: false
   required:
     - meta
     - data
   properties:
     meta:
       $ref: "#/components/schemas/Meta"
     data:
       $ref: "#/components/schemas/V2DeployGenerateUploadUrlResponseData"

 V2DeployGenerateUploadUrlResponseData:
   type: object
+  additionalProperties: false
   required:
     - uploadUrl
     - context
   properties:
     uploadUrl:
       type: string
+      format: uri
       description: Presigned PUT URL for uploading the build context tar file

Also applies to: 2634-2647

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f950c51 and 78e72dc.

📒 Files selected for processing (2)
  • svc/api/openapi/openapi-generated.yaml
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • svc/api/openapi/spec/paths/v2/deploy/generateUploadUrl/V2DeployGenerateUploadUrlResponseData.yaml
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3952
File: apps/dashboard/lib/trpc/routers/deploy/rollback.ts:23-24
Timestamp: 2025-09-12T18:11:33.481Z
Learning: In apps/dashboard/lib/trpc/routers/deploy/rollback.ts, the CTRL_URL environment variable should fail fast with a clear error message if missing in non-development environments, rather than defaulting to localhost which can mask production configuration issues.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3297
File: apps/dashboard/lib/trpc/routers/authorization/roles/query.ts:210-323
Timestamp: 2025-06-04T20:13:12.060Z
Learning: The user ogzhanolguncu prefers explicit, duplicated code over abstracted helper functions when it improves readability, even if it means some duplication in filter building functions in the authorization roles query module.
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.
📚 Learning: 2025-07-03T05:58:10.699Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as `description`) alongside `$ref` in schema objects. Do not flag this as an error in future reviews.

Applied to files:

  • svc/api/openapi/openapi-generated.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: autofix
  • GitHub Check: Bazel / Test
  • GitHub Check: Bazel / Build
🔇 Additional comments (1)
svc/api/openapi/openapi-generated.yaml (1)

2626-2647: Verify createDeployment response contract: does it also return upload-url data or not?
In this file, V2DeployCreateDeploymentResponseData shows only deploymentId. If the implementation now optionally includes V2DeployGenerateUploadUrlResponseData (as described in the PR summary/AI summary), the OpenAPI contract should expose that field to avoid breaking generated clients.

Copy link
Member

@Flo4604 Flo4604 left a comment

Choose a reason for hiding this comment

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

left some comments otherwise lgtm

@vercel vercel bot temporarily deployed to Preview – dashboard January 14, 2026 10:20 Inactive
@chronark chronark merged commit 40e39e3 into main Jan 14, 2026
20 checks passed
@chronark chronark deleted the generate-upload-url-api branch January 14, 2026 11:13
@coderabbitai coderabbitai bot mentioned this pull request Jan 15, 2026
19 tasks
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.

3 participants