Skip to content

feat: edit identity ratelimits#4508

Merged
perkinsjr merged 5 commits intomainfrom
ENG-2270-edit-identity-ratelimits
Dec 17, 2025
Merged

feat: edit identity ratelimits#4508
perkinsjr merged 5 commits intomainfrom
ENG-2270-edit-identity-ratelimits

Conversation

@mcstepp
Copy link
Collaborator

@mcstepp mcstepp commented Dec 15, 2025

What does this PR do?

Added edit ratelimits functionality for identities, bringing feature parity with keys.

  • Edit ratelimit dialog for identities from context menu
  • Ratelimit configuration in create identity flow (multi-step dialog with General, Ratelimit, and Metadata sections)
  • Shared RatelimitSetup component for both keys and identities

Refactoring:

  • Extracted ratelimit schemas and utilities to shared location
  • Moved RatelimitSetup component to shared location
  • Generalized useEditRatelimits hook to support both keys and identities via entityType specification
  • Updated create identity dialog to use NavigableDialog pattern (matching key creation UX)

Backend:

  • New tRPC endpoint: identity.updateRatelimit
  • Enhanced identity.create to accept ratelimits during creation
Screenshot 2025-12-12 at 3 47 26 PM Screenshot 2025-12-12 at 3 47 38 PM Screenshot 2025-12-15 at 11 26 36 AM Screenshot 2025-12-15 at 11 26 56 AM

Fixes #4459

Type of change

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

How should this be tested?

Setup: workspace is opted into identities feature

Edit existing identity ratelimits:

  • Navigate to Identities page
  • Click the action menu (•••) on any identity
  • Select "Edit ratelimit..."
  • Toggle ratelimit on/off and add/modify/remove limits
  • Verify changes persist and update immediately

Create identity with ratelimits:

  • Click "Create Identity" button
  • Fill in External ID in General Setup
  • Navigate to Ratelimit tab
  • Enable ratelimit and configure limits
  • Submit and verify identity is created with ratelimits attached

Verify key ratelimits still work:

  • Go to API Keys page
  • Test editing ratelimits on existing keys to ensure refactoring didn't break functionality

Checklist

Required

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

Appreciated

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

@linear
Copy link

linear bot commented Dec 15, 2025

@changeset-bot
Copy link

changeset-bot bot commented Dec 15, 2025

⚠️ No Changeset found

Latest commit: 4f728f5

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 Dec 15, 2025

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

Project Deployment Review Updated (UTC)
dashboard Ready Ready Preview, Comment Dec 17, 2025 1:32pm
1 Skipped Deployment
Project Deployment Review Updated (UTC)
engineering Ignored Ignored Preview Dec 17, 2025 1:32pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

📝 Walkthrough

Walkthrough

Adds identity-level ratelimit management: new CreateIdentityDialog and EditRatelimitDialog, centralized ratelimit schema and RatelimitSetup refactor to support keys and identities, TRPC routes to create and update identity ratelimits, and import/hook updates to useEditRatelimits across the dashboard.

Changes

Cohort / File(s) Summary
Ratelimit Schema & Types
apps/dashboard/lib/schemas/ratelimit.ts
New centralized ratelimit schemas, default values, and exported types (RatelimitItem, RatelimitFormValues, RatelimitFormContextValues).
Identity TRPC: create/update/query/search
apps/dashboard/lib/trpc/routers/identity/create.ts
apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts
apps/dashboard/lib/trpc/routers/identity/query.ts
apps/dashboard/lib/trpc/routers/identity/search.ts
Extend create input to accept ratelimits and insert records; add updateIdentityRatelimit route with transactional sync/upsert/delete and audit logging; expand query/search responses to include ratelimit fields (name, limit, duration, autoApply).
TRPC Router Index
apps/dashboard/lib/trpc/routers/index.ts
Expose new identity.update.ratelimit route by importing and wiring updateIdentityRatelimit.
Ratelimit Hook
apps/dashboard/hooks/use-edit-ratelimits.ts
Refactor useEditRatelimits to accept entityType ("key"
RatelimitSetup Component
apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx
Add entityType prop (default "key"), introduce getFieldError helper for nested errors, adjust form context typing and dynamic UI text based on entityType.
Identity Dialogs & Schema
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity-dialog.tsx
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.constants.tsx
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/general-setup.tsx
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/edit-ratelimit-dialog.tsx
Add multi-section CreateIdentityDialog (general, ratelimit, metadata), SECTIONS constants, form schema/defaults, GeneralSetup input, and EditRatelimitDialog using RatelimitSetup and useEditRatelimits("identity").
Identity Table & Navigation
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/table/identity-table-actions.tsx
apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/create-identity-dialog.tsx
Add "Edit ratelimit" action (Gauge) to identity actions menu and render EditRatelimitDialog; move CreateIdentityDialog to dialogs/ and delete legacy file; update import paths.
Import Path Updates
apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.constants.tsx
apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/.../edit-ratelimits/index.tsx
apps/dashboard/app/new/hooks/use-key-creation-step.tsx
apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
Consolidate imports to centralized ratelimit schema/component paths and update useEditRatelimits usage for "key" where applicable.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Table as Identity Table
    participant Dialog as EditRatelimitDialog
    participant Form as react-hook-form
    participant RatelimitSetup as RatelimitSetup UI
    participant Hook as useEditRatelimits("identity")
    participant TRPC as identity.update.ratelimit
    participant Server as Server (handler)
    participant DB as Database

    User->>Table: Click "Edit ratelimit"
    Table->>Dialog: open(identity)
    Dialog->>Form: init with identity.ratelimits
    Form->>RatelimitSetup: render controls
    User->>RatelimitSetup: edit/add/remove rules
    User->>Dialog: submit
    Dialog->>Form: validate
    alt validation fails
        Form->>Dialog: show inline errors
    else validation passes
        Dialog->>Hook: useEditRatelimits("identity").mutateAsync(ratelimitPayload)
        Hook->>TRPC: call mutation with payload
        TRPC->>Server: auth & input validation
        Server->>DB: begin transaction
        Server->>DB: delete removed rules / update existing / insert new
        DB-->>Server: commit
        Server->>Server: insert audit logs
        Server-->>TRPC: return success
        TRPC-->>Hook: resolve
        Hook->>Dialog: onSuccess (toast, invalidate queries)
        Dialog->>Table: refresh view
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay attention to hook overload typings and all call sites updated to pass entityType.
  • Review transactional sync logic and audit log generation in updateRatelimitV2.
  • Validate form schema vs. form context types (RatelimitFormContextValues) and RatelimitSetup integration.
  • Confirm trpc route input/output schema changes are consistent with client usage.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: edit identity ratelimits' accurately and concisely summarizes the main feature added: edit ratelimits functionality for identities.
Description check ✅ Passed The PR description comprehensively relates to the changeset, detailing UI changes, refactoring work, backend additions, and testing instructions that align with the provided code modifications.
Linked Issues check ✅ Passed The PR successfully implements all acceptance criteria from issue #4459: new 'Edit ratelimits...' context menu item with gauge icon [#4459], functional ratelimit dialog with name/limit/duration/auto-apply/delete fields [#4459], validation and error handling [#4459], success/error toasts [#4459], trpc.identity.updateRatelimit endpoint [#4459], and full replacement model [#4459].
Out of Scope Changes check ✅ Passed All changes are directly scoped to the stated objectives: identity ratelimit edit functionality, create identity with ratelimits, shared RatelimitSetup component refactoring, and generalized useEditRatelimits hook. No unrelated or extraneous changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ENG-2270-edit-identity-ratelimits

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mcstepp mcstepp changed the title Eng 2270 edit identity ratelimits feat: edit identity ratelimits Dec 15, 2025
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: 5

🧹 Nitpick comments (3)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity-dialog.tsx (1)

62-72: Consider wrapping JSON.parse in try-catch for defensive error handling.

If metadata.data contains invalid JSON (despite schema validation), JSON.parse will throw. While the schema should prevent this, adding a try-catch provides defense-in-depth.

 const onSubmit = (data: FormValues) => {
-  const meta =
-    data.metadata?.enabled && data.metadata.data ? JSON.parse(data.metadata.data) : null;
+  let meta = null;
+  if (data.metadata?.enabled && data.metadata.data) {
+    try {
+      meta = JSON.parse(data.metadata.data);
+    } catch {
+      toast.error("Invalid metadata", { description: "Please ensure metadata is valid JSON" });
+      return;
+    }
+  }
   const ratelimits =
     data.ratelimit?.enabled && data.ratelimit.data ? data.ratelimit.data : undefined;
apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts (2)

74-79: Consider batching deletes for better performance.

Sequential deletes in a loop can be slow with many ratelimits. Consider using a single delete with inArray for better performance.

+import { inArray } from "@/lib/db";
+
 // Delete ratelimits that exist in DB but are not in the input (they were removed)
-for (const existing of existingRatelimits) {
-  if (!inputRatelimitIds.has(existing.id)) {
-    await tx.delete(schema.ratelimits).where(eq(schema.ratelimits.id, existing.id));
-  }
-}
+const idsToDelete = existingRatelimits
+  .filter((existing) => !inputRatelimitIds.has(existing.id))
+  .map((r) => r.id);
+if (idsToDelete.length > 0) {
+  await tx.delete(schema.ratelimits).where(inArray(schema.ratelimits.id, idsToDelete));
+}

110-112: Throw TRPCError instead of generic Error for consistent error handling.

This throw new Error(...) will be caught and re-thrown as INTERNAL_SERVER_ERROR, but throwing a TRPCError with BAD_REQUEST would be more semantically correct for this validation failure.

       } else if (input.ratelimit.enabled) {
         // Rate limiting is enabled but no rules provided (edge case). Should not happen in v2.
-        throw new Error("Rate limiting is enabled but no rules were provided");
+        throw new TRPCError({
+          code: "BAD_REQUEST",
+          message: "Rate limiting is enabled but no rules were provided",
+        });
       } else {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02045e8 and 91214fb.

📒 Files selected for processing (20)
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.constants.tsx (1 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx (3 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/create-identity-dialog.tsx (0 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.constants.tsx (1 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts (1 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/edit-ratelimit-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/general-setup.tsx (1 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/table/identity-table-actions.tsx (2 hunks)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx (1 hunks)
  • apps/dashboard/app/new/hooks/use-key-creation-step.tsx (1 hunks)
  • apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx (6 hunks)
  • apps/dashboard/hooks/use-edit-ratelimits.ts (2 hunks)
  • apps/dashboard/lib/schemas/ratelimit.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/identity/create.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/identity/query.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/identity/search.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/create-identity-dialog.tsx
🧰 Additional context used
🧠 Learnings (28)
📓 Common learnings
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.743Z
Learning: PR #4190 for unkeyed/unkey is focused solely on database schema and query changes for identity-based credits. It adds IdentityCredits and KeyCredits fields to structs and queries, but does not implement the priority enforcement logic in the usagelimiter. The logic implementation is intentionally deferred to a later PR in the stack.
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4484
File: go/pkg/db/queries/identity_list.sql:11-24
Timestamp: 2025-12-05T12:05:33.166Z
Learning: In go/pkg/db/queries/identity_list.sql and similar identity ratelimit queries, the RatelimitInfo struct's key_id and identity_id fields can be omitted from JSON_OBJECT construction because: (1) these are identity-level ratelimits where key_id doesn't apply, and (2) identity_id is redundant since it's already known from the parent identity context. Zero values for these fields are acceptable and intentional.
📚 Learning: 2025-06-19T13:01:55.338Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey 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.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/general-setup.tsx
📚 Learning: 2024-10-04T17:27:09.821Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In `apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx`, the hidden `<input>` elements for `workspaceId` and `keyAuthId` work correctly without being registered with React Hook Form.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/general-setup.tsx
  • apps/dashboard/app/new/hooks/use-key-creation-step.tsx
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx
  • apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx
📚 Learning: 2025-04-22T11:49:06.167Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3156
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/index.tsx:112-0
Timestamp: 2025-04-22T11:49:06.167Z
Learning: In the CreateKeyDialog component of Unkey, section navigation is designed to always allow progression even if validation fails, as visual indicators display the validation state of each section.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity-dialog.tsx
📚 Learning: 2025-07-28T20:38:53.244Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3662
File: apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx:322-341
Timestamp: 2025-07-28T20:38:53.244Z
Learning: In apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx, mcstepp prefers to keep hardcoded endpoint logic in the getDiffType function during POC phases for demonstrating diff functionality, rather than implementing a generic diff algorithm. This follows the pattern of keeping simplified implementations for demonstration purposes.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx
📚 Learning: 2025-06-02T11:08:56.397Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the `api` directory and should be kept consistent with that original implementation.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx
📚 Learning: 2025-09-23T17:39:59.820Z
Learnt from: perkinsjr
Repo: unkeyed/unkey PR: 4009
File: apps/dashboard/app/(app)/[workspace]/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:88-97
Timestamp: 2025-09-23T17:39:59.820Z
Learning: The useWorkspaceNavigation hook in the Unkey dashboard guarantees that a workspace exists. If no workspace is found, the hook redirects the user to create a new workspace. Users cannot be logged in without a workspace, and new users must create one to continue. Therefore, workspace will never be null when using this hook.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx
📚 Learning: 2025-09-23T17:40:44.944Z
Learnt from: perkinsjr
Repo: unkeyed/unkey PR: 4009
File: apps/dashboard/app/(app)/[workspace]/authorization/roles/navigation.tsx:26-40
Timestamp: 2025-09-23T17:40:44.944Z
Learning: In the Unkey dashboard authorization section navigation components, the maintainer prefers to wrap entire navbars in Suspense rather than scoping Suspense to individual components, even if it blocks the whole navigation during loading.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx
📚 Learning: 2024-10-23T12:05:31.121Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 2544
File: apps/api/src/pkg/env.ts:4-6
Timestamp: 2024-10-23T12:05:31.121Z
Learning: The `cloudflareRatelimiter` type definition in `apps/api/src/pkg/env.ts` should not have its interface changed; it should keep the `limit` method returning `Promise<{ success: boolean }>` without additional error properties.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
  • apps/dashboard/hooks/use-edit-ratelimits.ts
  • apps/dashboard/lib/trpc/routers/identity/query.ts
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts
  • apps/dashboard/lib/trpc/routers/identity/search.ts
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.constants.tsx
  • apps/dashboard/app/new/hooks/use-key-creation-step.tsx
  • apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx
📚 Learning: 2024-10-20T07:05:55.471Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In `apps/api/src/pkg/keys/service.ts`, `ratelimitAsync` is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
  • apps/dashboard/hooks/use-edit-ratelimits.ts
  • apps/dashboard/lib/trpc/routers/identity/query.ts
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
  • apps/dashboard/lib/trpc/routers/identity/create.ts
  • apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts
  • apps/dashboard/lib/trpc/routers/identity/search.ts
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.constants.tsx
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx
📚 Learning: 2025-04-22T11:48:39.670Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3156
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx:36-47
Timestamp: 2025-04-22T11:48:39.670Z
Learning: The Unkey dashboard's form validation for numeric values like rate limits is handled through the Zod schema validation (with `.positive()` validators and additional checks in `superRefine`), rather than HTML input attributes like `min`.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
📚 Learning: 2025-04-22T11:47:24.733Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3156
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx:48-59
Timestamp: 2025-04-22T11:47:24.733Z
Learning: For form inputs in the Unkey dashboard, validation constraints are handled at the schema level using Zod rather than using HTML attributes like min/step on numeric inputs.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
  • apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts
📚 Learning: 2025-10-30T15:10:52.743Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.743Z
Learning: PR #4190 for unkeyed/unkey is focused solely on database schema and query changes for identity-based credits. It adds IdentityCredits and KeyCredits fields to structs and queries, but does not implement the priority enforcement logic in the usagelimiter. The logic implementation is intentionally deferred to a later PR in the stack.

Applied to files:

  • apps/dashboard/hooks/use-edit-ratelimits.ts
📚 Learning: 2025-08-25T12:56:59.310Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3834
File: apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts:59-66
Timestamp: 2025-08-25T12:56:59.310Z
Learning: In the ratelimit namespace query system (apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts), the nameQuery filter is designed as an array for future extensibility to support multiple filters, but currently only the first filter (index 0) is processed. This is intentional future-proofing.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/query.ts
  • apps/dashboard/lib/trpc/routers/identity/search.ts
📚 Learning: 2025-12-05T12:05:33.166Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4484
File: go/pkg/db/queries/identity_list.sql:11-24
Timestamp: 2025-12-05T12:05:33.166Z
Learning: In go/pkg/db/queries/identity_list.sql and similar identity ratelimit queries, the RatelimitInfo struct's key_id and identity_id fields can be omitted from JSON_OBJECT construction because: (1) these are identity-level ratelimits where key_id doesn't apply, and (2) identity_id is redundant since it's already known from the parent identity context. Zero values for these fields are acceptable and intentional.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/query.ts
  • apps/dashboard/lib/trpc/routers/identity/create.ts
  • apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts
📚 Learning: 2025-08-22T12:49:20.668Z
Learnt from: perkinsjr
Repo: unkeyed/unkey PR: 3775
File: apps/dashboard/lib/trpc/routers/api/setDefaultBytes.ts:1-1
Timestamp: 2025-08-22T12:49:20.668Z
Learning: The team at unkeyed/unkey is comfortable with keeping TRPC schema imports from app route folders as temporary technical debt during their migration from TRPC to calling their v2 API directly, since this code will be replaced rather than maintained long-term.

Applied to files:

  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
📚 Learning: 2025-08-22T12:50:07.024Z
Learnt from: perkinsjr
Repo: unkeyed/unkey PR: 3775
File: apps/dashboard/lib/trpc/routers/api/keys/query-overview-timeseries/index.ts:1-2
Timestamp: 2025-08-22T12:50:07.024Z
Learning: The team at Unkey is comfortable accepting TRPC schema imports from app route folders in server-side TRPC routers as technical debt, since they're planning to migrate away from TRPC to calling their v2 API directly, making these imports temporary and not worth refactoring.

Applied to files:

  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
📚 Learning: 2025-08-22T12:48:58.289Z
Learnt from: perkinsjr
Repo: unkeyed/unkey PR: 3775
File: apps/dashboard/lib/trpc/routers/api/keys/query-key-usage-timeseries/index.ts:1-1
Timestamp: 2025-08-22T12:48:58.289Z
Learning: The team at Unkey is planning to move from TRPC to calling their v2 API directly. They're comfortable keeping TRPC input schemas imported from app route folders as technical debt since this code will be replaced, rather than refactoring to move schemas to lib/schemas.

Applied to files:

  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
📚 Learning: 2025-03-19T09:25:59.751Z
Learnt from: Flo4604
Repo: unkeyed/unkey 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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
📚 Learning: 2025-08-22T12:45:07.187Z
Learnt from: perkinsjr
Repo: unkeyed/unkey PR: 3775
File: apps/dashboard/lib/trpc/routers/workspace/onboarding.ts:1-1
Timestamp: 2025-08-22T12:45:07.187Z
Learning: The team at Unkey is planning to move from TRPC to calling their v2 API directly, making current TRPC schema organization temporary. They're comfortable with keeping server input schemas imported from app route folders as technical debt since this code will be replaced.

Applied to files:

  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts
📚 Learning: 2024-12-05T13:27:55.555Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 2707
File: apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts:63-63
Timestamp: 2024-12-05T13:27:55.555Z
Learning: In `apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts`, when determining the maximum number of rate limit overrides (`max`), the intentional use of `const max = hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) || 5;` allows `max` to fall back to `5` when `hasWorkspaceAccess` returns `0` or `false`. This fallback behavior is expected and intended in the codebase.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts
📚 Learning: 2025-08-21T15:54:45.198Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 3825
File: go/internal/services/usagelimiter/limit.go:38-0
Timestamp: 2025-08-21T15:54:45.198Z
Learning: In go/internal/services/usagelimiter/limit.go, the UpdateKeyCreditsDecrement operation cannot be safely wrapped with db.WithRetry due to the lack of idempotency mechanisms in the current tech stack. Retrying this non-idempotent write operation risks double-charging users if the first attempt commits but the client sees a transient error.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts
📚 Learning: 2025-06-19T11:48:05.070Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey 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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/search.ts
📚 Learning: 2025-07-25T19:11:00.208Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.208Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/search.ts
📚 Learning: 2025-07-25T19:09:43.284Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.284Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Applied to files:

  • apps/dashboard/lib/trpc/routers/identity/search.ts
📚 Learning: 2025-08-25T13:46:34.441Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3834
File: apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-refresh.tsx:4-4
Timestamp: 2025-08-25T13:46:34.441Z
Learning: The namespace list refresh component (apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-refresh.tsx) intentionally uses the overview hook (useFilters from @/app/(app)/ratelimits/[namespaceId]/_overview/hooks/use-filters) rather than a namespace-specific hook. This cross-coupling between namespace list components and overview hooks is an architectural design decision.

Applied to files:

  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.constants.tsx
  • apps/dashboard/app/new/hooks/use-key-creation-step.tsx
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx
📚 Learning: 2025-08-25T13:46:08.303Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3834
File: apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-datetime/index.tsx:1-1
Timestamp: 2025-08-25T13:46:08.303Z
Learning: The NamespaceListDateTime component in apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-datetime/index.tsx is intentionally designed to use the overview hook (useFilters from @/app/(app)/ratelimits/[namespaceId]/_overview/hooks/use-filters) rather than the namespace list hook, as clarified by ogzhanolguncu. This coupling is by design, not an architectural issue.

Applied to files:

  • apps/dashboard/app/new/hooks/use-key-creation-step.tsx
  • apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx
📚 Learning: 2025-01-22T16:52:29.277Z
Learnt from: MichaelUnkey
Repo: unkeyed/unkey PR: 2810
File: internal/ui/src/components/date-time/components/time-split.tsx:0-0
Timestamp: 2025-01-22T16:52:29.277Z
Learning: For time input fields in React, use pattern="[0-9]*" for HTML5 validation and handle specific range validation (hours: 0-23, minutes/seconds: 0-59) in the onChange handler to ensure better user experience and consistent validation behavior.

Applied to files:

  • apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx
🧬 Code graph analysis (10)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/general-setup.tsx (1)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts (1)
  • FormValues (17-17)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity-dialog.tsx (3)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts (3)
  • FormValues (17-17)
  • formSchema (5-15)
  • getDefaultValues (19-35)
apps/dashboard/lib/trpc/routers/identity/create.ts (1)
  • createIdentity (20-155)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.constants.tsx (1)
  • SECTIONS (7-26)
apps/dashboard/lib/schemas/ratelimit.ts (1)
apps/dashboard/lib/schemas/metadata.ts (1)
  • createConditionalSchema (4-23)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/edit-ratelimit-dialog.tsx (5)
apps/dashboard/lib/trpc/routers/identity/query.ts (1)
  • IdentityResponseSchema (13-31)
apps/dashboard/lib/schemas/ratelimit.ts (2)
  • RatelimitFormValues (49-49)
  • ratelimitSchema (33-45)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts (1)
  • getDefaultValues (19-35)
apps/dashboard/hooks/use-edit-ratelimits.ts (1)
  • useEditRatelimits (15-121)
apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx (1)
  • RatelimitSetup (10-217)
apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts (2)
apps/dashboard/lib/schemas/metadata.ts (1)
  • metadataSchema (52-56)
apps/dashboard/lib/schemas/ratelimit.ts (1)
  • ratelimitSchema (33-45)
apps/dashboard/lib/trpc/routers/identity/create.ts (2)
internal/id/src/generate.ts (1)
  • newId (36-55)
internal/db/src/index.ts (1)
  • schema (8-8)
apps/dashboard/lib/trpc/routers/index.ts (1)
apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts (1)
  • updateIdentityRatelimit (17-47)
apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts (5)
apps/dashboard/lib/schemas/ratelimit.ts (1)
  • ratelimitSchema (33-45)
apps/dashboard/lib/trpc/trpc.ts (3)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
internal/id/src/generate.ts (1)
  • newId (36-55)
apps/dashboard/lib/audit.ts (2)
  • UnkeyAuditLog (59-101)
  • insertAuditLogs (103-158)
apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx (1)
apps/dashboard/hooks/use-edit-ratelimits.ts (1)
  • useEditRatelimits (15-121)
apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx (1)
apps/dashboard/lib/schemas/ratelimit.ts (2)
  • RatelimitFormContextValues (52-57)
  • RatelimitItem (48-48)
⏰ 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 Dashboard / Test Dashboard
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (32)
apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/keys/[keyAuthId]/_components/components/table/components/actions/components/edit-ratelimits/index.tsx (2)

1-6: LGTM on import centralization.

The imports are correctly updated to use the centralized schema and component paths (@/lib/schemas/ratelimit and @/components/dashboard/ratelimits/ratelimit-setup), aligning with the PR's refactoring goals.


50-66: LGTM on hook usage and payload simplification.

The hook is correctly invoked with "key" entity type, and the payload simplification to ratelimit: data.ratelimit is cleaner. The catch block appropriately suppresses console noise while letting the hook handle toast notifications.

apps/dashboard/hooks/use-edit-ratelimits.ts (3)

5-15: Well-structured TypeScript overloads for entity type discrimination.

The overload pattern correctly provides type-safe return values based on entityType. When callers use "key", they get the key mutation type; when using "identity", they get the identity mutation type. This enables proper type inference at call sites.


69-118: LGTM on identity mutation handler.

The implementation correctly mirrors the key mutation pattern with appropriate identity-specific messaging and cache invalidation (trpcUtils.identity.query.invalidate()). Error handling is comprehensive with specific messages for NOT_FOUND and INTERNAL_SERVER_ERROR cases.


120-121: Conditional return correctly handles entity type.

Both mutations must be instantiated unconditionally per React hooks rules, and the conditional return correctly selects the appropriate one. The TypeScript overloads ensure callers receive the correct return type.

apps/dashboard/app/(app)/[workspaceSlug]/apis/[apiId]/_components/create-key/create-key.constants.tsx (1)

6-6: LGTM on import path update.

The import is correctly updated to use the centralized component path, consistent with other files in this PR.

apps/dashboard/app/new/hooks/use-key-creation-step.tsx (1)

13-13: LGTM on import path update.

The import is correctly updated to the centralized component path.

apps/dashboard/lib/trpc/routers/index.ts (2)

65-65: LGTM on import addition.

The import correctly references the new identity ratelimit update procedure.


349-352: LGTM on identity ratelimit route registration.

The route is correctly registered under identity.update.ratelimit, maintaining structural consistency with key.update.ratelimit. This aligns with the hook's expectation in use-edit-ratelimits.ts which calls trpc.identity.update.ratelimit.useMutation().

apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (1)

3-3: LGTM! Clean refactoring to centralized schema.

The import path update to use the centralized ratelimit schema improves maintainability and consistency across the codebase.

apps/dashboard/app/(app)/[workspaceSlug]/identities/navigation.tsx (1)

6-6: LGTM! Import path correctly updated.

The import path reflects the new component location in the dialogs subdirectory.

apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/table/identity-table-actions.tsx (1)

5-5: LGTM! Edit ratelimit feature correctly integrated.

The implementation correctly adds the "Edit ratelimit..." menu item with proper state management and dialog integration, matching the requirements from issue #4459.

Also applies to: 9-9, 16-16, 20-27, 76-80

apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/general-setup.tsx (1)

1-26: LGTM! Clean form component implementation.

The GeneralSetup component correctly integrates with react-hook-form and provides appropriate validation feedback for the externalId field.

apps/dashboard/lib/trpc/routers/identity/search.ts (1)

50-53: LGTM! Ratelimit data properly expanded.

The additional columns (name, limit, duration, autoApply) provide the necessary data for displaying and editing identity ratelimits in the UI.

apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.schema.ts (1)

5-35: LGTM! Schema validation correctly implemented.

The form schema properly validates the externalId field with appropriate length and whitespace constraints, and correctly merges metadata and ratelimit schemas. Default values are sensible for identity creation.

apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/edit-ratelimit-dialog.tsx (1)

23-151: LGTM! Edit ratelimit dialog well-structured.

The dialog correctly initializes from existing identity ratelimits, handles form submission with proper error handling, and provides a clean UI with identity information display and the shared RatelimitSetup component.

apps/dashboard/lib/trpc/routers/identity/create.ts (1)

102-103: LGTM! Audit log metadata correctly updated.

The audit log properly tracks whether ratelimits were included during identity creation and their count.

apps/dashboard/components/dashboard/ratelimits/ratelimit-setup.tsx (3)

10-15: Well-structured props for entity-type flexibility.

The addition of entityType with a sensible default maintains backward compatibility for existing key-based usage while enabling identity-specific behavior.


26-40: Robust error accessor for conditional schema validation.

The getFieldError helper safely navigates the nested error structure from react-hook-form when using conditional/dynamic schemas. The defensive checks handle edge cases well.


78-81: Dynamic description correctly reflects entity context.

The description text appropriately varies based on entityType, providing clear guidance for both key and identity ratelimit configurations.

apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity-dialog.tsx (3)

39-60: Solid error handling with field-level CONFLICT feedback.

The mutation properly handles the CONFLICT error by setting a form-level error on externalId, giving users clear feedback about duplicate external IDs.


81-130: Clean NavigableDialog integration with proper form wiring.

The dialog correctly wraps the form with FormProvider, uses a separate form attribute to connect the submit button outside the form element, and properly manages the open/close state.


117-118: No changes needed. The project uses TanStack Query v4.36.1, where isLoading is the correct property for mutations. This is only a concern when upgrading to v5, which renames isLoading to isPending.

apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts (1)

17-47: Well-structured TRPC procedure with proper middleware chain.

The procedure correctly uses requireUser and requireWorkspace middleware, validates input against the combined schema, and handles the identity lookup with appropriate error responses.

apps/dashboard/lib/trpc/routers/identity/query.ts (2)

22-30: Schema correctly expanded to support ratelimit editing.

The extended ratelimits schema includes all fields needed for the edit ratelimit dialog (name, limit, duration, autoApply). This aligns well with the PR objectives.


113-120: Query projection matches the updated response schema.

The with.ratelimits.columns selection correctly fetches all fields defined in IdentityResponseSchema, ensuring data consistency between the API contract and database query.

apps/dashboard/app/(app)/[workspaceSlug]/identities/_components/dialogs/create-identity.constants.tsx (2)

7-26: Clean section definitions with proper entity context.

The SECTIONS constant follows the established NavigableDialog pattern, correctly passes entityType="identity" to both RatelimitSetup and MetadataSetup, and uses as const for proper type inference with StepNamesFrom.


28-28: Type-safe section name derivation.

Using StepNamesFrom<typeof SECTIONS> ensures DialogSectionName stays in sync with the SECTIONS array, preventing typos and enabling autocomplete.

apps/dashboard/lib/schemas/ratelimit.ts (4)

1-2: LGTM!

The imports are correct and necessary for the schema definitions.


28-31: LGTM!

The validation schema correctly enforces that when enabled, at least one rate limit must be present.


33-45: LGTM!

The conditional schema pattern with default values provides good UX when ratelimits are disabled, and the default rate limit configuration (10 requests per 1000ms) is reasonable.


47-57: LGTM!

The type exports are correctly defined. The manual RatelimitFormContextValues type is a pragmatic solution to avoid TypeScript union issues with conditional schemas when using form libraries.

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

🧹 Nitpick comments (2)
apps/dashboard/lib/schemas/ratelimit.ts (2)

17-23: Enforce integer constraint for limit field.

The PR acceptance criteria specify "limit positive integer", but the current schema allows decimal values. Since rate limits are inherently discrete counts, add .int() to ensure only integers are accepted.

Apply this diff:

   limit: z.coerce
     .number({
       errorMap: (issue, { defaultError }) => ({
         message: issue.code === "invalid_type" ? "Limit must be a valid number" : defaultError,
       }),
     })
-    .positive({ message: "Limit must be greater than 0" }),
+    .int({ message: "Limit must be a whole number" })
+    .positive({ message: "Limit must be greater than 0" }),

10-16: Enforce integer constraint for refillInterval field.

Milliseconds are inherently integers, and allowing decimal values could lead to unexpected behavior or confusion. Add .int() to ensure only whole milliseconds are accepted.

Apply this diff:

   refillInterval: z.coerce
     .number({
       errorMap: (issue, { defaultError }) => ({
         message: issue.code === "invalid_type" ? "Duration must be a valid number" : defaultError,
       }),
     })
-    .min(1000, { message: "Refill interval must be at least 1 second (1000ms)" }),
+    .int({ message: "Refill interval must be a whole number" })
+    .min(1000, { message: "Refill interval must be at least 1 second (1000ms)" }),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91214fb and 0930fdc.

📒 Files selected for processing (2)
  • apps/dashboard/lib/schemas/ratelimit.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/identity/create.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/lib/trpc/routers/identity/create.ts
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 4508
File: apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts:141-147
Timestamp: 2025-12-16T16:52:48.628Z
Learning: In apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts, audit log resources for identity ratelimit updates intentionally use type "ratelimit" with id set to identity.id (rather than type "identity"). This design pattern shows that the ratelimit operations belong to the identity entity.
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.743Z
Learning: PR #4190 for unkeyed/unkey is focused solely on database schema and query changes for identity-based credits. It adds IdentityCredits and KeyCredits fields to structs and queries, but does not implement the priority enforcement logic in the usagelimiter. The logic implementation is intentionally deferred to a later PR in the stack.
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4484
File: go/pkg/db/queries/identity_list.sql:11-24
Timestamp: 2025-12-05T12:05:33.166Z
Learning: In go/pkg/db/queries/identity_list.sql and similar identity ratelimit queries, the RatelimitInfo struct's key_id and identity_id fields can be omitted from JSON_OBJECT construction because: (1) these are identity-level ratelimits where key_id doesn't apply, and (2) identity_id is redundant since it's already known from the parent identity context. Zero values for these fields are acceptable and intentional.
📚 Learning: 2025-12-16T16:52:48.628Z
Learnt from: mcstepp
Repo: unkeyed/unkey PR: 4508
File: apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts:141-147
Timestamp: 2025-12-16T16:52:48.628Z
Learning: In apps/dashboard/lib/trpc/routers/identity/updateRatelimit.ts, audit log resources for identity ratelimit updates intentionally use type "ratelimit" with id set to identity.id (rather than type "identity"). This design pattern shows that the ratelimit operations belong to the identity entity.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
📚 Learning: 2024-10-23T12:05:31.121Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 2544
File: apps/api/src/pkg/env.ts:4-6
Timestamp: 2024-10-23T12:05:31.121Z
Learning: The `cloudflareRatelimiter` type definition in `apps/api/src/pkg/env.ts` should not have its interface changed; it should keep the `limit` method returning `Promise<{ success: boolean }>` without additional error properties.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
📚 Learning: 2024-10-20T07:05:55.471Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In `apps/api/src/pkg/keys/service.ts`, `ratelimitAsync` is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
📚 Learning: 2025-04-22T11:48:39.670Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3156
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx:36-47
Timestamp: 2025-04-22T11:48:39.670Z
Learning: The Unkey dashboard's form validation for numeric values like rate limits is handled through the Zod schema validation (with `.positive()` validators and additional checks in `superRefine`), rather than HTML input attributes like `min`.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
📚 Learning: 2025-03-19T09:25:59.751Z
Learnt from: Flo4604
Repo: unkeyed/unkey 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.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
📚 Learning: 2025-04-22T11:47:24.733Z
Learnt from: ogzhanolguncu
Repo: unkeyed/unkey PR: 3156
File: apps/dashboard/app/(app)/apis/[apiId]/_components/create-key/components/ratelimit-setup.tsx:48-59
Timestamp: 2025-04-22T11:47:24.733Z
Learning: For form inputs in the Unkey dashboard, validation constraints are handled at the schema level using Zod rather than using HTML attributes like min/step on numeric inputs.

Applied to files:

  • apps/dashboard/lib/schemas/ratelimit.ts
🔇 Additional comments (3)
apps/dashboard/lib/schemas/ratelimit.ts (3)

27-46: LGTM! Name uniqueness validation correctly implemented.

The superRefine implementation properly enforces unique ratelimit names using a Set for efficient tracking and correctly reports validation errors with the appropriate path. This addresses the acceptance criteria requiring "non-empty unique names."


48-60: LGTM! Conditional schema and defaults are well-structured.

The use of createConditionalSchema with sensible defaults (enabled: false, with a default ratelimit entry) provides a good user experience and allows flexible ratelimit configuration.


62-72: LGTM! Type exports are well-defined.

The type exports provide good type safety, and the manual RatelimitFormContextValues type is a pragmatic solution to avoid conditional schema union issues in form contexts.

Copy link
Member

@perkinsjr perkinsjr left a comment

Choose a reason for hiding this comment

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

Alright LGTM

@perkinsjr perkinsjr merged commit c1c167d into main Dec 17, 2025
9 of 11 checks passed
@perkinsjr perkinsjr deleted the ENG-2270-edit-identity-ratelimits branch December 17, 2025 13:29
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.

Identity Edit Ratelimits

2 participants