Skip to content

feat(api): add project update endpoint#6475

Merged
ogzhanolguncu merged 6 commits into
mainfrom
06-16-feat_api_add_project_update_endpoint
Jun 19, 2026
Merged

feat(api): add project update endpoint#6475
ogzhanolguncu merged 6 commits into
mainfrom
06-16-feat_api_add_project_update_endpoint

Conversation

@ogzhanolguncu

@ogzhanolguncu ogzhanolguncu commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Important

Unkey is not accepting external pull requests at this time. Pull requests from people outside the Unkey team will not be reviewed or merged.

What does this PR do?

Adds a new POST /v2/projects.updateProject endpoint that allows updating a project's display name and delete protection setting, identified by its slug. The slug itself is immutable. Omitted fields are left unchanged, so callers can patch only what they need.

This includes:

  • A new UpdateProject SQL query and generated database code
  • A new update_project RBAC action (project.*.update_project or project.<project_id>.update_project)
  • A project.update audit log event emitted on every successful update
  • OpenAPI spec definitions for the request/response bodies and the new path
  • The update_project permission exposed in the dashboard root key permissions UI
  • Full test coverage for 200, 400, 401, 403, and 404 response scenarios

Fixes # (issue)

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?

  • Call POST /v2/projects.updateProject with a valid root key bearing project.*.update_project and a body containing a known project slug plus an updated name or deleteProtection value — expect a 200 response with the updated project reflected in data.
  • Omit both name and deleteProtection from the body (only slug provided) — expect a 200 response with the project unchanged.
  • Call with a slug that does not exist in the workspace — expect 404.
  • Call with a root key that lacks update_project permission — expect 403.
  • Call with an invalid or missing bearer token — expect 401.
  • Call with an invalid slug format (uppercase, underscores, too long) or an empty/too-long name — expect 400.
  • Verify a project.update audit log entry is created after a successful update.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Internal Workflow Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Ran mise run fmt
  • 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

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
dashboard Ready Ready Preview, Comment Jun 19, 2026 2:22pm
design Ready Ready Preview, Comment Jun 19, 2026 2:22pm

Request Review

@ogzhanolguncu ogzhanolguncu force-pushed the 06-16-feat_api_add_project_update_endpoint branch from f765ace to ec990a0 Compare June 16, 2026 10:59
@ogzhanolguncu ogzhanolguncu force-pushed the 06-15-feat_api_add_get_project branch from 38aac6a to 0e657f1 Compare June 16, 2026 10:59
@ogzhanolguncu ogzhanolguncu force-pushed the 06-15-feat_api_add_get_project branch from 0e657f1 to e784ce1 Compare June 16, 2026 11:20
@ogzhanolguncu ogzhanolguncu force-pushed the 06-16-feat_api_add_project_update_endpoint branch from ec990a0 to b9bc707 Compare June 16, 2026 11:20
@ogzhanolguncu ogzhanolguncu force-pushed the 06-15-feat_api_add_get_project branch from e784ce1 to ce4d46c Compare June 16, 2026 11:37
@ogzhanolguncu ogzhanolguncu force-pushed the 06-16-feat_api_add_project_update_endpoint branch from b9bc707 to 96584f9 Compare June 16, 2026 11:37
@ogzhanolguncu ogzhanolguncu force-pushed the 06-15-feat_api_add_get_project branch from ce4d46c to 3943366 Compare June 16, 2026 12:13
@ogzhanolguncu ogzhanolguncu force-pushed the 06-16-feat_api_add_project_update_endpoint branch from 96584f9 to 8048b4e Compare June 16, 2026 12:13
@ogzhanolguncu ogzhanolguncu force-pushed the 06-16-feat_api_add_project_update_endpoint branch from 26ec6eb to 5cb28c9 Compare June 16, 2026 12:20
@ogzhanolguncu ogzhanolguncu force-pushed the 06-15-feat_api_add_get_project branch from 3943366 to 5f6c290 Compare June 16, 2026 12:20

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — re-reviewed the delta since the prior Pullfrog review (73ca2b16): the endpoint switched its identifier from the project slug to the immutable projectId, made slug itself a patchable field, and added 409 conflict handling for duplicate slugs.

  • Lookup by immutable projectIdhandler.go now resolves the project via FindProjectByWorkspaceAndId and the request body requires projectId (^[a-zA-Z0-9_]+$, len 8-255), byte-identical to the sibling getProject request schema. The UpdateProject SQL is now keyed by WHERE workspace_id = ? AND id = ?.
  • slug is now a mutable field — a new slug = CASE WHEN ... THEN ? ELSE p.slug END clause plus SlugSpecified/Slug params let callers rename the slug; the no-op short-circuit and the response/audit slug now reflect the requested value via the slug := project.Slug; if req.Slug != nil { ... } block.
  • 409 on duplicate slugdb.IsDuplicateKeyError (MySQL 1062) maps the projects.workspace_slug_idx UNIQUE(workspace_id, slug) violation to codes.Data.Project.Duplicate, with a 409 spec block and ConflictErrorResponse $ref added to the path.
  • Test coverage extended — new 409_test.go (slug collision), an update slug only 200 case asserting name survival, and 400 cases for projectId length/charset; 400 tests now use ptr.P for optional slug.

The identifier scheme now fully mirrors getProject, the read-independent CASE-on-specified-flags UPDATE keeps the persisted write race-free, and the 409 path is only reachable when the slug is actually being changed (the message correctly interpolates the requested slug). Verified IsDuplicateKeyError, the Duplicate URN, FindProjectByWorkspaceAndId, ConflictErrorResponse, and ptr.P all exist and the unique constraint backs the conflict. Both humans approved at 1853feca. Mergeable as-is.

ℹ️ Slug mutation vs. deployment domains

The new request-body description states that changing the slug "affects the deployment domains generated for this project," but the handler only updates the projects row.

  • Deployment domains are built in svc/ctrl/worker/deploy/domains.go from <prefix>-<deploymentID>-<workspaceSlug>.<apex> — they key off the workspace slug, not the project slug.
  • Already-generated domains are not retroactively rewritten by this endpoint, so existing deployments keep their current hostnames after a slug change.

This is a forward-looking scope question for the human, not a bug — flagging it only so the doc wording and any downstream expectation (e.g. whether future deployments pick up the renamed slug) stay aligned.

Pullfrog

Pullfrog  | View workflow run | Using Claude Opus𝕏

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