Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: transaction deadlock in trpc #2843

Merged
merged 18 commits into from
Feb 7, 2025
Merged

fix: transaction deadlock in trpc #2843

merged 18 commits into from
Feb 7, 2025

Conversation

chronark
Copy link
Collaborator

@chronark chronark commented Jan 27, 2025

Fixes transaction deadlocks around audit log buckets.

This moves the workspace fetch to the beginning of the trpc handler and
loads the bucket id once, to be reused.

In theory we can now cache the workspace lookup but I'm not sure if
Next.js does that automatically now or if we need to do it.

Summary by CodeRabbit

  • New Features

    • Introduced LLM-powered log search, enabling natural language structured queries.
    • Added a system health (liveness) endpoint for real-time service monitoring.
  • Removed Features

    • Deprecated legacy LLM Gateway management functionality.
  • Improvements

    • Enhanced input validations for API naming, providing clearer user feedback.
    • Streamlined workspace operations for a more consistent and reliable experience.

Fixes transaction deadlocks around audit log buckets.

This moves the workspace fetch to the beginning of the trpc handler and
loads the bucket id once, to be reused.

In theory we can now cache the workspace lookup but I'm not sure if
Next.js does that automatically now or if we need to do it.
Copy link

vercel bot commented Jan 27, 2025

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

Name Status Preview Comments Updated (UTC)
dashboard 🛑 Canceled (Inspect) Feb 7, 2025 0:42am
engineering 🛑 Canceled (Inspect) Feb 7, 2025 0:42am
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 7, 2025 0:42am
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 7, 2025 0:42am

Copy link

changeset-bot bot commented Jan 27, 2025

⚠️ No Changeset found

Latest commit: 7e64b9e

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

Copy link
Contributor

coderabbitai bot commented Jan 27, 2025

Warning

Rate limit exceeded

@chronark has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 22 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9100a24 and 7e64b9e.

⛔ Files ignored due to path filters (2)
  • go/gen/proto/ratelimit/v1/ratelimitv1connect/service.connect.go is excluded by !**/gen/**
  • go/gen/proto/ratelimit/v1/service.pb.go is excluded by !**/*.pb.go, !**/gen/**
📒 Files selected for processing (31)
  • .github/workflows/job_test_api_canary.yaml (1 hunks)
  • .github/workflows/job_test_api_local.yaml (1 hunks)
  • .github/workflows/job_test_api_staging.yaml (1 hunks)
  • .github/workflows/pr.yaml (1 hunks)
  • apps/dashboard/app/(app)/logs/components/controls/components/logs-filters/components/paths-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/logs/components/controls/components/logs-search/index.tsx (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/logs/llm-search.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/logs/llm-search/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/logs/query-distinct-paths.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/logs/query-timeseries/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/createPermission.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/createRole.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (2 hunks)
  • go/buf.gen.yaml (1 hunks)
  • go/buf.yaml (1 hunks)
  • go/cmd/api/routes/v2_liveness/handler.go (1 hunks)
  • go/internal/services/ratelimit/bucket.go (1 hunks)
  • go/internal/services/ratelimit/interface.go (1 hunks)
  • go/internal/services/ratelimit/peer.go (1 hunks)
  • go/internal/services/ratelimit/replay.go (1 hunks)
  • go/internal/services/ratelimit/sequence.go (1 hunks)
  • go/internal/services/ratelimit/sliding_window.go (1 hunks)
  • go/internal/services/ratelimit/window.go (1 hunks)
  • go/pkg/certificate/amazon_certificate_manager.go (1 hunks)
  • go/pkg/certificate/autocert.go (1 hunks)
  • go/pkg/certificate/interface.go (1 hunks)
  • go/pkg/circuitbreaker/interface.go (1 hunks)
  • go/pkg/circuitbreaker/lib.go (1 hunks)
  • go/pkg/circuitbreaker/lib_test.go (1 hunks)
  • go/pkg/circuitbreaker/metrics.go (1 hunks)
📝 Walkthrough

Walkthrough

This change set updates audit logging across multiple components by modifying the insertAuditLogs function to require an explicit audit log bucket ID from the workspace context. Several TRPC router procedures and UI components have been refactored to directly use workspace data (ctx.workspace) while removing redundant database queries and tenant validations. New type definitions and schema modifications were introduced, and additional packages for LLM search, rate limiting, certificate management, and circuit breaking were added. GitHub workflow configuration files were also adjusted for formatting and clarity.

Changes

File(s) Change Summary
apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx Updated insertAuditLogs call to include workspace auditLogBucket ID as second parameter.
apps/dashboard/app/new/create-ratelimit.tsx
apps/dashboard/app/new/page.tsx
Introduced new Props type for CreateRatelimit and updated component signatures; added unique bucket ID generation and passed workspace.auditLogBucket to components.
apps/dashboard/lib/audit.ts Modified insertAuditLogs signature to require a bucketId parameter; removed internal bucket lookup logic and related constants.
apps/dashboard/lib/trpc/context.ts Enhanced context creation to include workspace data with an associated auditLogBucket; added logic to retrieve or create the bucket.
apps/dashboard/lib/trpc/routers/api/,
apps/dashboard/lib/trpc/routers/key/
,
apps/dashboard/lib/trpc/routers/rbac/,
apps/dashboard/lib/trpc/routers/workspace/
.ts,
apps/dashboard/lib/trpc/vercel.ts
Updated query filters and mutation logic to use ctx.workspace.id and ctx.workspace.auditLogBucket.id; removed redundant workspace retrieval and tenant validations.
apps/dashboard/lib/trpc/routers/logs/llm-search*.ts Integrated new LLM search mutation with structured search via OpenAI API; added helper for system prompt and rate limiting enforcement.
apps/dashboard/lib/trpc/routers/ratelimit/*.ts Refined namespace and override procedures to restrict queries to ctx.workspace.id and updated audit log calls accordingly.
.github/workflows/*.yaml
.github/workflows/pr.yaml
Applied formatting changes, removed deprecated workflow steps, and updated job names and shard matrix formatting.
internal/db/src/types.ts
internal/db/src/schema/workspaces.ts
Added new AuditLogBucket type and removed the 'ratelimit' property from the betaFeatures schema.
apps/dashboard/app/(app)/logs/components/controls/components/logs-filters/paths-filter.tsx
apps/dashboard/app/(app)/logs/components/controls/components/logs-search/index.tsx
Simplified query parameters and removed unused memoization in log filtering and search components.
go/buf.gen.yaml,
go/buf.yaml
Added new Buf configuration files defining versions and plugin specifications for code generation.
go/cmd/api/routes/v2_liveness/handler.go Introduced a new v2 liveness check HTTP route using the zen framework, returning a status message.
go/internal/services/ratelimit/* Added a new ratelimit package with bucket, window, sliding window, and sequence logic for rate limiting management.
go/pkg/certificate/* Introduced new certificate management packages for ACM support, autocert-based certificate retrieval, and defined a certificate Source interface.
go/pkg/circuitbreaker/*
go/pkg/circuitbreaker/lib_test.go
go/pkg/circuitbreaker/metrics.go
Added a generic circuit breaker implementation with configuration options, middleware, Prometheus metrics integration, and accompanying unit tests.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Context
  participant Database
  Client->>Context: API Request initiates context creation
  Context->>Database: Query workspace by tenantId
  Database-->>Context: Return workspace (with/without auditLogBucket)
  alt AuditLogBucket missing
    Context->>Database: Insert new auditLogBucket record
    Database-->>Context: Return new bucket ID
  end
  Context-->>Client: Respond with context { workspace, auditLogBucket }
Loading
sequenceDiagram
  participant Client
  participant llmSearch
  participant RateLimiter
  participant OpenAI
  Client->>llmSearch: Send search query
  llmSearch->>RateLimiter: Enforce rate limit
  RateLimiter-->>llmSearch: Permit request
  llmSearch->>OpenAI: Request structured search (query)
  OpenAI-->>llmSearch: Return structured result
  llmSearch-->>Client: Deliver search result
Loading

Possibly related PRs

Suggested labels

Bug, Core Team

Suggested reviewers

  • mcstepp
  • perkinsjr
  • MichaelUnkey

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 for this PR. (Beta)
  • @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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented Jan 27, 2025

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

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

🔭 Outside diff range comments (5)
apps/dashboard/lib/trpc/routers/rbac/connectPermissionToRole.ts (1)

Line range hint 15-31: Potential race condition between workspace fetches.

The code fetches workspace data directly from the database while also relying on ctx.workspace.auditLogBucket.id later. This creates a potential consistency issue where the workspace in context might differ from the one fetched. Consider using the workspace from context throughout to maintain consistency and prevent deadlocks.

- const workspace = await db.query.workspaces
-   .findFirst({
-     where: (table, { and, eq, isNull }) =>
-       and(eq(table.tenantId, ctx.tenant.id), isNull(table.deletedAt)),
-     with: {
-       roles: {
-         where: (table, { eq }) => eq(table.id, input.roleId),
-       },
-       permissions: {
-         where: (table, { eq }) => eq(table.id, input.permissionId),
-       },
-     },
-   })
+ // Use workspace from context
+ const workspace = ctx.workspace;
+ // Fetch only the required role and permission
+ const [role, permission] = await Promise.all([
+   db.query.roles.findFirst({
+     where: (table, { eq }) => eq(table.id, input.roleId),
+   }),
+   db.query.permissions.findFirst({
+     where: (table, { eq }) => eq(table.id, input.permissionId),
+   }),
+ ]);
apps/dashboard/lib/trpc/routers/workspace/changeName.ts (1)

The race condition concern in the audit log is valid and should be addressed.

The current implementation could lead to incorrect audit logs if concurrent name updates occur. To fix this:

  • Fetch the current workspace name within the transaction before the update
  • Use the fetched name in the audit log instead of ctx.workspace.name

Example locations that need attention:

  • apps/dashboard/lib/trpc/routers/workspace/changeName.ts: Move workspace name retrieval inside the transaction block before the update.
🔗 Analysis chain

Line range hint 31-46: Consider fetching the current workspace name within the transaction.

The current implementation reads ctx.workspace.name from the context, which could be stale if another transaction updates the name concurrently. Consider fetching the current name within the transaction for the audit log.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for concurrent workspace name updates
rg -A 5 "update.*workspaces.*set.*name" --type ts

Length of output: 51


Script:

#!/bin/bash
# Search for workspace updates using multiple patterns
echo "=== Searching for Prisma workspace updates ==="
rg "workspace\s*\.\s*update" -A 5 --type ts

echo -e "\n=== Searching for workspace name changes ==="
rg "workspace.*name.*=" -A 5 --type ts

echo -e "\n=== Checking transaction patterns ==="
ast-grep --pattern 'prisma.$transaction(async ($$$) => { $$$ })'

Length of output: 9136


Script:

#!/bin/bash
# Search for Prisma workspace name updates with various patterns
echo "=== Searching for Prisma workspace name updates ==="
rg "prisma.*workspace.*update.*name" -A 5 --type ts

echo -e "\n=== Searching for updateName implementation ==="
ast-grep --pattern 'updateName = procedure
  .$$$
  .mutation(async ({ $$$, input }) => {
    $$$
  })'

echo -e "\n=== Checking for direct name updates ==="
rg "name.*=.*input\.name" -A 5 --type ts

Length of output: 523


Script:

#!/bin/bash
# Check the changeName.ts implementation
echo "=== Checking changeName.ts implementation ==="
cat apps/dashboard/lib/trpc/routers/workspace/changeName.ts

echo -e "\n=== Searching for update in changeName.ts ==="
rg "update" apps/dashboard/lib/trpc/routers/workspace/changeName.ts -A 5

Length of output: 3338

apps/dashboard/lib/trpc/routers/rbac/updatePermission.ts (1)

Line range hint 38-46: Add error handling for permission update operation.

The permission update operation inside the transaction is missing error handling, unlike similar operations in other files.

 await tx
   .update(schema.permissions)
   .set({
     name: input.name,
     description: input.description,
     updatedAt: new Date(),
   })
-  .where(eq(schema.permissions.id, permission.id));
+  .where(eq(schema.permissions.id, permission.id))
+  .catch((_err) => {
+    throw new TRPCError({
+      code: "INTERNAL_SERVER_ERROR",
+      message: "We are unable to update the permission. Please try again or contact support@unkey.dev.",
+    });
+  });
apps/dashboard/lib/trpc/routers/key/delete.ts (1)

Line range hint 14-33: Consider moving workspace fetch to use context.

The separate workspace fetch could still cause deadlocks. Consider using ctx.workspace instead, similar to other files in this PR.

-    const workspace = await db.query.workspaces
-      .findFirst({
-        where: (table, { and, eq, isNull }) =>
-          and(eq(table.tenantId, ctx.tenant.id), isNull(table.deletedAt)),
-        with: {
-          keys: {
-            where: (table, { and, inArray, isNull }) =>
-              and(isNull(table.deletedAt), inArray(table.id, input.keyIds)),
-            columns: {
-              id: true,
-            },
-          },
-        },
-      })
+    const keys = await db.query.keys
+      .findMany({
+        where: (table, { and, inArray, isNull }) =>
+          and(
+            eq(table.workspaceId, ctx.workspace.id),
+            isNull(table.deletedAt),
+            inArray(table.id, input.keyIds)
+          ),
+        columns: {
+          id: true,
+        },
+      })
apps/dashboard/lib/trpc/routers/api/updateDeleteProtection.ts (1)

Line range hint 63-66: Fix template literal syntax error.

There's an extra closing brace in the description string.

-          description: `API ${api.name} delete protection is now ${
-            input.enabled ? "enabled" : "disabled"
-          }.}`,
+          description: `API ${api.name} delete protection is now ${
+            input.enabled ? "enabled" : "disabled"
+          }.`,
🧹 Nitpick comments (26)
apps/dashboard/lib/trpc/routers/rbac/connectPermissionToRole.ts (2)

Line range hint 32-48: Enhance error handling with specific error messages and proper error propagation.

The current error handling:

  1. Uses generic error messages
  2. Swallows original errors in catch blocks
  3. Doesn't distinguish between different types of errors

Consider adding more specific error messages and proper error logging:

 if (!role) {
   throw new TRPCError({
     code: "NOT_FOUND",
-    message:
-      "We are unable to find the correct role. Please try again or contact support@unkey.dev.",
+    message: `Role with ID ${input.roleId} not found in workspace ${workspace.id}.`,
   });
 }
 if (!permission) {
   throw new TRPCError({
     code: "NOT_FOUND",
-    message:
-      "We are unable to find the correct permission. Please try again or contact support@unkey.dev.",
+    message: `Permission with ID ${input.permissionId} not found in workspace ${workspace.id}.`,
   });
 }

Line range hint 79-98: Improve audit log description and context.

While the audit logging changes address the deadlock issue by using a consistent bucket ID, the audit log description could be more informative.

   await insertAuditLogs(tx, ctx.workspace.auditLogBucket.id, {
     workspaceId: workspace.id,
     actor: { type: "user", id: ctx.user.id },
     event: "authorization.connect_role_and_permission",
-    description: `Connect role ${role.id} to ${permission.id}`,
+    description: `Connected role "${role.name}" (${role.id}) to permission "${permission.name}" (${permission.id})`,
     resources: [
       {
         type: "role",
         id: role.id,
+        name: role.name,
       },
       {
         type: "permission",
         id: permission.id,
+        name: permission.name,
       },
     ],
     context: {
       location: ctx.audit.location,
       userAgent: ctx.audit.userAgent,
+      roleId: role.id,
+      permissionId: permission.id,
     },
   });
apps/dashboard/lib/trpc/routers/ratelimit/deleteNamespace.ts (2)

Line range hint 74-82: Improve error message specificity in catch blocks.

The error messages in both catch blocks are identical but handle different operations (override deletion vs audit log insertion). Consider making them more specific to help with debugging.

-              message:
-                "We are unable to delete the namespaces. Please try again or contact support@unkey.dev",
+              message:
+                "Failed to delete rate limit overrides. Please try again or contact support@unkey.dev",

// And in the audit log catch block:
-              message:
-                "We are unable to delete the namespaces. Please try again or contact support@unkey.dev",
+              message:
+                "Failed to record audit logs for override deletion. Please try again or contact support@unkey.dev",

Also applies to: 108-116


Line range hint 45-60: Consider extracting common audit log properties.

The audit log entries for both namespace and override deletions share common properties (actor, context). Consider extracting these to reduce duplication and improve maintainability.

+ const commonAuditProps = {
+   workspaceId: ctx.workspace.id,
+   actor: {
+     type: "user" as const,
+     id: ctx.user.id,
+   },
+   context: {
+     location: ctx.audit.location,
+     userAgent: ctx.audit.userAgent,
+   },
+ };

  await insertAuditLogs(tx, ctx.workspace.auditLogBucket.id, {
-   workspaceId: namespace.workspaceId,
-   actor: {
-     type: "user",
-     id: ctx.user.id,
-   },
+   ...commonAuditProps,
    event: "ratelimitNamespace.delete",
    description: `Deleted ${namespace.id}`,
    resources: [/*...*/],
-   context: {
-     location: ctx.audit.location,
-     userAgent: ctx.audit.userAgent,
-   },
  });

  // Later in the overrides audit logs
  await insertAuditLogs(
    tx,
    ctx.workspace.auditLogBucket.id,
    overrides.map(({ id }) => ({
-     workspaceId: ctx.workspace.id,
-     actor: {
-       type: "user",
-       id: ctx.user.id,
-     },
+     ...commonAuditProps,
      event: "ratelimit.delete_override",
      description: `Deleted ${id} as part of the ${namespace.id} deletion`,
      resources: [/*...*/],
-     context: {
-       location: ctx.audit.location,
-       userAgent: ctx.audit.userAgent,
-     },
    })),
  );

Also applies to: 84-106

apps/dashboard/lib/trpc/routers/rbac/removePermissionFromRootKey.ts (1)

59-60: Consider schema optimization for audit logs.

The current implementation correctly uses ctx.workspace.auditLogBucket.id to prevent deadlocks, which is good. However, I notice that audit logs require both bucket.id and workspace.id. Consider if this dual ID requirement could be simplified in the schema to further reduce potential deadlock scenarios.

apps/dashboard/lib/trpc/context.ts (1)

14-45: Be mindful of concurrency when creating a new audit log bucket.

If two concurrent requests try to create the same bucket for the same workspace, you risk duplicate records or deadlocks. Consider placing a unique constraint on (workspaceId, name) in the database and re-check if the bucket was created during the transaction to avoid potential conflicts.

apps/dashboard/lib/trpc/routers/ratelimit/updateNamespaceName.ts (1)

38-38: Clarify the error message to match the missing namespace context.

Current message references a workspace, which might confuse users when the real issue is a missing namespace. Consider updating the error message to reflect that the namespace was not found or is not associated with the current workspace.

apps/dashboard/lib/trpc/routers/workspace/changePlan.ts (1)

124-124: Stripe-related checks are sound, but handle non-default payment scenarios carefully.

Some users might have multiple payment methods, expired cards, or upcoming invoice items. As an optional refinement, consider clarifying or logging edge cases to assist with support issues.

Also applies to: 131-131

apps/dashboard/lib/trpc/routers/rbac/disconnectRoleFromKey.ts (3)

21-24: Consider adding NOWAIT or SKIP LOCKED to prevent deadlocks.

While using transactions is correct, consider adding lock timeouts or skip locked options to prevent potential deadlocks when multiple transactions compete for the same keysRoles records.

 await tx
   .delete(schema.keysRoles)
+  .setLockTimeout(3000) // 3 seconds timeout
   .where(
     and(
       eq(schema.keysRoles.workspaceId, ctx.workspace.id),
       eq(schema.keysRoles.roleId, input.roleId),
       eq(schema.keysRoles.keyId, input.keyId),
     ),
   );

Line range hint 26-44: Consider separating audit log insertion from the main transaction.

The current transaction combines both the role disconnection and audit log insertion. Consider separating these operations to reduce transaction duration and minimize contention on the audit log bucket.


Line range hint 45-52: Enhance error handling with specific error cases.

The current error handling is generic. Consider adding specific error cases for common failures like:

  • Role not found
  • Key not found
  • Permission denied
  • Deadlock detected
 .catch((err) => {
   console.error(err);
+  if (err.code === '40P01') { // PostgreSQL deadlock detected
+    throw new TRPCError({
+      code: "CONFLICT",
+      message: "Operation conflicted with another request. Please try again.",
+    });
+  }
   throw new TRPCError({
     code: "INTERNAL_SERVER_ERROR",
     message:
       "We are unable to disconnect the role from the key. Please try again or contact support@unkey.dev",
   });
 });
apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts (1)

Line range hint 1-1: Consider implementing workspace caching as mentioned in PR objectives.

The changes consistently move workspace fetching to the trpc handler context, which effectively addresses the transaction deadlock issues. As mentioned in the PR objectives, there's potential for further optimization through workspace caching.

Consider implementing a caching layer for workspace data if Next.js doesn't already handle this. This could further reduce database load and improve performance. Some options to consider:

  1. React Query's caching mechanisms
  2. Next.js Server Components with built-in caching
  3. Redis-based caching for workspace data
apps/dashboard/lib/trpc/routers/rbac/deletePermission.ts (1)

32-33: Redundant workspace ID check in delete query.

The workspace ID check in the delete query is redundant since we already verified the permission belongs to the workspace. This adds an unnecessary condition to the index scan.

           .delete(schema.permissions)
           .where(
-            and(
-              eq(schema.permissions.id, permission.id),
-              eq(schema.permissions.workspaceId, ctx.workspace.id),
-            ),
+            eq(schema.permissions.id, permission.id),
           );
apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (1)

14-32: Optimize switch statement using object literal.

The switch statement can be simplified using an object literal mapping, making it more maintainable and less prone to errors.

+    const betaFeatureMap = {
+      rbac: 'rbac',
+      identities: 'identities',
+      ratelimit: 'ratelimit',
+      logsPage: 'logsPage'
+    } as const;
+    
+    ctx.workspace.betaFeatures[betaFeatureMap[input.feature]] = true;
-    switch (input.feature) {
-      case "rbac": {
-        ctx.workspace.betaFeatures.rbac = true;
-        break;
-      }
-      case "identities": {
-        ctx.workspace.betaFeatures.identities = true;
-        break;
-      }
-      case "ratelimit": {
-        ctx.workspace.betaFeatures.ratelimit = true;
-        break;
-      }
-      case "logsPage": {
-        ctx.workspace.betaFeatures.logsPage = true;
-        break;
-      }
-    }
apps/dashboard/lib/trpc/routers/rbac/deleteRole.ts (1)

Line range hint 28-58: Simplify error handling to avoid nested catch blocks.

The current implementation has nested catch blocks that could mask errors and complicate transaction rollback. Consider simplifying the error handling.

     await db.transaction(async (tx) => {
-      await tx
-        .delete(schema.roles)
-        .where(
-          and(eq(schema.roles.id, input.roleId), eq(schema.roles.workspaceId, ctx.workspace.id)),
-        )
-        .catch((_err) => {
-          throw new TRPCError({
-            code: "INTERNAL_SERVER_ERROR",
-            message:
-              "We are unable to delete the role. Please try again or contact support@unkey.dev",
-          });
-        });
+      await tx.delete(schema.roles).where(
+        and(eq(schema.roles.id, input.roleId), eq(schema.roles.workspaceId, ctx.workspace.id)),
+      );

-      await insertAuditLogs(tx, ctx.workspace.auditLogBucket.id, {
-        // ... audit log details ...
-      }).catch((err) => {
-        console.error(err);
-        throw new TRPCError({
-          code: "INTERNAL_SERVER_ERROR",
-          message:
-            "We are unable to delete the role. Please try again or contact support@unkey.dev.",
-        });
-      });
+      await insertAuditLogs(tx, ctx.workspace.auditLogBucket.id, {
+        // ... audit log details ...
+      });
     });
apps/dashboard/lib/trpc/routers/key/updateEnabled.ts (1)

53-54: Consider using consistent workspace ID source.

While the audit log bucket ID is correctly taken from ctx.workspace, the workspaceId is still taken from key.workspace.id. For consistency and to prevent potential issues, consider using ctx.workspace.id for both.

  await insertAuditLogs(tx, ctx.workspace.auditLogBucket.id, {
-   workspaceId: key.workspace.id,
+   workspaceId: ctx.workspace.id,
    actor: {
apps/dashboard/lib/trpc/routers/key/updateOwnerId.ts (1)

Line range hint 61-61: Consider sanitizing ownerId in audit log message.

The audit log message includes the raw ownerId value. Consider handling null case and potentially masking or formatting the ID for better readability.

-          description: `Changed ownerId of ${key.id} to ${input.ownerId}`,
+          description: `Changed ownerId of ${key.id} to ${input.ownerId ?? 'null'}`,
apps/dashboard/lib/trpc/routers/ratelimit/deleteOverride.ts (2)

Line range hint 59-85: Consider consolidating error handling.

The error handling for audit logs could be moved to the transaction catch block to maintain consistency and reduce code duplication.

       await insertAuditLogs(tx, ctx.workspace.auditLogBucket.id, {
         workspaceId: ctx.workspace.id,
         // ... audit log details ...
-      }).catch((_err) => {
-        throw new TRPCError({
-          message:
-            "We are unable to delete the override. Please try again or contact support@unkey.dev",
-          code: "INTERNAL_SERVER_ERROR",
-        });
-      });
+      });
     });

Line range hint 1-1: Overall PR Assessment

The changes effectively address the transaction deadlock issues by standardizing workspace and audit log bucket handling. However, there are a few items that need attention:

  1. The delete.ts file still performs a separate workspace fetch that could cause deadlocks
  2. There's a syntax error in the audit log description in updateDeleteProtection.ts
  3. Some files have redundant error handling that could be consolidated

The overall approach of using ctx.workspace is solid and should prevent the deadlock issues once these items are addressed.

apps/dashboard/lib/trpc/routers/api/setDefaultBytes.ts (1)

24-28: Consider reordering WHERE conditions for optimal index usage.

The query's WHERE conditions should be ordered with the most selective condition first. Consider moving eq(table.id, input.keyAuthId) before the workspace check since IDs are typically more selective.

  and(
-   eq(table.workspaceId, ctx.workspace.id),
-   eq(table.id, input.keyAuthId),
+   eq(table.id, input.keyAuthId),
+   eq(table.workspaceId, ctx.workspace.id),
    isNull(table.deletedAt),
  ),
apps/dashboard/lib/trpc/routers/key/updateMetadata.ts (1)

32-36: Consider reordering WHERE conditions for optimal index usage.

Similar to other routers, consider moving the ID check before the workspace check for better index utilization.

  and(
-   eq(table.workspaceId, ctx.workspace.id),
-   eq(table.id, input.keyId),
+   eq(table.id, input.keyId),
+   eq(table.workspaceId, ctx.workspace.id),
    isNull(table.deletedAt),
  ),
apps/dashboard/lib/trpc/routers/key/updateRemaining.ts (1)

31-35: Consider reordering WHERE conditions for optimal index usage.

For consistency with other routers and optimal index usage, consider moving the ID check before the workspace check.

  and(
-   eq(table.workspaceId, ctx.workspace.id),
-   eq(table.id, input.keyId),
+   eq(table.id, input.keyId),
+   eq(table.workspaceId, ctx.workspace.id),
    isNull(table.deletedAt),
  ),
apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (2)

23-27: Improve error message specificity.

While the query changes look good, the error message could be more specific about the namespace not being found in the current workspace.

-          "We are unable to find the correct namespace. Please try again or contact support@unkey.dev.",
+          "The namespace was not found in your workspace. Please verify the namespace ID and try again.",

Also applies to: 36-42


58-59: Extract the default rate limit override value.

Consider extracting the magic number 5 into a named constant for better maintainability.

+const DEFAULT_MAX_RATELIMIT_OVERRIDES = 5;
+
 const max =
   typeof ctx.workspace.features.ratelimitOverrides === "number"
     ? ctx.workspace.features.ratelimitOverrides
-    : 5;
+    : DEFAULT_MAX_RATELIMIT_OVERRIDES;
apps/dashboard/lib/trpc/routers/key/create.ts (1)

41-42: Improve error message clarity.

The error message could be more specific about the key auth not being found in the current workspace.

-          "We are unable to find the correct keyAuth. Please try again or contact support@unkey.dev",
+          "The key auth was not found in your workspace. Please verify the keyAuth ID and try again.",

Also applies to: 54-60

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

101-102: Consider consolidating error handling.

The error handling logic is duplicated between the enable and disable paths. Consider extracting it into a reusable function.

+const handleRatelimitError = (err: unknown) => {
+  console.error(err);
+  throw new TRPCError({
+    code: "INTERNAL_SERVER_ERROR",
+    message:
+      "We were unable to update ratelimit on this key. Please try again or contact support@unkey.dev",
+  });
+};

 // In the enable path
-}).catch((_err) => {
-  throw new TRPCError({
-    code: "INTERNAL_SERVER_ERROR",
-    message:
-      "We were unable to update ratelimit on this key. Please try again or contact support@unkey.dev",
-  });
-});
+}).catch(handleRatelimitError);

 // In the disable path
-}).catch((err) => {
-  console.error(err);
-  throw new TRPCError({
-    code: "INTERNAL_SERVER_ERROR",
-    message:
-      "We were unable to update ratelimit on this key. Please try again or contact support@unkey.dev",
-  });
-});
+}).catch(handleRatelimitError);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6a55035 and 4f2db1f.

📒 Files selected for processing (58)
  • apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx (2 hunks)
  • apps/dashboard/app/new/create-ratelimit.tsx (1 hunks)
  • apps/dashboard/app/new/page.tsx (1 hunks)
  • apps/dashboard/lib/audit.ts (1 hunks)
  • apps/dashboard/lib/trpc/context.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/api/create.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/api/delete.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/api/setDefaultBytes.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateDeleteProtection.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateName.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/audit/fetch.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/key/create.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/key/createRootKey.ts (6 hunks)
  • apps/dashboard/lib/trpc/routers/key/delete.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/key/deleteRootKey.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateEnabled.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateExpiration.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateMetadata.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateName.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateOwnerId.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRatelimit.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRemaining.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/key/updateRootKeyName.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/llmGateway/create.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/llmGateway/delete.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/logs/llm-search.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/logs/query-log.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/logs/query-timeseries.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (5 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/deleteNamespace.ts (4 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/deleteOverride.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/updateNamespaceName.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/updateOverride.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/rbac.ts (12 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/addPermissionToRootKey.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/connectPermissionToRole.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/connectRoleToKey.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/createPermission.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/createRole.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/deletePermission.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/deleteRole.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/disconnectPermissionFromRole.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/disconnectRoleFromKey.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/removePermissionFromRootKey.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/updatePermission.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/updateRole.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/rbac/upsertPermission.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/vercel.ts (10 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/changeName.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/changePlan.ts (6 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/create.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (2 hunks)
  • apps/dashboard/lib/trpc/trpc.ts (1 hunks)
  • internal/db/src/types.ts (1 hunks)
💤 Files with no reviewable changes (3)
  • apps/dashboard/lib/trpc/routers/llmGateway/create.ts
  • apps/dashboard/lib/trpc/routers/llmGateway/delete.ts
  • apps/dashboard/lib/trpc/routers/index.ts
🧰 Additional context used
📓 Learnings (1)
apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts (1)
Learnt from: chronark
PR: unkeyed/unkey#2146
File: apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts:80-80
Timestamp: 2024-11-10T16:45:16.994Z
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.
🔇 Additional comments (63)
apps/dashboard/lib/trpc/routers/rbac/connectPermissionToRole.ts (1)

Line range hint 49-78: Consider using a unique constraint for role-permission pairs.

The current implementation uses onDuplicateKeyUpdate, but it's not clear if there's a unique constraint ensuring role-permission pairs are unique within a workspace.

Run this script to verify the table structure:

✅ Verification successful

Unique constraints are properly implemented

The schema already has two levels of constraints ensuring uniqueness:

  • A primary key on (roleId, permissionId, workspaceId) preventing duplicates within a workspace
  • A unique constraint on (permissionId, roleId) preventing duplicates globally

The current implementation using onDuplicateKeyUpdate is appropriate and works correctly with these constraints.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there's a unique constraint on the roles_permissions table
ast-grep --pattern 'createTable("roles_permissions", (table) => {
  $$$
  table.unique($$$)
  $$$
})'

Length of output: 103


Script:

#!/bin/bash
# Search for files containing roles_permissions table definition
rg -l "roles_permissions" --type sql --type typescript

# Then search for content in those files
rg "roles_permissions" -A 10 -B 10 --type sql --type typescript

Length of output: 188


Script:

#!/bin/bash
# Find TypeScript files containing roles_permissions
fd -e ts | xargs rg "roles_permissions" -A 10 -B 10

# Specifically look for schema definitions
fd -e ts | xargs rg "schema.*roles_permissions|createTable.*roles_permissions" -A 10 -B 10

Length of output: 5183

apps/dashboard/lib/trpc/routers/ratelimit/deleteNamespace.ts (2)

18-22: LGTM! Query conditions are well structured.

The workspace check is correctly moved to the query level, which helps prevent potential race conditions and aligns with the PR's objective of reducing deadlocks.


Line range hint 1-116: Verify transaction deadlock resolution.

The changes effectively address the transaction deadlock issue by consistently using workspace context and reducing duplicate workspace fetches. However, let's verify that this is the only procedure accessing these tables to ensure we've caught all potential deadlock scenarios.

✅ Verification successful

Transaction patterns are properly implemented to prevent deadlocks

The codebase follows consistent transaction patterns across all ratelimit operations:

  • Pre-transaction validations with workspace context
  • Atomic operations within transactions
  • Proper cascading updates (e.g., namespace deletion updating related overrides)
  • No circular dependencies or nested transactions
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other procedures that might access ratelimitNamespaces or ratelimitOverrides tables
echo "Searching for other procedures accessing the same tables..."
rg -l "ratelimitNamespaces|ratelimitOverrides" --type ts

# Search for potential transaction blocks
echo "Searching for transaction blocks in related files..."
ast-grep --pattern 'db.transaction' 

Length of output: 11777


Script:

#!/bin/bash
# Examine transaction patterns in ratelimit-related files
echo "Examining ratelimit router transaction patterns..."
for file in $(find apps/dashboard/lib/trpc/routers/ratelimit -type f -name "*.ts"); do
  echo "=== $file ==="
  rg "transaction|findFirst|findMany|update|insert|delete" "$file" -A 2 -B 2
done

Length of output: 11650

apps/dashboard/lib/trpc/routers/rbac/removePermissionFromRootKey.ts (2)

20-20: LGTM! Good optimization for deadlock prevention.

Using ctx.workspace.id directly instead of fetching the workspace separately is a good optimization. This aligns with the PR's objective of preventing deadlocks by moving workspace fetching to the beginning of the tRPC handler.


Line range hint 18-85: Verify transaction isolation level for deadlock prevention.

The transaction structure looks good with proper atomicity and error handling. To further prevent deadlocks, verify that the database is configured with an appropriate isolation level (e.g., READ COMMITTED) for these workspace-related transactions.

Run this script to check the current transaction isolation level:

✅ Verification successful

Transaction implementation is safe with default isolation level

The current implementation is safe as it leverages PlanetScale's default REPEATABLE READ isolation level, which prevents dirty reads and non-repeatable reads. The transaction's structure follows best practices with proper error handling and atomic operations. No additional isolation configuration is needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check transaction isolation configuration in database setup

# Search for isolation level configuration
rg -i "isolation.*level|BEGIN\s+TRANSACTION" --type ts

# Look for any custom transaction settings
ast-grep --pattern 'transaction($$$)' 

Length of output: 8341


Script:

#!/bin/bash
# Search for database configuration and transaction settings
rg -i "drizzle.*config|createPool|connection.*options" --type ts

# Look for any custom transaction configurations in Drizzle setup
ast-grep --pattern 'drizzle($$$)'

Length of output: 18013


Script:

#!/bin/bash
# Check for any transaction-related configurations in the specific file
rg -A 5 -B 5 "transaction" "apps/dashboard/lib/trpc/routers/rbac/removePermissionFromRootKey.ts"

# Look for any transaction-related imports
ast-grep --pattern 'import { $$$transaction$$$ } from $path'

Length of output: 524

apps/dashboard/lib/trpc/context.ts (3)

5-6: No immediate concerns about these new imports.


11-13: Initialization of a workspace variable in the TRPC context is a good design choice.


55-55: Exposing the workspace via the returned context looks appropriate.

apps/dashboard/lib/trpc/routers/ratelimit/updateNamespaceName.ts (2)

17-24: Directly fetching the namespace by ID is more efficient than going through the workspace relationship first.


57-58: Using ctx.workspace.auditLogBucket.id for audit logs is consistent with the new workspace context approach.

apps/dashboard/lib/trpc/routers/workspace/changePlan.ts (3)

33-35: Plan-change frequency check looks good but verify monthly logic.

Please ensure this date-based comparison aligns with business rules about plan-change intervals. If you really want once-per-calendar-month logic, confirm that comparing just the year and month is sufficient.


44-45: Skipping further updates when the plan is already the same may cause corner cases.

If a user tries to resubscribe to the same plan, the conditional logic must handle it carefully, as partly done with planDowngradeRequest. Make sure all use-cases (e.g., canceled vs. active, reactivation, etc.) are covered.


56-57: Consistent usage of ctx.workspace.auditLogBucket.id helps avoid mismatched log buckets.

Also applies to: 100-101, 150-151

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

12-14: LGTM! Early workspace validation helps prevent deadlocks.

Moving the workspace validation to the auth middleware is a good approach as it ensures workspace existence is verified before any transaction begins, helping prevent potential deadlocks.


18-21: LGTM! Consistent context propagation.

Properly passing the validated workspace through the context ensures consistent access throughout the request lifecycle.

apps/dashboard/lib/trpc/routers/logs/query-log.ts (1)

11-11: LGTM! Consistent use of centralized workspace.

Correctly utilizing workspace ID from the context, benefiting from the early validation in the auth middleware.

internal/db/src/types.ts (1)

24-24: LGTM! Type safety for audit log buckets.

Adding the AuditLogBucket type ensures type safety when working with audit log transactions, reducing potential runtime errors.

apps/dashboard/lib/trpc/routers/rbac/createPermission.ts (1)

31-31: LGTM! Consistent implementation of workspace context usage.

The changes follow the same pattern of using ctx.workspace to prevent transaction deadlocks. The transaction ordering and error handling remain robust.

Also applies to: 33-34

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

19-19: LGTM! Verify the workspace filtering logic.

The changes correctly use ctx.workspace to prevent deadlocks. The implementation properly handles batch deletion with appropriate audit logging.

Let's verify the workspace filtering logic is consistent:

Also applies to: 40-40, 42-42

✅ Verification successful

Workspace filtering implementation is correct

The code properly implements dual workspace filtering:

  • Uses env().UNKEY_WORKSPACE_ID to identify internal workspace ownership
  • Uses ctx.workspace.id to filter keys for the specific user workspace
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify workspace filtering in key operations
ast-grep --pattern 'eq($_, env().UNKEY_WORKSPACE_ID)' -l typescript

Length of output: 184


Script:

#!/bin/bash
# Check for workspace filtering patterns
echo "=== Checking for workspace filtering with ctx.workspace.id ==="
ast-grep --pattern 'eq($_, ctx.workspace.id)'

echo -e "\n=== Checking for UNKEY_WORKSPACE_ID usage ==="
rg "UNKEY_WORKSPACE_ID" -A 2 -B 2

Length of output: 10360

apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts (1)

23-23: LGTM! Well-structured transaction with proper error handling.

The changes maintain the pattern of using ctx.workspace to prevent deadlocks while preserving the specific error handling for duplicate namespace entries.

Also applies to: 27-28

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

17-21: LGTM! Well-structured query with proper workspace scoping.

The key query is properly scoped to the workspace and includes all necessary conditions (workspaceId, keyId, and deletedAt check).

apps/dashboard/lib/trpc/routers/api/create.ts (2)

15-16: LGTM: Clear validation messages.

The validation messages for API names are now more specific and accurate.


24-24: Improved transaction handling by removing redundant workspace fetch.

The changes effectively address the transaction deadlock issue by:

  1. Using ctx.workspace.id directly instead of fetching workspace separately
  2. Reusing the same workspace ID throughout the transaction
  3. Correctly passing the audit log bucket ID from the context

This change reduces the likelihood of deadlocks by eliminating the race condition between workspace fetch and transaction start.

Also applies to: 43-43, 57-58

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

18-22: Well-structured changes that improve security and consistency.

The changes effectively:

  1. Add workspace validation at query level
  2. Simplify error handling
  3. Use consistent workspace ID source throughout
  4. Properly scope the transaction

This implementation aligns well with the PR's objective of reducing deadlocks while also improving security.

Also applies to: 31-37, 53-54

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

18-22: LGTM: Consistent implementation of workspace validation.

The changes follow the same secure pattern as other files, properly validating workspace ownership at the query level and simplifying error handling.

Also applies to: 31-37

apps/dashboard/lib/trpc/routers/api/updateName.ts (2)

22-26: LGTM! Enhanced security with workspace validation.

The addition of workspace ID validation in the query ensures proper authorization and prevents cross-workspace access.


57-58: Verify transaction ordering with audit logs.

The change to use ctx.workspace.auditLogBucket.id helps prevent deadlocks by avoiding nested workspace fetches. However, let's verify there are no other transaction orderings that could still cause deadlocks.

✅ Verification successful

Transaction ordering with audit logs is consistent and safe

The codebase consistently performs the main operation first, followed by audit log insertion, with pre-fetched workspace data. This pattern prevents deadlocks and maintains reliable transaction ordering.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other potential transaction patterns that might cause deadlocks
rg -A 5 "transaction.*async.*tx" | rg -B 5 "insertAuditLogs"

Length of output: 2643

apps/dashboard/lib/trpc/routers/api/updateDeleteProtection.ts (1)

21-25: LGTM! Consistent workspace validation pattern.

The workspace validation follows the same pattern as other files, ensuring proper authorization.

apps/dashboard/lib/trpc/routers/ratelimit/deleteOverride.ts (1)

17-22: LGTM! Proper workspace validation.

The workspace validation ensures proper authorization and follows the consistent pattern across the codebase.

apps/dashboard/lib/trpc/routers/api/setDefaultBytes.ts (1)

59-60: LGTM! Good use of workspace context for audit logging.

Using ctx.workspace.auditLogBucket.id from the context helps prevent transaction deadlocks by ensuring consistent bucket ID access.

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

67-68: LGTM! Consistent audit log bucket handling.

The audit log changes align with the deadlock prevention strategy by using the workspace context.

apps/dashboard/lib/trpc/routers/rbac/addPermissionToRootKey.ts (2)

65-68: LGTM! Good handling of multiple audit logs.

The spread operator correctly combines existing audit logs with the new one, maintaining the transaction atomicity.


27-27: Verify the use of forWorkspaceId vs workspaceId.

The query uses forWorkspaceId while other routers use workspaceId. Please verify this is intentional and not a potential bug.

✅ Verification successful

The use of forWorkspaceId is correct and intentional

The field distinction between workspaceId and forWorkspaceId follows an established pattern in the codebase where:

  • workspaceId indicates the workspace that owns/created a resource
  • forWorkspaceId is specifically used for keys that are created by one workspace (usually the Unkey workspace) for use by another workspace
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check schema to verify workspace ID field naming
rg -A 5 "forWorkspaceId|workspaceId" --type ts

Length of output: 71100

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

60-61: LGTM! Consistent audit log handling with proper error logging.

Good use of workspace context for audit logging. The error handling with console.error helps with debugging while maintaining user-friendly error messages.

apps/dashboard/lib/trpc/routers/key/updateExpiration.ts (2)

39-43: Security improvement: Added workspace validation in key query.

The addition of eq(table.workspaceId, ctx.workspace.id) ensures that keys can only be accessed within their own workspace context, preventing potential cross-workspace access.


74-75: Consistent with deadlock prevention: Using workspace context for audit logs.

Using ctx.workspace.auditLogBucket.id from the context aligns with the PR's goal of preventing transaction deadlocks by avoiding repeated workspace fetches.

apps/dashboard/lib/trpc/routers/rbac/createRole.ts (1)

34-34: Consistent workspace context usage across role creation flow.

The changes consistently use ctx.workspace.id and ctx.workspace.auditLogBucket.id throughout the role creation process, which:

  1. Reduces database queries by using pre-loaded workspace context
  2. Maintains consistency in audit logging
  3. Helps prevent transaction deadlocks as intended

Also applies to: 43-44, 69-69, 74-76

apps/dashboard/lib/trpc/routers/rbac/connectRoleToKey.ts (1)

79-79: LGTM! Audit logging updated correctly.

The audit log insertion has been properly updated to use the workspace's audit log bucket ID from the context, which aligns with the PR's objective of reducing transaction deadlocks.

apps/dashboard/lib/trpc/routers/ratelimit/updateOverride.ts (2)

68-69: LGTM! Audit logging updated correctly.

The audit log insertion has been properly updated to use the workspace context.


20-25: ⚠️ Potential issue

Remove duplicate workspace ID check.

The where clause contains a duplicate condition checking the workspace ID.

  where: (table, { and, eq, isNull }) =>
    and(
-     eq(table.workspaceId, ctx.workspace.id),
      eq(table.workspaceId, ctx.workspace.id),
      eq(table.id, input.id),
      isNull(table.deletedAt),
    ),

Likely invalid or redundant comment.

apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (1)

84-85: LGTM! Audit logging updated correctly.

The audit log insertion has been properly updated to use the workspace context.

apps/dashboard/lib/audit.ts (1)

96-96: LGTM! Function signature updated correctly.

The addition of the bucketId parameter aligns with the PR's objective of reducing transaction deadlocks by moving workspace and bucket fetching to the beginning of the trpc handler.

Let's verify that all callers have been updated to provide the bucketId parameter:

✅ Verification successful

All callers correctly provide the bucketId parameter

The codebase scan shows that all 65 calls to insertAuditLogs have been properly updated to include the bucketId parameter, consistently using either ctx.workspace.auditLogBucket.id or an explicit bucketId.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for all insertAuditLogs calls to ensure they provide the bucketId
rg "insertAuditLogs\(" -A 3

Length of output: 28007

apps/dashboard/lib/trpc/routers/api/delete.ts (3)

18-22: LGTM! Query changes effectively prevent race conditions.

The workspace validation is now properly handled at the query level, which helps prevent potential race conditions and reduces the risk of deadlocks.

Also applies to: 31-35


50-50: LGTM! Consistent audit log bucket usage.

Using ctx.workspace.auditLogBucket.id ensures the same bucket ID is used throughout the transaction, reducing the risk of deadlocks.


82-84: LGTM! Consistent workspace context usage.

Using ctx.workspace for both bucket ID and workspaceId maintains consistency and atomicity throughout the transaction.

apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (1)

69-69: LGTM! Consistent workspace context usage in transaction.

The changes maintain consistency with the workspace context throughout the transaction, preventing potential deadlocks.

Also applies to: 78-79

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

77-77: LGTM! Consistent workspace context usage.

The changes maintain consistency with the workspace context throughout the key creation process.

Also applies to: 93-94

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

64-65: LGTM! Consistent workspace context usage for enabling rate limits.

The changes maintain consistency with the workspace context when enabling rate limits.


22-26: Consider validating workspaceId matches context.

The input schema includes workspaceId, but it's not validated against ctx.workspace.id. Consider either removing it from the input if unused or validating it matches the context.

Also applies to: 35-41

apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx (1)

32-36: Add validation for audit log bucket existence.

The code assumes that auditLogBuckets[0] exists without validation. This could lead to runtime errors if no audit log bucket is found.

Add a check before using the bucket ID:

 with: {
   auditLogBuckets: {
     where: (table, { eq }) => eq(table.name, "unkey_mutations"),
   },
 },
+if (!ws?.auditLogBuckets?.[0]) {
+  throw new Error("Audit log bucket not found");
+}
apps/dashboard/lib/trpc/routers/logs/llm-search.ts (2)

84-86: LGTM! Simplified mutation logic.

The workspace validation has been moved to middleware, making the code more concise and maintainable.


112-117: LGTM! Improved formatting.

The example output formatting has been improved for better readability.

apps/dashboard/lib/trpc/routers/audit/fetch.ts (1)

41-41: LGTM! Using workspace from context.

The change aligns with the PR objective of moving workspace fetch to the beginning of the trpc handler, which helps prevent transaction deadlocks.

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

27-28: LGTM! Consistent usage of workspace from context.

The changes consistently use ctx.workspace.id throughout the file, which aligns with the PR objective of moving workspace fetch to the beginning of the trpc handler.

Also applies to: 68-68, 80-80, 110-110, 114-114, 141-141

apps/dashboard/app/new/page.tsx (2)

213-221: LGTM: Audit log bucket creation looks good.

The implementation correctly creates a new audit log bucket with appropriate properties:

  • Unique ID generation using newId
  • Proper workspace association
  • Sensible defaults for retention (30 days) and delete protection

223-223: Verify audit log bucket ID usage.

The audit log bucket ID is now required as a parameter for insertAuditLogs.

Run the following script to verify consistent usage of the bucket ID parameter:

✅ Verification successful

✓ Audit log bucket ID usage is consistent

All calls to insertAuditLogs correctly include the bucket ID as the second parameter, following the required pattern.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that all calls to insertAuditLogs include a bucket ID parameter.
# Test: Search for insertAuditLogs calls. Expect: All calls should have 3 parameters.

rg -p "insertAuditLogs\((.*?)\)" --multiline

Length of output: 305

apps/dashboard/lib/trpc/routers/vercel.ts (1)

76-78: LGTM: Consistent audit log bucket usage across all mutations.

The changes correctly use ctx.workspace.auditLogBucket.id in all audit log insertions, maintaining consistency across different operations:

  • Key creation
  • Vercel binding operations
  • Permission updates

Also applies to: 126-126, 177-177, 257-257, 297-297, 377-377, 422-422, 463-463, 530-530, 587-587

apps/dashboard/lib/trpc/routers/rbac.ts (1)

78-78: LGTM: Consistent audit log bucket usage in RBAC operations.

The changes correctly use ctx.workspace.auditLogBucket.id in all RBAC-related audit log insertions:

  • Role operations (create, update, delete)
  • Permission operations (create, update, delete)
  • Role-Permission connections
  • Role-Key connections

Also applies to: 138-138, 213-213, 263-263, 338-338, 416-416, 447-447, 507-507, 559-559, 603-603, 667-667, 727-727

apps/dashboard/lib/trpc/routers/logs/query-timeseries.ts (1)

19-19: LGTM: Simplified workspace access.

The change correctly uses ctx.workspace.id from the context instead of performing a separate database query, which:

  • Reduces database load
  • Prevents potential transaction deadlocks
  • Aligns with the PR's objective
apps/dashboard/lib/trpc/routers/api/setDefaultPrefix.ts (4)

20-24: LGTM! Well-structured query conditions.

The query conditions are comprehensive and properly validate workspace ownership while checking for soft-deleted records.


Line range hint 33-39: LGTM! Clear and concise error handling.

The error handling is appropriately simplified since workspace ownership is already validated in the query conditions.


Line range hint 56-77: LGTM! Comprehensive audit logging with accurate description.

The audit logging implementation is thorough and follows best practices:

  • Uses the workspace's audit log bucket from context
  • Includes detailed metadata
  • Accurately describes the prefix change with before/after values

56-57: Consider caching the workspace lookup.

As mentioned in the PR objectives, there might be potential for caching the workspace lookup. This could further reduce database load and potential deadlocks.

Let's check if Next.js is already handling the caching:

apps/dashboard/lib/trpc/routers/rbac/deletePermission.ts Outdated Show resolved Hide resolved
apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts Outdated Show resolved Hide resolved
apps/dashboard/lib/trpc/routers/rbac/deleteRole.ts Outdated Show resolved Hide resolved
apps/dashboard/app/new/create-ratelimit.tsx Outdated Show resolved Hide resolved
apps/dashboard/lib/trpc/routers/workspace/create.ts Outdated Show resolved Hide resolved
apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts Outdated Show resolved Hide resolved
@chronark chronark enabled auto-merge January 29, 2025 07:58
@chronark chronark added this pull request to the merge queue Jan 29, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jan 29, 2025
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu left a comment

Choose a reason for hiding this comment

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

lgtm

@chronark chronark added this pull request to the merge queue Jan 30, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jan 30, 2025
@chronark chronark closed this Jan 30, 2025
@chronark chronark deleted the trpc-load-workspace branch January 30, 2025 13:36
@chronark chronark restored the trpc-load-workspace branch February 7, 2025 11:09
@chronark chronark reopened this Feb 7, 2025
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