Skip to content

refactor: update identity param for v2#3661

Merged
Flo4604 merged 11 commits intomainfrom
update-identity-param-for-v2
Jul 29, 2025
Merged

refactor: update identity param for v2#3661
Flo4604 merged 11 commits intomainfrom
update-identity-param-for-v2

Conversation

@ogzhanolguncu
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu commented Jul 25, 2025

What does this PR do?

This PR refactors api/routes/v2_identities_get_identity endpoint to make it queryable by both identityId and externalId using a single field.

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

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
  • 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

Summary by CodeRabbit

  • New Features

    • You can now delete or retrieve an identity using either the external ID or the internal identity ID in API requests.
    • The API request parameter for identity operations is now named identity and accepts both types of identifiers.
  • Bug Fixes

    • Improved validation and error messages for identity operations, including stricter minimum length requirements and clearer descriptions.
  • Refactor

    • Unified and streamlined identity lookup and deletion logic across the platform for consistency and reliability.
    • Enhanced workspace scoping in identity queries and operations.
    • Removed redundant code and improved database query parameterization.
  • Tests

    • Updated tests to reflect the new identity parameter and added coverage for using internal identity IDs.
  • Documentation

    • Clarified API documentation to explain that the identity field supports both internal and external identifiers.

@changeset-bot
Copy link

changeset-bot bot commented Jul 25, 2025

⚠️ No Changeset found

Latest commit: 6d1252f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jul 25, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
engineering ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 29, 2025 9:33am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
dashboard ⬜️ Ignored (Inspect) Visit Preview Jul 29, 2025 9:33am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 25, 2025

📝 Walkthrough
## Walkthrough

This change updates the API, handler logic, database queries, and tests to unify identity lookup and deletion by replacing separate external and internal identity identifiers with a single generic `identity` field. The database queries are refactored to accept workspace-scoped parameters and identify records by either internal ID or external ID. OpenAPI schemas and request bodies are updated accordingly.

## Changes

| File(s)                                                                                                                                                                                                                           | Change Summary                                                                                                      |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------|
| **OpenAPI schema and request bodies**<br>`go/apps/api/openapi/gen.go`<br>`go/apps/api/openapi/openapi-generated.yaml`<br>`go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml`<br>`go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml` | Replace `externalId` with `identity` in request bodies and OpenAPI schemas; update descriptions, constraints, and required fields. |
| **Handlers refactoring and query updates**<br>`go/apps/api/routes/v2_identities_delete_identity/handler.go`<br>`go/apps/api/routes/v2_identities_get_identity/handler.go`<br>`go/apps/api/routes/v2_identities_update_identity/handler.go`<br>`go/apps/api/routes/v2_keys_create_key/handler.go`<br>`go/apps/api/routes/v2_keys_get_key/handler.go`<br>`go/apps/api/routes/v2_keys_update_key/handler.go`<br>`go/apps/api/routes/v2_apis_list_keys/handler.go`<br>`go/apps/api/routes/v2_keys_whoami/handler.go` | Refactor handlers to use unified `FindIdentity` query with workspace-scoped parameters; update soft delete logic; streamline deletion helper; adjust type alias formatting. |
| **Test code updates**<br>`go/apps/api/routes/v2_identities_create_identity/200_test.go`<br>`go/apps/api/routes/v2_identities_delete_identity/200_test.go`<br>`go/apps/api/routes/v2_identities_delete_identity/400_test.go`<br>`go/apps/api/routes/v2_identities_delete_identity/401_test.go`<br>`go/apps/api/routes/v2_identities_delete_identity/403_test.go`<br>`go/apps/api/routes/v2_identities_delete_identity/404_test.go`<br>`go/apps/api/routes/v2_identities_get_identity/200_test.go`<br>`go/apps/api/routes/v2_identities_get_identity/400_test.go`<br>`go/apps/api/routes/v2_identities_get_identity/401_test.go`<br>`go/apps/api/routes/v2_identities_get_identity/403_test.go`<br>`go/apps/api/routes/v2_identities_get_identity/404_test.go`<br>`go/apps/api/routes/v2_identities_list_identities/200_test.go`<br>`go/apps/api/routes/v2_keys_update_key/200_test.go`<br>`go/apps/api/routes/v2_keys_update_key/three_state_test.go` | Update tests to use `identity` field; replace calls to separate identity queries with unified `FindIdentity`; add test for retrieval by internal ID; update soft delete calls. |
| **New and updated SQL queries and generated code**<br>`go/pkg/db/queries/identity_find.sql`<br>`go/pkg/db/identity_find.sql_generated.go`                                                                                                                                            | Add new SQL query and generated code for `FindIdentity` supporting workspace-scoped lookup by internal or external ID. |
| **Updated delete queries and generated code**<br>`go/pkg/db/queries/identity_delete.sql`<br>`go/pkg/db/identity_delete.sql_generated.go`                                                                                                                                       | Update delete query and generated code to accept workspace and identity parameters; delete by either internal or external ID. |
| **Updated soft delete queries and generated code**<br>`go/pkg/db/queries/identity_soft_delete.sql`<br>`go/pkg/db/identity_soft_delete.sql_generated.go`                                                                                                                             | Update soft delete query and generated code to accept workspace and identity parameters; soft delete by either internal or external ID. |
| **Database interface updates**<br>`go/pkg/db/querier_generated.go`                                                                                                                                                                                              | Update interface: remove separate internal/external ID methods; add unified `FindIdentity`; update delete and soft delete signatures; add new method for deleting old identities with ratelimits. |
| **Removed obsolete queries and generated code**<br>`go/pkg/db/queries/identity_find_by_external_id.sql`<br>`go/pkg/db/identity_find_by_external_id.sql_generated.go`<br>`go/pkg/db/queries/identity_find_by_id.sql`<br>`go/pkg/db/identity_find_by_id.sql_generated.go`                | Remove obsolete separate queries and generated code for internal and external ID lookups.                           |
| **Code style and comment formatting**<br>`go/pkg/codes/constants_gen.go`                                                                                                                                                                                                | Remove indentation from comment blocks for consistency; no functional changes.                                      |
| **Database schema update**<br>`go/pkg/db/schema.sql`                                                                                                                                                                                                          | Add composite index on `identities(workspace_id, id, deleted)` to optimize new queries.                             |
| **New SQL query and generated code for deleting old identities with ratelimits**<br>`go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go`<br>`go/pkg/db/queries/identity_delete_old_with_ratelimits.sql` | Add new query and generated code to delete old soft-deleted identities and their associated ratelimits by workspace and identity. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant API_Handler
    participant DB

    Client->>API_Handler: Request (identity: externalId or identityId)
    API_Handler->>DB: FindIdentity(workspaceId, identity, deleted=false)
    DB-->>API_Handler: Identity record or error
    alt Found
        API_Handler-->>Client: Success response
    else Not found
        API_Handler-->>Client: Not found error
    end
sequenceDiagram
    participant Client
    participant API_Handler
    participant DB

    Client->>API_Handler: Delete Identity (identity: externalId or identityId)
    API_Handler->>DB: SoftDeleteIdentity(workspaceId, identity)
    DB-->>API_Handler: Success or error
    alt Success
        API_Handler-->>Client: 200 OK
    else Error
        API_Handler-->>Client: Error response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Suggested labels

Core Team

Suggested reviewers

  • perkinsjr
  • imeyer

</details>

<!-- walkthrough_end -->

<!-- announcements_start -->

> [!NOTE]
> <details open="true">
> <summary>⚡️ Unit Test Generation is now available in beta!</summary>
> 
> Learn more [here](https://docs.coderabbit.ai/finishing-touches/unit-test-generation), or try it out under "Finishing Touches" below.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d29a7dc0e40e8c7f563bd6a196f37e80a630f8e8 and 6d1252f626d3014037a4e267129140a11f02b3e0.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `go/apps/api/openapi/openapi-generated.yaml` (2 hunks)
* `go/apps/api/routes/v2_identities_delete_identity/handler.go` (3 hunks)
* `go/apps/api/routes/v2_keys_whoami/handler.go` (2 hunks)
* `go/pkg/codes/constants_gen.go` (3 hunks)
* `go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go` (1 hunks)
* `go/pkg/db/querier_generated.go` (4 hunks)
* `go/pkg/db/queries/identity_delete_old_with_ratelimits.sql` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (8)</summary>

<details>
<summary>📓 Common learnings</summary>

Learnt from: ogzhanolguncu
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.


Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.


Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.


Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.


Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.


Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.


Learnt from: ogzhanolguncu
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
PR: #3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.


Learnt from: ogzhanolguncu
PR: #3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.


Learnt from: ogzhanolguncu
PR: #3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.


</details>
<details>
<summary>go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (2)</summary>

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Learnt from: Flo4604
PR: unkeyed/unkey#3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.403Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.

</details>
<details>
<summary>go/pkg/db/queries/identity_delete_old_with_ratelimits.sql (1)</summary>

Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

</details>
<details>
<summary>go/pkg/codes/constants_gen.go (5)</summary>

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Update relevant anchors when modifying associated code.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use `AIDEV-NOTE:`, `AIDEV-TODO:`, `AIDEV-BUSINESS_RULE:`, or `AIDEV-QUESTION:` (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove `AIDEV-*`s without explicit human instruction.

</details>
<details>
<summary>go/apps/api/routes/v2_identities_delete_identity/handler.go (6)</summary>

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses `ExternalId` field instead of the unified `Identity` field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic `Identity` field.

Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2876
File: apps/dashboard/components/logs/datetime/constants.ts:96-96
Timestamp: 2025-02-06T17:41:47.228Z
Learning: In the Unkey codebase, avoid hardcoding IDs or indices that depend on array positions, as the arrays may be modified in the future. Instead, use methods like `find` with unique identifiers or properties to locate specific items.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.491Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.

</details>
<details>
<summary>go/apps/api/routes/v2_keys_whoami/handler.go (5)</summary>

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses `ExternalId` field instead of the unified `Identity` field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic `Identity` field.

Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/apps/api/routes/v2_keys_create_key/handler.go:353-466
Timestamp: 2025-07-15T14:25:05.608Z
Learning: In the Unkey codebase, input validation for API endpoints is handled at the OpenAPI schema layer, which validates request fields like permission slugs (pattern: "^[a-zA-Z0-9_]+$", length: 1-100 characters) before requests reach the handler code. This validation occurs during the zen.BindBody call in handlers.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using `eq(table.workspaceId, ctx.workspace.id)` to prevent cross-workspace access.

</details>
<details>
<summary>go/apps/api/openapi/openapi-generated.yaml (13)</summary>

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses `ExternalId` field instead of the unified `Identity` field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic `Identity` field.

Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.

Learnt from: Flo4604
PR: unkeyed/unkey#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.

Learnt from: Flo4604
PR: unkeyed/unkey#2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

Learnt from: MichaelUnkey
PR: unkeyed/unkey#3173
File: apps/docs/security/delete-protection.mdx:32-36
Timestamp: 2025-04-22T17:33:28.162Z
Learning: In the Unkey dashboard UI for delete protection, the button/link to initiate the process is labeled "Disable Delete Protection" while the confirmation button is labeled "Disable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: MichaelUnkey
PR: unkeyed/unkey#3173
File: apps/docs/security/delete-protection.mdx:21-24
Timestamp: 2025-04-22T17:34:04.438Z
Learning: In the Unkey dashboard UI for enabling delete protection, the button/link to initiate the process is labeled "Enable Delete Protection" while the confirmation button is labeled "Enable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts:80-80
Timestamp: 2024-10-04T17:27:08.666Z
Learning: Ensure that audit log descriptions accurately reflect the action being performed, such as updating the `defaultPrefix`, and avoid incorrect references like 'name' when not applicable.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use `AIDEV-NOTE:`, `AIDEV-TODO:`, `AIDEV-BUSINESS_RULE:`, or `AIDEV-QUESTION:` (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove `AIDEV-*`s without explicit human instruction.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.

</details>
<details>
<summary>go/pkg/db/querier_generated.go (3)</summary>

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses `ExternalId` field instead of the unified `Identity` field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic `Identity` field.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.

Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

</details>

</details><details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>go/pkg/codes/constants_gen.go (1)</summary><blockquote>

<details>
<summary>go/pkg/urn/urn.go (1)</summary>

* `URN` (12-19)

</details>

</blockquote></details>
<details>
<summary>go/apps/api/routes/v2_identities_delete_identity/handler.go (7)</summary><blockquote>

<details>
<summary>go/apps/api/openapi/gen.go (2)</summary>

* `V2IdentitiesDeleteIdentityRequestBody` (636-639)
* `Identity` (141-150)

</details>
<details>
<summary>go/pkg/db/identity_find.sql_generated.go (1)</summary>

* `FindIdentityParams` (20-24)

</details>
<details>
<summary>go/pkg/db/models_generated.go (1)</summary>

* `Identity` (657-666)

</details>
<details>
<summary>go/pkg/db/identity_soft_delete.sql_generated.go (1)</summary>

* `SoftDeleteIdentityParams` (19-22)

</details>
<details>
<summary>go/pkg/db/handle_err_duplicate_key.go (1)</summary>

* `IsDuplicateKeyError` (7-13)

</details>
<details>
<summary>go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (1)</summary>

* `DeleteOldIdentityWithRatelimitsParams` (21-24)

</details>
<details>
<summary>go/pkg/fault/wrap.go (3)</summary>

* `Wrap` (25-67)
* `Internal` (75-89)
* `Public` (97-111)

</details>

</blockquote></details>
<details>
<summary>go/apps/api/routes/v2_keys_whoami/handler.go (2)</summary><blockquote>

<details>
<summary>go/apps/api/openapi/gen.go (3)</summary>

* `V2KeysWhoamiRequestBody` (1365-1370)
* `V2KeysWhoamiResponseBody` (1373-1378)
* `Identity` (141-150)

</details>
<details>
<summary>go/pkg/db/identity_find.sql_generated.go (1)</summary>

* `FindIdentityParams` (20-24)

</details>

</blockquote></details>
<details>
<summary>go/pkg/db/querier_generated.go (6)</summary><blockquote>

<details>
<summary>go/pkg/hydra/store/db.go (1)</summary>

* `DBTX` (8-13)

</details>
<details>
<summary>go/pkg/db/identity_delete.sql_generated.go (1)</summary>

* `DeleteIdentityParams` (18-21)

</details>
<details>
<summary>go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (1)</summary>

* `DeleteOldIdentityWithRatelimitsParams` (21-24)

</details>
<details>
<summary>go/pkg/db/identity_find.sql_generated.go (1)</summary>

* `FindIdentityParams` (20-24)

</details>
<details>
<summary>go/pkg/db/models_generated.go (1)</summary>

* `Identity` (657-666)

</details>
<details>
<summary>go/pkg/db/identity_soft_delete.sql_generated.go (1)</summary>

* `SoftDeleteIdentityParams` (19-22)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ 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)</summary>

* GitHub Check: Test Go API Local / Test
* GitHub Check: Test Agent Local / test_agent_local
* GitHub Check: Test API / API Test Local

</details>

<details>
<summary>🔇 Additional comments (13)</summary><blockquote>

<details>
<summary>go/pkg/db/queries/identity_delete_old_with_ratelimits.sql (1)</summary>

`1-7`: **LGTM: Well-structured SQL query for atomic deletion.**

The SQL query correctly implements atomic deletion of soft-deleted identities and their associated ratelimits. The LEFT JOIN ensures all identities are processed regardless of ratelimit associations, and the workspace scoping with flexible identity matching (by ID or external ID) aligns with the unified identity parameter approach.

</details>
<details>
<summary>go/apps/api/routes/v2_keys_whoami/handler.go (2)</summary>

`24-27`: **LGTM: Improved type alias declaration.**

The grouped type alias declaration improves code organization without changing functionality.

---

`166-166`: **LGTM: Proper workspace-scoped identity lookup.**

The refactoring from `FindIdentityByID` to `FindIdentity` with workspace scoping enhances security by ensuring identity queries are properly scoped to the authorized workspace. The unified approach using the `Identity` parameter aligns with the PR objectives.

</details>
<details>
<summary>go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (2)</summary>

`21-24`: **LGTM: Well-structured parameter definition.**

The parameter struct correctly defines the workspace and identity fields needed for the delete operation, maintaining consistency with the unified identity approach.

---

`34-37`: **LGTM: Correct parameter passing for flexible identity matching.**

The generated method correctly passes the `Identity` parameter twice to support matching against both internal ID and external ID in the SQL query, enabling flexible identity identification within workspace scope.

</details>
<details>
<summary>go/apps/api/routes/v2_identities_delete_identity/handler.go (4)</summary>

`21-24`: **LGTM: Consistent type alias declaration.**

The grouped type alias declaration maintains consistency with other handlers in the codebase.

---

`74-78`: **LGTM: Enhanced security with workspace-scoped identity lookup.**

The refactoring from `FindIdentityByExternalID` to `FindIdentity` with workspace scoping improves security by ensuring identity queries are properly constrained to the authorized workspace context.

---

`101-104`: **LGTM: Unified parameter approach for soft delete operations.**

The migration to `SoftDeleteIdentityParams` struct with workspace and identity fields maintains consistency with the unified identity parameter approach while ensuring proper workspace scoping.




Also applies to: 123-127

---

`110-120`: **LGTM: Atomic deletion improves reliability.**

The replacement of manual deletion logic with the `DeleteOldIdentityWithRatelimits` query method provides atomic deletion of both identities and their associated ratelimits, improving data consistency and reducing the risk of partial deletions. The comprehensive error handling with proper fault codes enhances debugging capabilities.

</details>
<details>
<summary>go/pkg/db/querier_generated.go (4)</summary>

`23-28`: **LGTM! Workspace-scoped identity deletion.**

The refactored `DeleteIdentity` method now properly scopes deletion by workspace and supports unified identity lookup via either `id` or `external_id`.

---

`87-95`: **Good addition for atomic cleanup of soft-deleted identities.**

The new `DeleteOldIdentityWithRatelimits` method ensures atomic deletion of both identities and their associated ratelimits, preventing orphaned data. The workspace scoping and unified identity lookup are consistent with other methods.

---

`174-181`: **Excellent unification of identity lookup methods.**

Successfully consolidates `FindIdentityByExternalID` and `FindIdentityByID` into a single `FindIdentity` method, reducing API surface area while maintaining flexibility to query by either identifier type.

---

`1257-1263`: **Consistent workspace scoping for soft delete.**

The `SoftDeleteIdentity` method correctly implements workspace scoping and unified identity lookup, maintaining consistency with the other refactored identity methods.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details>
<summary>🧪 Generate unit tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post copyable unit tests in a comment
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `update-identity-param-for-v2`

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=unkeyed/unkey&utm_content=3661):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@github-actions
Copy link
Contributor

github-actions bot commented Jul 25, 2025

Thank you for following the naming conventions for pull request titles! 🙏

Copy link
Collaborator

bro what are these large PRs??? :

Copy link
Contributor Author

Nice try

Copy link
Collaborator

😄

Copy link
Contributor Author

🤣

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: 13

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8eab750 and 2f2da3c.

📒 Files selected for processing (37)
  • go/apps/api/openapi/gen.go (2 hunks)
  • go/apps/api/openapi/openapi-generated.yaml (3 hunks)
  • go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (1 hunks)
  • go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml (1 hunks)
  • go/apps/api/routes/v2_apis_list_keys/handler.go (2 hunks)
  • go/apps/api/routes/v2_identities_create_identity/200_test.go (10 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/200_test.go (6 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/400_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/401_test.go (8 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/403_test.go (7 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/404_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/handler.go (4 hunks)
  • go/apps/api/routes/v2_identities_get_identity/200_test.go (7 hunks)
  • go/apps/api/routes/v2_identities_get_identity/400_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_get_identity/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/403_test.go (2 hunks)
  • go/apps/api/routes/v2_identities_get_identity/404_test.go (2 hunks)
  • go/apps/api/routes/v2_identities_get_identity/handler.go (3 hunks)
  • go/apps/api/routes/v2_identities_list_identities/200_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/handler.go (2 hunks)
  • go/apps/api/routes/v2_keys_create_key/handler.go (2 hunks)
  • go/apps/api/routes/v2_keys_get_key/handler.go (2 hunks)
  • go/apps/api/routes/v2_keys_update_key/200_test.go (2 hunks)
  • go/apps/api/routes/v2_keys_update_key/handler.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/three_state_test.go (3 hunks)
  • go/pkg/codes/constants_gen.go (3 hunks)
  • go/pkg/db/identity_delete.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find_by_external_id.sql_generated.go (0 hunks)
  • go/pkg/db/identity_find_by_id.sql_generated.go (0 hunks)
  • go/pkg/db/identity_soft_delete.sql_generated.go (1 hunks)
  • go/pkg/db/querier_generated.go (3 hunks)
  • go/pkg/db/queries/identity_delete.sql (1 hunks)
  • go/pkg/db/queries/identity_find.sql (1 hunks)
  • go/pkg/db/queries/identity_find_by_external_id.sql (0 hunks)
  • go/pkg/db/queries/identity_find_by_id.sql (0 hunks)
  • go/pkg/db/queries/identity_soft_delete.sql (1 hunks)
💤 Files with no reviewable changes (4)
  • go/pkg/db/queries/identity_find_by_id.sql
  • go/pkg/db/queries/identity_find_by_external_id.sql
  • go/pkg/db/identity_find_by_external_id.sql_generated.go
  • go/pkg/db/identity_find_by_id.sql_generated.go
🧰 Additional context used
🧬 Code Graph Analysis (19)
go/apps/api/routes/v2_identities_get_identity/401_test.go (1)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/apps/api/routes/v2_identities_delete_identity/400_test.go (3)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)
  • Request (22-22)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/codes/constants_gen.go (1)
go/pkg/urn/urn.go (1)
  • URN (12-19)
go/apps/api/routes/v2_identities_delete_identity/404_test.go (5)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)
  • Request (22-22)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
go/pkg/testutil/seed/seed.go (1)
  • Resources (20-25)
go/apps/api/routes/v2_identities_list_identities/200_test.go (1)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
go/apps/api/routes/v2_identities_get_identity/handler.go (3)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_delete_identity/401_test.go (3)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)
  • Request (22-22)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_get_identity/403_test.go (1)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/apps/api/routes/v2_identities_get_identity/400_test.go (4)
go/apps/api/openapi/gen.go (2)
  • Meta (333-336)
  • Identity (142-151)
go/apps/api/routes/v2_identities_get_identity/handler.go (1)
  • Request (19-19)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/uid/uid.go (1)
  • TestPrefix (24-24)
go/apps/api/routes/v2_keys_create_key/handler.go (3)
go/apps/api/openapi/gen.go (3)
  • V2KeysCreateKeyRequestBody (958-1032)
  • V2KeysCreateKeyResponseBody (1035-1040)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_apis_list_keys/handler.go (3)
go/apps/api/openapi/gen.go (2)
  • V2ApisListKeysRequestBody (622-654)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_keys_update_key/handler.go (2)
go/apps/api/openapi/gen.go (3)
  • V2KeysUpdateKeyRequestBody (1444-1491)
  • V2KeysUpdateKeyResponseBody (1494-1500)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/apps/api/routes/v2_identities_update_identity/handler.go (3)
go/apps/api/openapi/gen.go (3)
  • V2IdentitiesUpdateIdentityRequestBody (779-796)
  • V2IdentitiesUpdateIdentityResponseBody (799-804)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_keys_get_key/handler.go (3)
go/apps/api/openapi/gen.go (3)
  • V2KeysGetKeyRequestBody (1080-1101)
  • V2KeysGetKeyResponseBody (1110-1115)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_create_identity/200_test.go (3)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/testutil/seed/seed.go (1)
  • Resources (20-25)
go/apps/api/routes/v2_identities_delete_identity/handler.go (8)
go/apps/api/openapi/gen.go (3)
  • V2IdentitiesDeleteIdentityRequestBody (717-720)
  • V2IdentitiesDeleteIdentityResponseBody (723-726)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
go/pkg/db/handle_err_duplicate_key.go (1)
  • IsDuplicateKeyError (7-13)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/fault/wrap.go (3)
  • Wrap (25-67)
  • Internal (75-89)
  • Public (97-111)
go/pkg/db/identity_delete.sql_generated.go (1)
  • DeleteIdentityParams (18-21)
go/apps/api/openapi/gen.go (1)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_find.sql_generated.go (1)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/querier_generated.go (5)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/identity_delete.sql_generated.go (1)
  • DeleteIdentityParams (18-21)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
⏰ 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: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
🔇 Additional comments (59)
go/pkg/codes/constants_gen.go (1)

9-126: Comment formatting changes look good.

The alignment of comment blocks to the left margin improves readability. Since this is a generated file (as indicated by the header comment), these formatting changes likely result from updates to the code generator, which is appropriate.

go/apps/api/routes/v2_identities_get_identity/401_test.go (1)

24-26: Field rename aligns with the refactor objectives.

The change from ExternalId to Identity is consistent with the PR's goal to unify identity lookup using a single field that can accept either external ID or internal ID.

go/apps/api/routes/v2_identities_list_identities/200_test.go (1)

200-203: Parameter struct usage properly implements workspace scoping.

The change to use db.SoftDeleteIdentityParams with both Identity and WorkspaceID fields correctly implements workspace scoping for the soft delete operation, which aligns with the refactor's goal to unify identity operations.

go/apps/api/routes/v2_identities_get_identity/404_test.go (3)

37-37: Field rename is consistent with the refactor.

The change from ExternalId to Identity aligns with the unified identity lookup approach.


70-73: Proper workspace scoping implementation.

The use of db.SoftDeleteIdentityParams with both Identity and WorkspaceID correctly implements workspace scoping for the soft delete operation.


81-81: Consistent field usage in test scenario.

The Identity field usage is consistent with the refactor's unified approach for identity lookup.

go/apps/api/routes/v2_identities_get_identity/400_test.go (2)

30-30: Good update to test description.

The test name change from "missing externalID" to "missing identity" correctly reflects the unified identity parameter that can accept both external IDs and internal identity IDs.


62-62: Field name changes are correct.

The changes from ExternalId to Identity are consistent with the refactor to unify identity parameters. The use of uid.New(uid.TestPrefix) generates appropriate test data for the unified identity field.

Also applies to: 76-76

go/pkg/db/queries/identity_delete.sql (1)

2-4: Excellent addition of workspace scoping and flexible identity matching.

The updated query improves both security and flexibility:

  1. Security: The workspace_id constraint prevents accidental cross-workspace deletions
  2. Flexibility: The OR condition (id = sqlc.arg(identity) OR external_id = sqlc.arg(identity)) allows deletion by either internal ID or external ID using a single parameter
  3. Consistency: Aligns with the broader refactor to unify identity parameters

The use of sqlc.arg() parameters prevents SQL injection, and the workspace scoping ensures proper data isolation.

go/apps/api/routes/v2_identities_get_identity/handler.go (2)

63-67: Excellent refactor to unified identity lookup.

The change from FindIdentityByExternalID to FindIdentity with the new parameter structure improves the API's flexibility:

  1. Unified lookup: Single method can find identities by either internal ID or external ID
  2. Workspace scoping: Maintains security by scoping to auth.AuthorizedWorkspaceID
  3. Active records: Deleted: false ensures only active identities are returned
  4. Consistent naming: req.Identity aligns with the unified parameter approach

The error handling logic remains unchanged, ensuring backward compatibility in error responses.


119-119: Good modernization to use any type alias.

The change from map[string]interface{} to map[string]any follows modern Go idioms. The any alias (introduced in Go 1.18) is more readable and is the preferred way to express the empty interface type in contemporary Go code.

Also applies to: 128-128

go/apps/api/routes/v2_identities_get_identity/403_test.go (1)

75-75: Field rename is consistent with the refactor.

The change from ExternalId to Identity correctly reflects the unified identity parameter that can accept both external IDs and internal identity IDs. The test logic remains valid for checking permission enforcement.

go/apps/api/routes/v2_identities_delete_identity/403_test.go (1)

34-34: LGTM! Field name change aligns with the unified identity parameter refactor.

The consistent replacement of ExternalId with Identity in all test request structs correctly reflects the API refactoring to use a single field that accepts either internal or external identity IDs.

Also applies to: 53-53, 72-72, 91-91, 110-110, 132-134, 153-153

go/apps/api/routes/v2_identities_delete_identity/401_test.go (1)

33-33: LGTM! Consistent field renaming across all test cases.

The replacement of ExternalId with Identity in request structs is consistent with the unified identity parameter refactor and maintains test coverage for all authorization scenarios.

Also applies to: 51-53, 71-71, 89-91, 109-109, 127-127, 150-150, 168-168

go/apps/api/routes/v2_identities_get_identity/200_test.go (2)

76-76: LGTM! Field name changes align with the unified identity parameter.

The consistent replacement of ExternalId with Identity in request structs correctly reflects the API refactoring.

Also applies to: 119-119, 154-154, 241-241, 321-321, 369-369


378-392: Excellent addition! New test validates querying by internal identity ID.

This new test case effectively demonstrates and validates the core functionality of the refactor - the ability to query identities using the internal identity ID in addition to the external ID. The test follows good practices by creating an identity and then retrieving it using the returned internal ID.

go/apps/api/routes/v2_identities_delete_identity/404_test.go (2)

37-37: LGTM! Consistent field renaming across test cases.

The replacement of ExternalId with Identity in request structs aligns with the unified identity parameter refactor.

Also applies to: 73-73, 107-107, 121-121


100-103: LGTM! Database method updated to use workspace-scoped parameters.

The change from a single identity ID parameter to the SoftDeleteIdentityParams struct with both Identity and WorkspaceID fields improves security by ensuring workspace scoping and aligns with the database layer refactoring.

go/apps/api/routes/v2_identities_update_identity/handler.go (3)

24-27: LGTM! Clean type declaration grouping.

The grouping of type declarations is a good formatting improvement that enhances readability.


115-119: LGTM! Database query updated to use unified identity lookup method.

The change from FindIdentityByExternalID to FindIdentity with the new parameter struct correctly implements the unified identity lookup approach with workspace scoping. The new method provides better security by ensuring workspace isolation.


115-119: No field naming inconsistency to address
Both the create and update identity request bodies in openapi/gen.go consistently use externalId rather than identity. No changes are required to the update endpoint’s field name.

Likely an incorrect or invalid review comment.

go/apps/api/routes/v2_apis_list_keys/handler.go (2)

23-26: LGTM! Clean type alias definitions.

The type alias definitions improve code readability and maintainability.


179-181: Excellent refactor to unified identity lookup.

The change from FindIdentityByExternalID to FindIdentity with structured parameters improves flexibility by allowing queries by either internal ID or external ID while maintaining proper workspace scoping. The logic correctly handles the case where identity is not found by returning an empty result.

go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml (1)

3-10: Excellent API design improvement for flexible identity lookup.

The changes enhance the API's usability:

  • Renaming externalId to identity makes the field more intuitive and flexible
  • The updated description clearly communicates that both external IDs and internal identity IDs are supported
  • The increased minLength constraint (3 characters) is reasonable for preventing very short identifiers
  • This aligns well with the broader refactor to unify identity handling across the codebase
go/apps/api/routes/v2_keys_update_key/three_state_test.go (2)

271-273: LGTM! Updated to use unified identity lookup with workspace scoping.

The test correctly updates to use the new FindIdentity method with structured parameters that include workspace scoping. This improves security by ensuring identity lookups are properly scoped to the workspace context.

Also applies to: 318-320


347-352: Good use of modern Go type alias.

The change from map[string]interface{} to map[string]any uses the more concise type alias introduced in Go 1.18+, improving code readability.

go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (1)

3-10: Excellent consistency with the getIdentity API design.

The changes mirror the improvements made to the getIdentity endpoint:

  • The identity field name is more intuitive than externalId
  • The description clearly explains support for both external IDs and internal identity IDs
  • The minLength: 3 constraint prevents very short identifiers
  • This maintains API consistency across identity-related endpoints
go/pkg/db/queries/identity_soft_delete.sql (1)

2-5: Excellent SQL design for unified identity handling with workspace scoping.

The updated query improves both security and flexibility:

  • Workspace scoping: The workspace_id filter ensures operations are properly isolated by workspace
  • Unified identity matching: Using the same parameter to match either id OR external_id elegantly supports the flexible identity lookup
  • Clean SQL pattern: The OR condition is efficient and maintains query performance

This aligns perfectly with the broader refactor to unify identity handling across the codebase.

go/apps/api/routes/v2_keys_update_key/200_test.go (2)

109-109: LGTM: Modern Go type alias usage

Good use of the any type alias instead of interface{} for improved readability.


137-139: LGTM: Correctly updated to use unified identity lookup

The change from FindIdentityByID to FindIdentity properly aligns with the refactor to unify identity lookups. The test correctly uses the internal identity ID and workspace scoping.

go/pkg/db/identity_find.sql_generated.go (3)

12-18: LGTM: Well-designed unified identity query

The SQL query correctly implements the unified identity lookup with proper workspace scoping and the ability to match by either external ID or internal ID using a single parameter.


20-24: LGTM: Clean parameter structure

The FindIdentityParams struct appropriately captures all necessary parameters for the unified identity lookup with proper database tags.


33-52: LGTM: Correct method implementation

The generated FindIdentity method properly implements the unified lookup logic. The double usage of arg.Identity (lines 36-37) is correct since the SQL query needs the same value for both external_id and id comparisons in the OR condition.

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

718-719: LGTM! Field rename improves API clarity.

The rename from ExternalId to Identity with the updated comment clearly communicates that this field now accepts both external IDs and internal identity IDs, supporting the unified identity lookup approach. The JSON tag remains consistent.


730-731: LGTM! Consistent field rename across identity operations.

The Identity field rename matches the pattern used in the delete request body, maintaining consistency across identity operations. The updated comment correctly explains that both external IDs and internal identity IDs are accepted.

go/apps/api/routes/v2_keys_create_key/handler.go (2)

29-32: Clean formatting improvement.

Grouping the type aliases into a single type block improves code organization and readability.


208-212: Good refactor to unified identity lookup with workspace scoping.

The change from FindIdentityByExternalID to FindIdentity is part of a beneficial refactor that:

  • Adds workspace scoping for improved security
  • Unifies identity lookup to handle both internal and external IDs
  • Standardizes parameter structures across the codebase
go/apps/api/routes/v2_keys_get_key/handler.go (2)

25-28: Clean formatting improvement.

Consistent with other files in this PR, grouping type aliases improves code organization.


256-260: Consistent refactor to unified identity lookup.

The update from FindIdentityByID to FindIdentity maintains the same functionality while adding workspace scoping and preparing for the unified identity parameter approach.

go/pkg/db/identity_delete.sql_generated.go (1)

12-31: Well-designed database layer update for unified identity deletion.

The changes improve both security and flexibility:

  • Security: Workspace scoping prevents unauthorized cross-workspace deletions
  • Flexibility: Single method handles deletion by either internal ID or external ID
  • Type safety: Structured parameters replace positional string arguments

The implementation correctly passes the Identity parameter twice to match both id and external_id fields in the SQL WHERE clause.

go/apps/api/routes/v2_keys_update_key/handler.go (3)

25-28: Consistent formatting improvement.

Type alias grouping maintains consistency across the codebase.


158-162: Consistent refactor to unified identity lookup.

The update adds workspace scoping and standardizes the parameter structure while maintaining the same functionality for finding existing identities during key updates.


193-197: Proper refactor of fallback identity lookup.

The error handling path correctly uses the same unified FindIdentity method, maintaining consistent behavior when dealing with duplicate identity creation scenarios.

go/pkg/db/identity_soft_delete.sql_generated.go (1)

12-33: Consistent database layer improvement for soft deletion.

The soft delete operation benefits from the same improvements as hard deletion:

  • Security: Workspace scoping prevents unauthorized access
  • Flexibility: Unified identity parameter handles both ID types
  • Consistency: Aligns with the broader refactoring pattern

The SQL logic correctly preserves the soft delete behavior (setting deleted = 1) while adding enhanced filtering capabilities.

go/apps/api/routes/v2_identities_create_identity/200_test.go (3)

52-56: LGTM! Unified identity lookup implementation is correct.

The migration from FindIdentityByID to FindIdentity properly demonstrates the new unified approach. The test correctly uses the internal identityID in the Identity field with proper workspace scoping.


111-115: LGTM! External ID lookup works correctly with unified method.

The test properly demonstrates that the unified FindIdentity method can handle external IDs in the Identity field. The workspace scoping and deletion filtering are correctly applied.


133-137: Excellent test coverage for the unified identity lookup.

All test cases consistently use the new FindIdentity method with the FindIdentityParams struct. The tests properly validate that external IDs work seamlessly with the unified approach, and workspace scoping is correctly applied throughout.

Also applies to: 174-178, 233-237, 309-313, 350-354, 380-384, 420-424

go/apps/api/routes/v2_identities_delete_identity/handler.go (4)

21-24: LGTM! Type aliases updated correctly for unified identity field.

The request and response types now properly reference the updated OpenAPI definitions that use the Identity field instead of ExternalId, supporting both internal and external ID lookups.


73-77: LGTM! Identity lookup correctly implemented with workspace scoping.

The unified FindIdentity method properly uses the req.Identity field with appropriate workspace scoping and deletion filtering. This allows the endpoint to accept either internal or external IDs seamlessly.


100-103: LGTM! Soft delete operations properly use unified parameter struct.

The SoftDeleteIdentity calls correctly use the new parameter struct with workspace scoping and the identity field from the request. The retry logic maintains the same parameter usage for consistency.

Also applies to: 114-117


206-229: LGTM! Hard delete function updated correctly with improved error handling.

The deleteOldIdentity function properly uses the new DeleteIdentity method with the parameter struct including workspace scoping. The error handling correctly ignores NotFound errors for both rate limit and identity deletion, which is appropriate for cleanup operations.

go/apps/api/openapi/openapi-generated.yaml (2)

1-5: Avoid direct edits to generated bundle

This file is regenerated by generate_bundle.go; any manual tweaks will disappear on the next run. Please ensure the underlying OpenAPI YAML fragments are updated instead.


416-424: Re-evaluate minLength: 3 constraint

Some integrations may legitimately use identifiers shorter than three characters (e.g. id1). Either lower the limit (e.g. minLength: 1) or document clearly why a minimum of three is required.

go/apps/api/routes/v2_identities_delete_identity/200_test.go (2)

85-89: LGTM: Unified identity lookup implementation looks correct.

The refactoring from FindIdentityByExternalID to FindIdentity is implemented correctly with proper workspace scoping and the generic Identity parameter.


94-94: LGTM: Request field rename aligns with API unification.

The field rename from ExternalId to Identity is consistent with the broader refactoring to support both internal and external identity references.

go/pkg/db/querier_generated.go (4)

23-28: LGTM: Well-designed unified deletion interface.

The DeleteIdentity method now properly enforces workspace scoping and supports both internal and external identity references through a single parameter. The SQL query correctly uses OR logic to match either ID type.


155-162: LGTM: Excellent consolidation of identity lookup methods.

The unified FindIdentity method successfully replaces the separate FindIdentityByExternalID and FindIdentityByID methods while adding proper workspace scoping. The SQL query design using (external_id = ? OR id = ?) elegantly handles both identity types.


1140-1146: LGTM: Consistent soft delete implementation.

The SoftDeleteIdentity method follows the same unified pattern as the other identity operations, with proper workspace scoping and support for both identity types.


155-162: ✅ Parameter binding order is correct

The generated code in go/pkg/db/identity_find.sql_generated.go passes parameters as:

  • arg.WorkspaceID
  • arg.Identity
  • arg.Identity
  • arg.Deleted

This matches the SQL placeholders (?, ?, ?, ?) in
WHERE workspace_id = ? AND (external_id = ? OR id = ?) AND deleted = ?. No changes required.

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

🔭 Outside diff range comments (1)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)

206-229: Improved deleteOldIdentity implementation

The refactored function is more efficient by removing redundant lookups and properly handling not-found errors. The workspace-scoped deletion ensures proper tenant isolation.

Consider extracting the error checking pattern to reduce duplication:

func deleteOldIdentity(ctx context.Context, tx db.DBTX, workspaceID, identityID string) error {
+	handleNotFound := func(err error) error {
+		if err != nil && !db.IsNotFound(err) {
+			return err
+		}
+		return nil
+	}
+
	err := db.Query.DeleteManyRatelimitsByIdentityID(ctx, tx, sql.NullString{String: identityID, Valid: true})
-	if err != nil && !db.IsNotFound(err) {
+	if err = handleNotFound(err); err != nil {
		return fault.Wrap(err,
			fault.Code(codes.App.Internal.ServiceUnavailable.URN()),
			fault.Internal("database failed to delete identity ratelimits"),
			fault.Public("Failed to delete Identity ratelimits."),
		)
	}

	err = db.Query.DeleteIdentity(ctx, tx, db.DeleteIdentityParams{
		WorkspaceID: workspaceID,
		Identity:    identityID,
	})
-	if err != nil && !db.IsNotFound(err) {
+	if err = handleNotFound(err); err != nil {
		return fault.Wrap(err,
			fault.Code(codes.App.Internal.ServiceUnavailable.URN()),
			fault.Internal("database failed to delete identity"),
			fault.Public("Failed to delete Identity."),
		)
	}

	return nil
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8eab750 and 2f2da3c.

📒 Files selected for processing (37)
  • go/apps/api/openapi/gen.go (2 hunks)
  • go/apps/api/openapi/openapi-generated.yaml (3 hunks)
  • go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (1 hunks)
  • go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml (1 hunks)
  • go/apps/api/routes/v2_apis_list_keys/handler.go (2 hunks)
  • go/apps/api/routes/v2_identities_create_identity/200_test.go (10 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/200_test.go (6 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/400_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/401_test.go (8 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/403_test.go (7 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/404_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/handler.go (4 hunks)
  • go/apps/api/routes/v2_identities_get_identity/200_test.go (7 hunks)
  • go/apps/api/routes/v2_identities_get_identity/400_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_get_identity/401_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/403_test.go (2 hunks)
  • go/apps/api/routes/v2_identities_get_identity/404_test.go (2 hunks)
  • go/apps/api/routes/v2_identities_get_identity/handler.go (3 hunks)
  • go/apps/api/routes/v2_identities_list_identities/200_test.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/handler.go (2 hunks)
  • go/apps/api/routes/v2_keys_create_key/handler.go (2 hunks)
  • go/apps/api/routes/v2_keys_get_key/handler.go (2 hunks)
  • go/apps/api/routes/v2_keys_update_key/200_test.go (2 hunks)
  • go/apps/api/routes/v2_keys_update_key/handler.go (3 hunks)
  • go/apps/api/routes/v2_keys_update_key/three_state_test.go (3 hunks)
  • go/pkg/codes/constants_gen.go (3 hunks)
  • go/pkg/db/identity_delete.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find_by_external_id.sql_generated.go (0 hunks)
  • go/pkg/db/identity_find_by_id.sql_generated.go (0 hunks)
  • go/pkg/db/identity_soft_delete.sql_generated.go (1 hunks)
  • go/pkg/db/querier_generated.go (3 hunks)
  • go/pkg/db/queries/identity_delete.sql (1 hunks)
  • go/pkg/db/queries/identity_find.sql (1 hunks)
  • go/pkg/db/queries/identity_find_by_external_id.sql (0 hunks)
  • go/pkg/db/queries/identity_find_by_id.sql (0 hunks)
  • go/pkg/db/queries/identity_soft_delete.sql (1 hunks)
💤 Files with no reviewable changes (4)
  • go/pkg/db/queries/identity_find_by_external_id.sql
  • go/pkg/db/queries/identity_find_by_id.sql
  • go/pkg/db/identity_find_by_id.sql_generated.go
  • go/pkg/db/identity_find_by_external_id.sql_generated.go
🧰 Additional context used
🧬 Code Graph Analysis (15)
go/pkg/codes/constants_gen.go (1)
go/pkg/urn/urn.go (1)
  • URN (12-19)
go/apps/api/routes/v2_identities_delete_identity/400_test.go (3)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)
  • Request (22-22)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_get_identity/403_test.go (1)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/apps/api/routes/v2_identities_get_identity/401_test.go (1)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/apps/api/routes/v2_identities_get_identity/handler.go (3)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_list_identities/200_test.go (1)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
go/apps/api/routes/v2_keys_create_key/handler.go (3)
go/apps/api/openapi/gen.go (3)
  • V2KeysCreateKeyRequestBody (958-1032)
  • V2KeysCreateKeyResponseBody (1035-1040)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_apis_list_keys/handler.go (3)
go/apps/api/openapi/gen.go (2)
  • V2ApisListKeysRequestBody (622-654)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_soft_delete.sql_generated.go (2)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/identity_find.sql_generated.go (1)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/apps/api/openapi/gen.go (1)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_update_identity/handler.go (3)
go/apps/api/openapi/gen.go (3)
  • V2IdentitiesUpdateIdentityRequestBody (779-796)
  • V2IdentitiesUpdateIdentityResponseBody (799-804)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_delete_identity/handler.go (8)
go/apps/api/openapi/gen.go (3)
  • V2IdentitiesDeleteIdentityRequestBody (717-720)
  • V2IdentitiesDeleteIdentityResponseBody (723-726)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
go/pkg/db/handle_err_duplicate_key.go (1)
  • IsDuplicateKeyError (7-13)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/fault/wrap.go (3)
  • Wrap (25-67)
  • Internal (75-89)
  • Public (97-111)
go/pkg/db/identity_delete.sql_generated.go (1)
  • DeleteIdentityParams (18-21)
go/pkg/db/querier_generated.go (5)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/identity_delete.sql_generated.go (1)
  • DeleteIdentityParams (18-21)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/db/identity_soft_delete.sql_generated.go (1)
  • SoftDeleteIdentityParams (19-22)
go/apps/api/routes/v2_keys_get_key/handler.go (3)
go/apps/api/openapi/gen.go (3)
  • V2KeysGetKeyRequestBody (1080-1101)
  • V2KeysGetKeyResponseBody (1110-1115)
  • Identity (142-151)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
⏰ 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: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
🔇 Additional comments (61)
go/apps/api/routes/v2_identities_get_identity/401_test.go (1)

25-25: LGTM! Field rename aligns with the unified identity parameter approach.

The change from ExternalId to Identity is consistent with the broader refactor to support both external and internal identity IDs through a single parameter.

go/apps/api/routes/v2_identities_get_identity/handler.go (2)

63-67: Excellent refactor to unified identity lookup.

The change from FindIdentityByExternalID to FindIdentity with the new parameter structure successfully implements the core objective of supporting both external and internal identity IDs through a single parameter. The workspace scoping is properly maintained for security.


119-119: Good modernization of Go type usage.

The change from map[string]interface{} to map[string]any is a nice modernization that takes advantage of Go 1.18+ type alias for interface{}.

Also applies to: 128-128

go/apps/api/routes/v2_identities_get_identity/404_test.go (2)

37-37: LGTM! Field renames are consistent with the unified identity parameter approach.

The changes from ExternalId to Identity align with the refactor to support both external and internal identity IDs through a single parameter.

Also applies to: 81-81


70-73: Good improvement to workspace-scoped identity deletion.

The change to use a parameter struct for SoftDeleteIdentity with explicit WorkspaceID and Identity fields improves security by ensuring workspace scoping and supports the unified identity parameter approach.

go/pkg/db/queries/identity_find.sql (1)

1-6: Excellent unified identity lookup query design.

This SQL query cleanly supports lookups by either external_id or id with proper workspace_id scoping and deleted filtering.

Please verify that your database schema includes indexes to optimize this OR condition, for example:

  • Composite index on (workspace_id, external_id, deleted)
  • Composite index on (workspace_id, id, deleted)

If these indexes are defined in your migrations or elsewhere in the schema, confirm they’re applied to avoid performance issues.

go/apps/api/routes/v2_identities_get_identity/403_test.go (1)

75-75: LGTM! Consistent field rename aligns with unified identity parameter.

The changes correctly update the request struct to use the new Identity field instead of ExternalId, maintaining the same test logic while supporting the unified identity lookup approach.

Also applies to: 93-93

go/apps/api/routes/v2_identities_delete_identity/403_test.go (1)

34-34: LGTM! Comprehensive field rename maintains test consistency.

All test cases correctly update from ExternalId to Identity field, maintaining the original test logic while supporting the unified identity parameter approach. The multi-line struct format on lines 132-134 also improves readability.

Also applies to: 53-53, 72-72, 91-91, 110-110, 132-134, 153-153

go/apps/api/routes/v2_identities_list_identities/200_test.go (1)

200-203: LGTM! Updated to use workspace-scoped parameter struct.

The change correctly updates the soft delete operation to use the new db.SoftDeleteIdentityParams struct, which includes both Identity and WorkspaceID fields. This ensures proper workspace scoping and aligns with the unified identity parameter approach.

go/apps/api/routes/v2_identities_update_identity/handler.go (1)

24-27: LGTM! Cleaner type declaration format.

The grouped type declaration improves code organization and readability.

go/apps/api/routes/v2_identities_delete_identity/401_test.go (1)

33-33: LGTM! Consistent field updates across all unauthorized test cases.

All test cases correctly update from ExternalId to Identity field, maintaining the original authentication and authorization test logic. The multi-line struct formats also improve code readability.

Also applies to: 51-53, 71-71, 89-91, 109-109, 127-127, 150-150, 168-168

go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (2)

3-6: LGTM! Proper unification of identity field.

The schema correctly updates the field name from externalId to identity and provides clear documentation that it can accept either the external ID or internal identity ID. The constraints and requirements are properly maintained.


10-10: Required field updated correctly.

The required field array is properly updated to reflect the new identity field name.

go/pkg/db/queries/identity_soft_delete.sql (1)

2-5: Excellent unified identity lookup with proper workspace scoping.

The SQL query correctly implements the unified identity approach by:

  1. Adding workspace scoping for security (workspace_id = sqlc.arg('workspace_id'))
  2. Supporting both internal and external ID lookup with a single parameter (id = sqlc.arg('identity') OR external_id = sqlc.arg('identity'))

This ensures proper isolation between workspaces while enabling flexible identity identification.

go/apps/api/routes/v2_keys_update_key/three_state_test.go (3)

271-274: Correct migration to unified identity query.

The test properly updates from FindIdentityByID to FindIdentity with the new parameter structure that includes workspace scoping. This ensures the test uses the same unified identity lookup approach as the production code.


318-321: Consistent use of unified identity query.

Second instance correctly updated to use the new FindIdentity method with proper workspace scoping.


347-352: Good modernization of Go type alias.

The change from map[string]interface{} to map[string]any is a nice modernization using Go 1.18+ type alias. This makes the code more readable while maintaining the same functionality.

go/apps/api/routes/v2_identities_delete_identity/404_test.go (4)

37-37: Correct update to unified identity field.

Request structure properly updated from ExternalId to Identity field, consistent with the API refactor.


73-73: Consistent field naming across test scenarios.

Another instance correctly updated to use the Identity field.


100-103: Proper migration to new database query structure.

The SoftDeleteIdentity call correctly updated to use the new parameter structure with Identity and WorkspaceID fields, ensuring proper workspace scoping in the test.


107-107: Remaining instances updated consistently.

All other test cases properly updated to use the Identity field, maintaining consistency across the test file.

Also applies to: 121-121

go/apps/api/routes/v2_identities_get_identity/200_test.go (3)

43-43: LGTM: Metadata type declaration is correct.

The map[string]any type is appropriate for handling flexible JSON metadata.


76-76: LGTM: Field name changes are consistent with the API refactoring.

The changes from ExternalId to Identity across all test cases align with the unified identity parameter approach, where the API now accepts either external IDs or internal identity IDs through a single field.

Also applies to: 119-119, 154-154, 241-241, 321-321, 369-369


378-392: Excellent test coverage for the new unified identity parameter.

This test case properly validates that identities can be retrieved using their internal ID, which is a key part of the API refactoring. The test logic correctly:

  1. Creates an identity and captures the internal ID
  2. Uses the internal ID to retrieve the identity via the Identity field
  3. Verifies the response matches the expected external ID

This provides good coverage for the new functionality.

go/apps/api/routes/v2_identities_get_identity/400_test.go (3)

30-30: LGTM: Test name updated to reflect the new field name.

The test case name change from "missing externalId" to "missing identity" is consistent with the API field name change.


39-39: LGTM: Error location path correctly updated.

The validation error location path is properly updated to match the new field name in the OpenAPI schema.


47-47: LGTM: Request field names updated consistently.

The field name changes from ExternalId to Identity in the test request objects are consistent with the API refactoring.

Also applies to: 62-62, 76-76

go/apps/api/routes/v2_keys_update_key/200_test.go (2)

109-109: LGTM: Modern Go type alias usage.

Using map[string]any instead of map[string]interface{} follows modern Go conventions where any is the preferred alias for interface{}.


137-140: LGTM: Database query updated to use unified identity lookup.

The change from FindIdentityByID to FindIdentity with the new parameter struct is consistent with the database refactoring. The parameters are correctly set:

  • Identity uses the key's internal identity ID
  • WorkspaceID provides proper workspace scoping

This maintains the same functionality while using the new unified query interface.

go/apps/api/routes/v2_keys_create_key/handler.go (2)

29-32: LGTM: Improved code organization.

Grouping the type aliases in a single declaration block improves readability and follows Go style conventions.


208-211: LGTM: Database query updated to use unified identity lookup.

The change from FindIdentityByExternalID to FindIdentity is consistent with the database refactoring. The new parameter struct correctly includes:

  • WorkspaceID for proper workspace scoping
  • Identity with the external ID value
  • Deleted: false to filter out deleted identities

This maintains the same functionality while using the new unified query interface.

go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml (4)

3-3: LGTM: Property name updated to reflect unified identity parameter.

The change from externalId to identity aligns with the API refactoring to support both external and internal identity identifiers.


5-5: LGTM: Improved validation with minimum length requirement.

Increasing the minLength from 1 to 3 characters provides better validation for identity values, as very short identifiers are likely to be invalid.


6-6: LGTM: Clear and comprehensive field description.

The updated description clearly explains that the identity field can accept either an external ID or internal identity ID, which is essential for API users to understand the new unified parameter.


10-10: LGTM: Required field updated consistently.

The required field is correctly updated to match the new property name identity.

go/pkg/db/queries/identity_delete.sql (1)

2-4: LGTM! Secure implementation of unified identity deletion.

The SQL query correctly implements workspace scoping and uses proper parameter binding to prevent SQL injection. The OR condition allows flexible matching of either internal ID or external ID using the same parameter value.

go/pkg/db/identity_delete.sql_generated.go (2)

18-21: Parameter struct correctly implements unified identity lookup.

The struct properly defines the required fields for workspace-scoped identity operations.


28-30: Function correctly passes identity parameter twice.

The implementation properly passes arg.Identity twice to match both the id and external_id placeholders in the SQL query, enabling flexible identity matching.

go/pkg/db/identity_soft_delete.sql_generated.go (2)

19-22: Parameter struct correctly mirrors delete operation structure.

Consistent implementation of the unified identity parameter approach for soft delete operations.


30-32: Soft delete implementation correctly handles identity parameter.

The function properly passes the identity parameter twice to match both id and external_id conditions in the UPDATE query.

go/apps/api/routes/v2_keys_update_key/handler.go (3)

25-28: Code formatting improved with grouped type declarations.

Clean formatting change that improves readability without altering functionality.


158-162: Identity lookup correctly updated to use unified query.

The refactored call properly includes workspace scoping and uses the new FindIdentityParams structure. The Identity field correctly receives the external ID value.


193-197: Consistent implementation of unified identity lookup in error path.

The fallback identity lookup in the duplicate key error handling correctly uses the same unified approach with proper workspace scoping.

go/apps/api/routes/v2_keys_get_key/handler.go (2)

25-28: Type declarations properly grouped for improved readability.

Consistent formatting improvement that enhances code organization.


256-260: Identity lookup correctly implements workspace-scoped unified query.

The refactored call demonstrates the flexibility of the unified approach by using an internal identity ID while maintaining proper workspace scoping and security. The parameter structure correctly maps to the new FindIdentity method.

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

718-719: LGTM! Well-designed field consolidation.

The replacement of ExternalId with the more flexible Identity field is a good design improvement. The comment clearly explains that it accepts either external IDs or internal identity IDs, which provides better API flexibility for consumers.


730-731: Excellent consistency across identity operations.

The Identity field implementation here is identical to the delete operation, maintaining consistent API patterns. This consistency will improve the developer experience when working with identity operations.

go/pkg/db/identity_find.sql_generated.go (3)

1-4: Generated file - no manual edits allowed

This is an auto-generated file. Any manual changes will be overwritten on the next generation.


12-24: Well-designed unified identity query

The SQL query correctly implements a unified lookup that can match either external or internal IDs within a workspace scope. The parameter structure properly encapsulates the query inputs.


26-52: Correct implementation of unified identity lookup

The method properly executes the query with duplicate identity parameters to support the OR condition, correctly scans all columns, and follows standard database query patterns.

go/apps/api/routes/v2_identities_create_identity/200_test.go (3)

52-56: Correct migration to unified identity query

The test properly uses the new FindIdentity method with workspace scoping and the internal identity ID.


111-118: Validates external ID lookup functionality

These test cases correctly demonstrate that the unified FindIdentity method can query by external ID, confirming the OR condition in the SQL query works as intended.

Also applies to: 133-140, 174-181


233-240: Consistent application of unified identity query across all tests

All test cases have been properly updated to use the new FindIdentity method with appropriate workspace scoping, maintaining test coverage for both internal and external ID lookups.

Also applies to: 309-316, 350-358, 380-387, 420-426

go/apps/api/routes/v2_identities_delete_identity/200_test.go (1)

94-94: Correct API request field migration

The request structure properly uses the new Identity field, maintaining functionality while supporting the unified identity parameter approach.

Also applies to: 129-129, 158-158, 176-176, 202-202, 219-219

go/apps/api/routes/v2_identities_delete_identity/handler.go (3)

21-24: Correct type alias updates

The type aliases properly reference the updated OpenAPI schema for the unified identity parameter approach.


73-77: Proper workspace-scoped identity lookup

The identity lookup correctly uses the unified FindIdentity method with workspace scoping and the generic identity parameter from the request.


100-103: Correct soft delete parameter updates

The soft delete operations properly use the new parameter structure with workspace scoping and the generic identity parameter, maintaining the duplicate key retry logic.

Also applies to: 114-117

go/apps/api/openapi/openapi-generated.yaml (1)

435-443: Verify stricter minLength: 3 will not break existing clients

Both request bodies now require an identity with minLength: 3. If any existing external IDs / internal IDs are shorter (e.g. “id”, “42”), requests will begin failing with 400 responses once the spec is enforced.

Please confirm this constraint matches production data, or reduce the limit / add a migration notice. A quick grep over prod data (or tests) should surface shorter IDs.

go/pkg/db/querier_generated.go (3)

25-28: LGTM: Unified identity deletion with workspace scoping

The DeleteIdentity method correctly implements the unified approach by accepting either internal or external IDs through a single Identity parameter while properly scoping to the workspace for security.


155-162: LGTM: Unified identity lookup with comprehensive filtering

The new FindIdentity method successfully consolidates the previous separate FindIdentityByExternalID and FindIdentityByID methods into a single, more flexible interface. The implementation correctly includes workspace scoping, supports both ID types through the OR condition, and maintains proper deleted flag filtering.


142-146: LGTM: Unified identity soft deletion with workspace scoping

The SoftDeleteIdentity method correctly implements the unified approach consistent with the other identity operations. It properly supports both ID types through the OR condition and maintains workspace scoping for security.

Copy link
Collaborator

@chronark chronark left a comment

Choose a reason for hiding this comment

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

code rabbit caught most things

Copy link
Contributor Author

I’ll check first thing on Monday 🫡 🫡

@chronark chronark self-assigned this Jul 28, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jul 28, 2025
18 tasks
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

♻️ Duplicate comments (5)
go/apps/api/routes/v2_identities_delete_identity/400_test.go (1)

89-92: Update variable name for consistency.

The field change is correct, but the variable name longExternalID should be updated to reflect the unified identity parameter semantics.

-		longExternalID := "test_" + string(make([]byte, 1000))
-		req := handler.Request{Identity: longExternalID}
+		longIdentity := "test_" + string(make([]byte, 1000))
+		req := handler.Request{Identity: longIdentity}
go/apps/api/routes/v2_identities_get_identity/403_test.go (1)

91-93: Update comment to reflect the unified identity parameter.

The field change is correct, but the comment should be updated to reflect that the Identity field now accepts both external IDs and internal identity IDs.

-		// Try to use identity when only having permission for specific identity IDs
+		// Try to use identity when only having permission for specific identity IDs
go/apps/api/openapi/gen.go (1)

730-732: Same naming collision applies here

The Identity field in the get-request struct shadows the package-level Identity type for readers. See previous comment for rationale and suggested alternatives.

go/apps/api/openapi/openapi-generated.yaml (2)

1-3: Timestamp still causes noisy diffs

The non-deterministic “Generated at” line will flip on every regeneration and keep polluting the change log. Please strip or stabilise it in the generator/post-step.


416-423: Duplication of identity schema – extract a shared component

The identity property (type, minLength, description) is now repeated in both Delete and Get request bodies. Define it once under components/schemas/IdentityParam and $ref it here to keep the spec DRY and avoid future drift.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f2da3c and 21f3d06.

📒 Files selected for processing (8)
  • go/apps/api/openapi/gen.go (2 hunks)
  • go/apps/api/openapi/openapi-generated.yaml (3 hunks)
  • go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (1 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/200_test.go (7 hunks)
  • go/apps/api/routes/v2_identities_delete_identity/400_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_get_identity/400_test.go (4 hunks)
  • go/apps/api/routes/v2_identities_get_identity/403_test.go (2 hunks)
  • go/pkg/db/schema.sql (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.134Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses `ExternalId` field instead of the unified `Identity` field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic `Identity` field.
Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.
Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.
go/pkg/db/schema.sql (2)

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.

go/apps/api/routes/v2_identities_delete_identity/400_test.go (7)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: chronark
PR: #2126
File: apps/api/src/routes/v1_ratelimit_getOverride.happy.test.ts:36-36
Timestamp: 2024-11-13T19:06:36.786Z
Learning: In the rate limit test files (e.g., apps/api/src/routes/v1_ratelimit_getOverride.happy.test.ts), URL parameters like namespaceId and identifier do not need to be URL-encoded in the test code because the values used are always considered safe within the test environment.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.

Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

go/apps/api/routes/v2_identities_get_identity/403_test.go (5)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.134Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.

Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (4)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

go/apps/api/routes/v2_identities_get_identity/400_test.go (4)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: Flo4604
PR: #2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.

go/apps/api/routes/v2_identities_delete_identity/200_test.go (7)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

Learnt from: chronark
PR: #3617
File: go/apps/api/openapi/openapi.yaml:3309-3312
Timestamp: 2025-07-16T17:51:57.297Z
Learning: In the Unkey API OpenAPI schema, the permissions query regex for the verifyKey endpoint intentionally allows all whitespace characters (including tabs and newlines) via \s. Do not flag this as an error in future reviews.

go/apps/api/openapi/gen.go (4)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

go/apps/api/openapi/openapi-generated.yaml (13)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: Flo4604
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.

Learnt from: Flo4604
PR: #2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.

Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:32-36
Timestamp: 2025-04-22T17:33:28.162Z
Learning: In the Unkey dashboard UI for delete protection, the button/link to initiate the process is labeled "Disable Delete Protection" while the confirmation button is labeled "Disable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:21-24
Timestamp: 2025-04-22T17:34:04.438Z
Learning: In the Unkey dashboard UI for enabling delete protection, the button/link to initiate the process is labeled "Enable Delete Protection" while the confirmation button is labeled "Enable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: chronark
PR: #2146
File: apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts:80-80
Timestamp: 2024-10-04T17:27:08.666Z
Learning: Ensure that audit log descriptions accurately reflect the action being performed, such as updating the defaultPrefix, and avoid incorrect references like 'name' when not applicable.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's *.go code changes significantly, increase the patch-level version number.

🧬 Code Graph Analysis (4)
go/apps/api/routes/v2_identities_delete_identity/400_test.go (3)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)
  • Request (22-22)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/apps/api/routes/v2_identities_get_identity/403_test.go (2)
go/apps/api/openapi/gen.go (1)
  • Identity (142-151)
go/apps/api/routes/v2_identities_get_identity/handler.go (1)
  • Request (19-19)
go/apps/api/routes/v2_identities_delete_identity/200_test.go (4)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (20-24)
go/pkg/testutil/seed/seed.go (1)
  • Resources (20-25)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
go/pkg/testutil/http.go (1)
  • CallRoute (257-291)
go/apps/api/openapi/gen.go (1)
go/pkg/db/models_generated.go (1)
  • Identity (655-664)
⏰ 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: Test Go API Local / Test
  • GitHub Check: Test Agent Local / test_agent_local
🔇 Additional comments (21)
go/pkg/db/schema.sql (1)

433-433: LGTM! Index supports the unified identity lookup pattern.

This composite index is well-designed to support the new unified FindIdentity query that replaces the separate FindIdentityByID and FindIdentityByExternalID queries. The index covers workspace_id, id, and deleted columns in the optimal order, providing efficient lookups when querying identities by internal ID within a workspace while considering the deletion status.

This complements the existing workspace_id_external_id_deleted_idx unique constraint for external ID lookups, ensuring both identity lookup paths are properly indexed.

go/apps/api/routes/v2_identities_delete_identity/400_test.go (3)

47-48: LGTM! Field rename is consistent with the unified identity parameter.

The change from ExternalId to Identity correctly reflects the API refactoring that allows the identity field to accept both external IDs and internal identity IDs.


61-62: LGTM! Test description and field usage correctly updated.

The test name and field usage have been appropriately updated to reflect the unified identity parameter semantics.


75-77: LGTM! Test description and comment properly updated.

The test name and comment have been correctly updated to reflect that the Identity field now accepts both external IDs and internal identity IDs.

go/apps/api/routes/v2_identities_get_identity/403_test.go (1)

75-75: LGTM! Field rename is consistent with the unified identity parameter.

The change from ExternalId to Identity correctly reflects the refactoring that allows querying by either external ID or internal identity ID.

go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml (2)

3-6: LGTM! Property rename and description improve API clarity.

The change from externalId to identity unifies the identity parameter semantics, and the updated description clearly explains that the field accepts both external IDs and internal identity IDs. This provides better flexibility for API consumers.


8-10: LGTM! Required property and schema constraints correctly updated.

The repositioning of additionalProperties: false and the update of the required property from externalId to identity maintain schema correctness and consistency.

go/apps/api/routes/v2_identities_get_identity/400_test.go (5)

30-30: LGTM! Test description correctly updated.

The test name has been appropriately changed from "missing externalId" to "missing identity" to reflect the unified identity parameter.


39-39: LGTM! Error validation path correctly updated.

The assertion has been properly updated to check for the new error location /properties/identity/minLength instead of the previous /properties/externalId/minLength.


45-47: LGTM! Test description and field usage correctly updated.

Both the test name and the field usage have been appropriately updated to reflect the unified identity parameter semantics.


62-62: LGTM! Field rename is consistent.

The change from ExternalId to Identity maintains consistency with the unified identity parameter refactoring.


76-76: LGTM! Field rename is consistent.

The change from ExternalId to Identity correctly reflects the API refactoring that allows the identity field to accept both external IDs and internal identity IDs.

go/apps/api/routes/v2_identities_delete_identity/200_test.go (8)

40-40: LGTM: Modern Go range syntax.

Good use of the range-over-int syntax introduced in Go 1.22, which is more idiomatic for simple counting loops.


85-94: LGTM: Consistent unified identity parameter usage.

The changes correctly implement the unified identity approach by:

  • Using the new Identity field in request structs instead of ExternalId
  • Properly calling FindIdentity with FindIdentityParams including workspace scoping
  • Maintaining consistent use of external ID for lookups in this test case

101-113: LGTM: Proper soft deletion verification.

The FindIdentity calls correctly verify the soft deletion behavior by checking both the removal from active queries (Deleted: false) and persistence for audit purposes (Deleted: true).


129-140: LGTM: Testing unified identity parameter behavior.

The test correctly validates that deletion by external ID (in the request) properly affects the corresponding internal identity (in the verification), demonstrating the unified identity parameter functionality.


159-170: LGTM: Consistent wildcard permission testing.

The test maintains the same pattern of deleting by external ID and verifying by internal ID, properly testing the unified identity parameter with wildcard permissions.


178-178: LGTM: Consistent request parameter usage.

Correct use of the unified Identity field in the request struct.


204-230: LGTM: Proper duplicate key error handling test.

The test correctly implements the duplicate key scenario by deleting by external ID and verifying the new identity by internal ID, ensuring proper edge case handling.


234-238: LGTM: Correct hard deletion verification.

The test properly verifies that the original identity was hard deleted (not found even with Deleted: true) as expected in the duplicate key error handling scenario.

go/apps/api/openapi/openapi-generated.yaml (1)

435-443: Breaking change risk: minLength raised from 1 → 3

Clients that previously sent a 1- or 2-character externalId will now be rejected with 422. Was this increase intentional? If not strictly required, keep minLength: 1 to maintain backward compatibility.

-                    minLength: 3
+                    minLength: 1

@Flo4604
Copy link
Member

Flo4604 commented Jul 29, 2025

other than that we can merge it

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

♻️ Duplicate comments (2)
go/pkg/codes/constants_gen.go (1)

9-130: Do not hand-edit generated code – update the generator instead

This file carries the "// Code generated … DO NOT EDIT." header, yet this PR manually removes indentation from comment blocks throughout the file. Any future go generate (or CI) run will overwrite these changes, making the effort transient and adding merge noise.

Move the desired formatting logic into the code generator source and regenerate the file, then commit the result.

go/apps/api/openapi/openapi-generated.yaml (1)

409-416: identity property duplicated → extract shared schema
The exact same identity property block (type, minLength, description) now lives in both Get- and Delete-request bodies. Define it once under components/schemas/IdentityParam and $ref it from both places to avoid future drift.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2507a9a and d29a7dc.

📒 Files selected for processing (3)
  • go/apps/api/openapi/openapi-generated.yaml (2 hunks)
  • go/apps/api/routes/v2_keys_whoami/handler.go (1 hunks)
  • go/pkg/codes/constants_gen.go (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses `ExternalId` field instead of the unified `Identity` field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic `Identity` field.
Learnt from: Flo4604
PR: unkeyed/unkey#3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.
Learnt from: chronark
PR: unkeyed/unkey#2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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
PR: unkeyed/unkey#3480
File: apps/dashboard/app/new-2/hooks/use-workspace-step.tsx:47-79
Timestamp: 2025-07-09T11:35:51.724Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep invariant checks that throw errors for cases that shouldn't happen in normal operation (like null workspace ID checks), rather than adding graceful error handling code for edge cases that would only occur if someone tampers with the actual flow.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3375
File: apps/dashboard/app/(app)/settings/root-keys/components/table/hooks/use-root-keys-list-query.ts:0-0
Timestamp: 2025-06-25T20:32:10.471Z
Learning: In the Unkey codebase, ogzhanolguncu prefers strict validation with fail-fast error handling. When validation errors occur that shouldn't happen in normal operation (like invalid operators), throwing errors to crash the page is preferred over graceful error handling or console logging.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.
go/pkg/codes/constants_gen.go (5)

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's *.go code changes significantly, increase the patch-level version number.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Update relevant anchors when modifying associated code.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

go/apps/api/routes/v2_keys_whoami/handler.go (5)

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

go/apps/api/openapi/openapi-generated.yaml (13)

Learnt from: ogzhanolguncu
PR: #3661
File: go/apps/api/routes/v2_identities_update_identity/handler.go:115-119
Timestamp: 2025-07-28T11:47:43.144Z
Learning: The v2 update identity endpoint (go/apps/api/routes/v2_identities_update_identity/handler.go) intentionally uses ExternalId field instead of the unified Identity field used in other v2 identity endpoints. This is because the update endpoint needs to both find by externalId and potentially update the externalId value, making the specific field name more appropriate than the generic Identity field.

Learnt from: Flo4604
PR: #3151
File: go/apps/api/openapi/gen.go:221-233
Timestamp: 2025-04-18T20:01:33.812Z
Learning: For identity deletion operations in the Unkey API, identityId takes precedence over externalId when both are provided in the request body.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: Flo4604
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.

Learnt from: Flo4604
PR: #2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:32-36
Timestamp: 2025-04-22T17:33:28.162Z
Learning: In the Unkey dashboard UI for delete protection, the button/link to initiate the process is labeled "Disable Delete Protection" while the confirmation button is labeled "Disable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:21-24
Timestamp: 2025-04-22T17:34:04.438Z
Learning: In the Unkey dashboard UI for enabling delete protection, the button/link to initiate the process is labeled "Enable Delete Protection" while the confirmation button is labeled "Enable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: chronark
PR: #2146
File: apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts:80-80
Timestamp: 2024-10-04T17:27:08.666Z
Learning: Ensure that audit log descriptions accurately reflect the action being performed, such as updating the defaultPrefix, and avoid incorrect references like 'name' when not applicable.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's *.go code changes significantly, increase the patch-level version number.

Learnt from: ogzhanolguncu
PR: #3315
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/general-setup.tsx:40-50
Timestamp: 2025-06-19T13:01:55.338Z
Learning: In the create-key form's GeneralSetup component, the Controller is intentionally bound to "identityId" as the primary field while "externalId" is set explicitly via setValue. The ExternalIdField component has been designed to handle this pattern where it receives identityId as its value prop but manages both identityId and externalId through its onChange callback.

🧬 Code Graph Analysis (1)
go/pkg/codes/constants_gen.go (1)
go/pkg/urn/urn.go (1)
  • URN (12-19)
⏰ 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: Test Agent Local / test_agent_local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
🔇 Additional comments (1)
go/apps/api/routes/v2_keys_whoami/handler.go (1)

165-165: LGTM! Improved security with workspace scoping

The change from FindIdentityByID to the unified FindIdentity method properly scopes the identity lookup to the authorized workspace (auth.AuthorizedWorkspaceID), preventing potential cross-workspace access issues. This aligns with the broader refactor to consolidate identity query methods while enhancing security.

@ogzhanolguncu ogzhanolguncu force-pushed the update-identity-param-for-v2 branch from d29a7dc to 3ca7a64 Compare July 29, 2025 09:32
@ogzhanolguncu ogzhanolguncu added this pull request to the merge queue Jul 29, 2025
@graphite-app
Copy link

graphite-app bot commented Jul 29, 2025

TV gif. Mary Kate or Ashley Olsen as Michelle on Full House, giving a thumbs up and saying 'you got it dude.' (Added via Giphy)

@graphite-app
Copy link

graphite-app bot commented Jul 29, 2025

Graphite Automations

"Post a GIF when PR approved" took an action on this PR • (07/29/25)

1 gif was posted to this PR based on Andreas Thomas's automation.

@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 29, 2025
@Flo4604 Flo4604 added this pull request to the merge queue Jul 29, 2025
Merged via the queue into main with commit 4b796be Jul 29, 2025
17 of 19 checks passed
@Flo4604 Flo4604 deleted the update-identity-param-for-v2 branch July 29, 2025 11:06
@coderabbitai coderabbitai bot mentioned this pull request Nov 10, 2025
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