Skip to content

feat: add identity-based credits schema and database changes#4190

Closed
Flo4604 wants to merge 4 commits intomainfrom
feat/identity-credits-schema
Closed

feat: add identity-based credits schema and database changes#4190
Flo4604 wants to merge 4 commits intomainfrom
feat/identity-credits-schema

Conversation

@Flo4604
Copy link
Member

@Flo4604 Flo4604 commented Oct 28, 2025

What does this PR do?

This adds a new table where our identity/key credits are stored.

You either have credits on an identity or a key but NOT NEVER EVER both.
This als refactors the findIdentity function to return a single row and not 2.

Re-generated the non formatted ts files

DO NOT MERGE THIS

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?

Check if you like the schema, check if the tests still pass

Checklist

Required

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

Appreciated

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

@changeset-bot
Copy link

changeset-bot bot commented Oct 28, 2025

⚠️ No Changeset found

Latest commit: 8277f35

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 Oct 28, 2025

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

Project Deployment Preview Comments Updated (UTC)
dashboard Ready Ready Preview Comment Nov 4, 2025 5:15pm
engineering Ready Ready Preview Comment Nov 4, 2025 5:15pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

📝 Walkthrough

Walkthrough

This PR introduces a credits system into the database schema and refactors identity lookup to replace FindIdentityWithRatelimits (multi-row) with FindIdentity (single-row) that includes credit data. It adds comprehensive CRUD operations for credits, extends key and identity queries to join credit information, and updates three API handlers to use the new unified identity lookup with explicit not-found handling.

Changes

Cohort / File(s) Summary
Identity API Handler Refactoring
go/apps/api/routes/v2_identities_delete_identity/handler.go, go/apps/api/routes/v2_identities_get_identity/handler.go, go/apps/api/routes/v2_identities_update_identity/handler.go
Replaced FindIdentityWithRatelimits calls (returning slice) with FindIdentity (returning single row); added explicit db.IsNotFound error handling; removed results-slice indexing; updated transaction result types to use FindIdentityRow instead of FindIdentityWithRatelimitsRow.
Key Lookup & Data Extensions
go/internal/services/keys/verifier.go, go/pkg/db/key_data.go
Added IdentityCredits and KeyCredits fields to track identity-level and key-level credits; extended buildKeyData to populate credit fields from query results.
Credit Table Schema
go/pkg/db/schema.sql, internal/db/src/schema/credits.ts
Introduced new credits table with columns for id, workspace_id, key_id, identity_id, remaining, refill_day, refill_amount, refilled_at, created_at, updated_at; added primary key, unique constraints per key and identity, and indexes on workspace_id and refill lookup.
Credit CRUD Operations (Generated)
go/pkg/db/credits_insert.sql_generated.go, go/pkg/db/credits_delete.sql_generated.go, go/pkg/db/credits_update_*.sql_generated.go, go/pkg/db/credits_find_*.sql_generated.go, go/pkg/db/bulk_credits_*.sql_generated.go
Added methods for insert, delete, find-by-identity-id, find-by-key-id, find-remaining, update-set, update-increment, update-decrement, bulk-insert, and bulk-upsert operations; includes corresponding parameter and result row types.
Identity Lookup Query Updates
go/pkg/db/identity_find.sql_generated.go
New query FindIdentity returns single-row FindIdentityRow with identity, ratelimits (JSON array), and credit fields (credit_id, credit_remaining, credit_refill_amount, credit_refill_day, credit_refilled_at); includes not-found handling.
Removed Identity Query
go/pkg/db/identity_find_with_ratelimits.sql_generated.go
Deleted entire file; removed FindIdentityWithRatelimits method, FindIdentityWithRatelimitsParams, and FindIdentityWithRatelimitsRow types that returned multi-row results.
Key & Identity Query Extensions
go/pkg/db/key_find_for_verification.sql_generated.go, go/pkg/db/key_find_live_by_hash.sql_generated.go, go/pkg/db/key_find_live_by_id.sql_generated.go, go/pkg/db/identity_list.sql_generated.go, go/pkg/db/keys_find_without_credits.sql_generated.go
Extended queries to join credits and expose key-level and identity-level credit fields (credit_id, credit_remaining, refill fields); updated result row structs with new credit columns; ListIdentities now returns ListIdentitiesRow instead of Identity.
Identity Deletion Query Updates
go/pkg/db/identity_delete_old_by_external_id.sql_generated.go, go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go
Updated DELETE statements to target credits table in addition to identities and ratelimits; added LEFT JOIN to credits with appropriate cascade conditions.
DB Model & Interface Generation
go/pkg/db/models_generated.go, go/pkg/db/querier_generated.go, go/pkg/db/querier_bulk_generated.go
Added Credit struct type; exposed new methods in Querier and BulkQuerier interfaces for credit operations (insert, delete, find, update-set/increment/decrement, upsert, bulk-insert/upsert) and FindIdentity; updated ListIdentities signature.
SQL Query Definitions
go/pkg/db/queries/credits_*.sql, go/pkg/db/queries/identity_delete_*.sql, go/pkg/db/queries/identity_find.sql, go/pkg/db/queries/identity_list.sql, go/pkg/db/queries/key_find_*.sql, go/pkg/db/queries/keys_find_without_credits.sql
Added new SQL query files for credits CRUD; updated existing queries to join credits and expose credit columns; removed identity_find_with_ratelimits.sql.
UID Generator & Metrics
go/pkg/uid/uid.go, internal/metrics/src/index.ts
Added CreditPrefix = "cr" constant for ID generation; updated metric.usagelimit schema to make keyId optional and add optional creditId field.
TypeScript Schema & Types
internal/db/src/schema/index.ts, internal/db/src/schema/keys.ts, internal/db/src/types.ts, internal/id/src/generate.ts
Added credits table schema export; imported credits in keys schema; added deprecated refill fields (refillDay, refillAmount, lastRefillAt) and credits relation to keys; exported Credits and InsertCredits types; added "credit" prefix to ID generator.

Sequence Diagram

sequenceDiagram
    participant Handler as API Handler
    participant DB as Database
    participant Result as Result Processing
    
    rect rgb(200, 240, 255)
    Note over Handler,Result: Old Flow (FindIdentityWithRatelimits)
    Handler->>DB: FindIdentityWithRatelimits()
    DB-->>Handler: []FindIdentityWithRatelimitsRow (slice)
    Handler->>Result: Check len(results) == 0
    alt Empty Results
        Result-->>Handler: NotFound Error
    else Results Present
        Result->>Result: results[0]
        Result-->>Handler: Identity + Ratelimits
    end
    end
    
    rect rgb(240, 200, 255)
    Note over Handler,Result: New Flow (FindIdentity)
    Handler->>DB: FindIdentity()
    DB-->>Handler: FindIdentityRow (single)
    Handler->>Result: Check db.IsNotFound(err)
    alt Not Found
        Result-->>Handler: NotFound Fault
    else Found
        Result->>Result: Process row
        Result-->>Handler: Identity + Ratelimits + Credits
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Handler refactoring (3 files): Each handler requires careful verification of not-found handling logic and result type changes, though the pattern is consistent across all three.
  • Generated DB files (25+ files): While largely homogeneous (templated code generation), the sheer volume and interconnected nature of credit operations across insert/delete/update/find/bulk operations demand attention to correctness of parameter binding and result scanning.
  • Query updates with joins: Extended queries across key/identity lookups require validation that join conditions and NULL handling for nullable credit fields are correct.
  • SQL schema introduction: The credits table definition, indexes, and constraints must align with deletion cascades and uniqueness assumptions used throughout the codebase.
  • Cross-module coordination: Changes span Go handlers, generated DB code, SQL schemas, TypeScript schema, and metrics—requiring verification that all layers properly wire new credit fields end-to-end.

Areas requiring extra attention:

  • Verify that the FindIdentity query correctly handles identities with no associated credits (LEFT JOIN nullability).
  • Confirm all three identity handlers properly use the new FindIdentityRow type and that transaction result aggregation is correct in update-identity.
  • Validate that identity deletion properly cascades to credits via the updated DELETE statements.
  • Check that the db.IsNotFound error wrapping is consistent across all three handlers and matches existing error handling patterns.
  • Review key/identity list queries for correctness of column aliasing and scan order when joining credits.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete and does not follow the template structure. Many required checklist items are unchecked, and critical sections like 'Filled out the How to test section' lack detail. The description explicitly states 'DO NOT MERGE THIS', indicating it is not ready for merging. Complete all required checklist items, provide detailed testing instructions, and remove the 'DO NOT MERGE THIS' note only when the PR is ready for review. Ensure all template sections are properly filled out before requesting review.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding identity-based credits schema and database changes, which aligns with the core objective of introducing a new credits table.
✨ 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 feat/identity-credits-schema

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7017b5a and 8277f35.

📒 Files selected for processing (29)
  • go/apps/api/routes/v2_identities_delete_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/handler.go (1 hunks)
  • go/internal/services/keys/verifier.go (1 hunks)
  • go/pkg/db/bulk_credits_insert.sql_generated.go (1 hunks)
  • go/pkg/db/bulk_credits_upsert.sql_generated.go (1 hunks)
  • go/pkg/db/credits_delete.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_by_key_id.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_remaining.sql_generated.go (1 hunks)
  • go/pkg/db/credits_insert.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_decrement.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_increment.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_set.sql_generated.go (1 hunks)
  • go/pkg/db/credits_upsert.sql_generated.go (1 hunks)
  • go/pkg/db/identity_delete_old_by_external_id.sql_generated.go (2 hunks)
  • go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (2 hunks)
  • go/pkg/db/identity_find.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find_with_ratelimits.sql_generated.go (0 hunks)
  • go/pkg/db/identity_list.sql_generated.go (4 hunks)
  • go/pkg/db/key_data.go (2 hunks)
  • go/pkg/db/key_find_for_verification.sql_generated.go (3 hunks)
  • go/pkg/db/key_find_live_by_hash.sql_generated.go (4 hunks)
  • go/pkg/db/key_find_live_by_id.sql_generated.go (4 hunks)
  • go/pkg/db/key_find_remaining.sql_generated.go (1 hunks)
  • go/pkg/db/keys_find_without_credits.sql_generated.go (1 hunks)
  • go/pkg/db/models_generated.go (1 hunks)
  • go/pkg/db/querier_bulk_generated.go (1 hunks)
  • go/pkg/db/querier_generated.go (14 hunks)
💤 Files with no reviewable changes (1)
  • go/pkg/db/identity_find_with_ratelimits.sql_generated.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • go/pkg/db/credits_delete.sql_generated.go
  • go/pkg/db/key_data.go
  • go/internal/services/keys/verifier.go
  • go/pkg/db/credits_find_by_key_id.sql_generated.go
  • go/pkg/db/credits_find_remaining.sql_generated.go
  • go/pkg/db/identity_find.sql_generated.go
  • go/pkg/db/identity_delete_old_by_external_id.sql_generated.go
🧰 Additional context used
🧠 Learnings (8)
📓 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.
📚 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:

  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/apps/api/routes/v2_identities_update_identity/handler.go
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/models_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go
  • go/pkg/db/querier_bulk_generated.go
  • go/pkg/db/identity_list.sql_generated.go
  • go/pkg/db/querier_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
📚 Learning: 2025-07-17T14:24:20.403Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.403Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.

Applied to files:

  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/querier_bulk_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
📚 Learning: 2025-09-11T14:24:40.988Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3944
File: go/apps/ctrl/services/deployment/deploy_workflow.go:326-334
Timestamp: 2025-09-11T14:24:40.988Z
Learning: The InsertDomains method in the bulk queries uses ON DUPLICATE KEY UPDATE, making it an upsert operation that is idempotent and safe for retries, despite the "Insert" naming convention.

Applied to files:

  • go/pkg/db/bulk_credits_upsert.sql_generated.go
📚 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:

  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/apps/api/routes/v2_identities_update_identity/handler.go
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/querier_bulk_generated.go
  • go/pkg/db/querier_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
📚 Learning: 2025-09-12T08:01:20.792Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3944
File: go/pkg/db/acme_challenge_update_verified_with_expiry.sql_generated.go:31-39
Timestamp: 2025-09-12T08:01:20.792Z
Learning: Do not review or suggest changes to files with sql_generated.go suffix or other files marked as auto-generated (containing "Code generated by" comments), as these are generated by tools like sqlc and changes would be overwritten on regeneration.

Applied to files:

  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go
📚 Learning: 2025-08-27T13:48:54.016Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3841
File: go/pkg/db/queries/key_find_for_verification.sql:15-15
Timestamp: 2025-08-27T13:48:54.016Z
Learning: The pending_migration_id field in FindKeyForVerification is only used internally by get_migrated.go for migration logic and doesn't need to be exposed in KeyVerifier struct or API response DTOs since it's an internal implementation detail.

Applied to files:

  • go/pkg/db/key_find_for_verification.sql_generated.go
📚 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:

  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/querier_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
🧬 Code graph analysis (11)
go/pkg/db/bulk_credits_upsert.sql_generated.go (1)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (46-61)
go/apps/api/routes/v2_identities_delete_identity/handler.go (3)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (52-56)
go/pkg/db/models_generated.go (1)
  • Identity (656-665)
go/pkg/codes/unkey_data.go (1)
  • Data (112-165)
go/apps/api/routes/v2_identities_update_identity/handler.go (4)
go/pkg/db/identity_find.sql_generated.go (2)
  • FindIdentityParams (52-56)
  • FindIdentityRow (58-73)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/codes/unkey_data.go (1)
  • Data (112-165)
go/pkg/db/custom_types.go (1)
  • RatelimitInfo (23-31)
go/apps/api/routes/v2_identities_get_identity/handler.go (3)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (52-56)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/codes/unkey_data.go (1)
  • Data (112-165)
go/pkg/db/credits_upsert.sql_generated.go (1)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
go/pkg/db/bulk_credits_insert.sql_generated.go (1)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/credits_find_by_identity_id.sql_generated.go (2)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
go/pkg/db/models_generated.go (1)
  • Credit (583-594)
go/pkg/db/querier_bulk_generated.go (3)
internal/db/src/types.ts (1)
  • InsertCredits (59-59)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (46-61)
go/pkg/db/identity_list.sql_generated.go (1)
go/pkg/db/models_generated.go (1)
  • Environment (645-654)
go/pkg/db/querier_generated.go (12)
go/pkg/db/identity_delete_old_by_external_id.sql_generated.go (1)
  • DeleteOldIdentityByExternalIDParams (23-27)
go/pkg/db/models_generated.go (2)
  • Credit (583-594)
  • Identity (656-665)
go/pkg/db/identity_find.sql_generated.go (2)
  • FindIdentityParams (52-56)
  • FindIdentityRow (58-73)
go/pkg/db/identity_find_by_external_id.sql_generated.go (1)
  • FindIdentityByExternalIDParams (20-24)
go/pkg/db/identity_find_by_id.sql_generated.go (1)
  • FindIdentityByIDParams (20-24)
go/pkg/db/keys_find_without_credits.sql_generated.go (2)
  • FindKeysWithoutCreditsParams (36-39)
  • FindKeysWithoutCreditsRow (41-50)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/identity_list.sql_generated.go (2)
  • ListIdentitiesParams (29-34)
  • ListIdentitiesRow (36-49)
go/pkg/db/credits_update_decrement.sql_generated.go (1)
  • UpdateCreditDecrementParams (23-27)
go/pkg/db/credits_update_increment.sql_generated.go (1)
  • UpdateCreditIncrementParams (20-24)
go/pkg/db/credits_update_set.sql_generated.go (1)
  • UpdateCreditSetParams (20-24)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (46-61)
go/pkg/db/key_find_live_by_id.sql_generated.go (1)
go/pkg/db/models_generated.go (5)
  • Environment (645-654)
  • Api (514-525)
  • KeyAuth (693-704)
  • Workspace (842-860)
  • EncryptedKey (636-643)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Packages / Test
  • GitHub Check: Test API / API Test Local
🔇 Additional comments (7)
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)

74-91: LGTM! Clean refactoring to single-row identity lookup.

The migration from FindIdentityWithRatelimits (multi-row) to FindIdentity (single-row) is implemented correctly, with explicit not-found handling via db.IsNotFound properly positioned before generic error handling.

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

54-72: LGTM! Clean refactor to single-row identity retrieval.

The replacement of FindIdentityWithRatelimits with FindIdentity simplifies the query surface by returning a single row instead of a results slice. The explicit not-found handling at lines 60-66 is clear and properly uses the standard error codes.

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

116-134: LGTM! Consistent refactor to single-row identity retrieval.

The change from FindIdentityWithRatelimits to FindIdentity aligns with the broader schema refactoring. The explicit not-found handling at lines 122-128 is appropriate, and all downstream references to identityRow are correctly updated throughout the transaction logic.

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

1-285: Auto-generated file acknowledged

This file is produced by sqlc; per our guidelines we skip manual review to avoid churn on regenerated artifacts.

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

1-360: Auto-generated file acknowledged

Generated by sqlc, so no manual review feedback is provided to keep automated outputs in sync with their source queries.

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

1-104: Auto-generated file acknowledged

As this is sqlc-generated code, we’re skipping manual review and letting the generator remain the single source of truth.

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

1-2134: Auto-generated file acknowledged

This large querier file comes from sqlc generation; following guidance, we’re not issuing manual review comments on it.


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

❤️ Share

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

@vercel vercel bot temporarily deployed to Preview – engineering October 28, 2025 14:13 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard October 28, 2025 14:15 Inactive
@Flo4604 Flo4604 force-pushed the feat/identity-credits-schema branch from b943287 to ae7385a Compare October 29, 2025 10:21
@vercel vercel bot temporarily deployed to Preview – engineering October 29, 2025 10:22 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard October 29, 2025 10:22 Inactive
@Flo4604 Flo4604 force-pushed the feat/identity-credits-schema branch from ae7385a to 3715c57 Compare October 29, 2025 18:01
@vercel vercel bot temporarily deployed to Preview – engineering October 29, 2025 18:02 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard October 29, 2025 18:04 Inactive
@Flo4604 Flo4604 mentioned this pull request Oct 30, 2025
18 tasks
@Flo4604 Flo4604 marked this pull request as ready for review October 30, 2025 14:26
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: 4

🧹 Nitpick comments (2)
go/pkg/db/queries/credits_update_increment.sql (1)

1-5: Verify parameter syntax consistency.

The query mixes sqlc.arg('credits') for the increment value with ? placeholders for updated_at and id. While sqlc supports both syntaxes, consider using a consistent approach across all parameters for better readability.

Additionally, the increment operation doesn't guard against overflow or validate bounds. Depending on your requirements, you might want to add constraints at the database level or validation at the application level.

Optional: Standardize to named parameters

 -- name: UpdateCreditIncrement :exec
 UPDATE `credits`
 SET remaining = remaining + sqlc.arg('credits'),
-    updated_at = ?
-WHERE id = ?;
+    updated_at = sqlc.arg('updated_at')
+WHERE id = sqlc.arg('id');
go/pkg/db/queries/credits_update_set.sql (1)

1-5: Consider consistent parameter syntax.

The query mixes parameter styles: sqlc.arg('remaining') for the remaining field but positional placeholders (?) for updated_at and id. While this may be valid sqlc syntax, using consistent parameter naming (e.g., sqlc.arg('updated_at') and sqlc.arg('id')) could improve readability and maintainability.

 -- name: UpdateCreditSet :exec
 UPDATE `credits`
 SET remaining = sqlc.arg('remaining'),
-    updated_at = ?
-WHERE id = ?;
+    updated_at = sqlc.arg('updated_at')
+WHERE id = sqlc.arg('id');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3389321 and 3715c57.

📒 Files selected for processing (57)
  • go/apps/api/routes/v2_identities_delete_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_verify_key/200_test.go (2 hunks)
  • go/internal/services/keys/verifier.go (1 hunks)
  • go/pkg/db/bulk_credits_insert.sql_generated.go (1 hunks)
  • go/pkg/db/bulk_credits_upsert.sql_generated.go (1 hunks)
  • go/pkg/db/credits_delete.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_by_key_id.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_remaining.sql_generated.go (1 hunks)
  • go/pkg/db/credits_insert.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_decrement.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_increment.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_set.sql_generated.go (1 hunks)
  • go/pkg/db/credits_upsert.sql_generated.go (1 hunks)
  • go/pkg/db/identity_delete_old_by_external_id.sql_generated.go (2 hunks)
  • go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (2 hunks)
  • go/pkg/db/identity_find.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find_with_ratelimits.sql_generated.go (0 hunks)
  • go/pkg/db/identity_list.sql_generated.go (4 hunks)
  • go/pkg/db/key_data.go (2 hunks)
  • go/pkg/db/key_find_for_verification.sql_generated.go (3 hunks)
  • go/pkg/db/key_find_live_by_hash.sql_generated.go (4 hunks)
  • go/pkg/db/key_find_live_by_id.sql_generated.go (4 hunks)
  • go/pkg/db/key_find_remaining.sql_generated.go (1 hunks)
  • go/pkg/db/keys_find_without_credits.sql_generated.go (1 hunks)
  • go/pkg/db/models_generated.go (1 hunks)
  • go/pkg/db/querier_bulk_generated.go (1 hunks)
  • go/pkg/db/querier_generated.go (14 hunks)
  • go/pkg/db/queries/credits_delete.sql (1 hunks)
  • go/pkg/db/queries/credits_find_by_identity_id.sql (1 hunks)
  • go/pkg/db/queries/credits_find_by_key_id.sql (1 hunks)
  • go/pkg/db/queries/credits_find_remaining.sql (1 hunks)
  • go/pkg/db/queries/credits_insert.sql (1 hunks)
  • go/pkg/db/queries/credits_update_decrement.sql (1 hunks)
  • go/pkg/db/queries/credits_update_increment.sql (1 hunks)
  • go/pkg/db/queries/credits_update_set.sql (1 hunks)
  • go/pkg/db/queries/credits_upsert.sql (1 hunks)
  • go/pkg/db/queries/identity_delete_old_by_external_id.sql (1 hunks)
  • go/pkg/db/queries/identity_delete_old_with_ratelimits.sql (1 hunks)
  • go/pkg/db/queries/identity_find.sql (1 hunks)
  • go/pkg/db/queries/identity_find_with_ratelimits.sql (0 hunks)
  • go/pkg/db/queries/identity_list.sql (1 hunks)
  • go/pkg/db/queries/key_find_for_verification.sql (1 hunks)
  • go/pkg/db/queries/key_find_live_by_hash.sql (1 hunks)
  • go/pkg/db/queries/key_find_live_by_id.sql (1 hunks)
  • go/pkg/db/queries/key_find_remaining.sql (1 hunks)
  • go/pkg/db/queries/keys_find_without_credits.sql (1 hunks)
  • go/pkg/db/schema.sql (2 hunks)
  • go/pkg/uid/uid.go (1 hunks)
  • internal/db/src/schema/credits.ts (1 hunks)
  • internal/db/src/schema/index.ts (1 hunks)
  • internal/db/src/schema/keys.ts (4 hunks)
  • internal/db/src/types.ts (1 hunks)
  • internal/id/src/generate.ts (1 hunks)
  • internal/metrics/src/index.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • go/pkg/db/identity_find_with_ratelimits.sql_generated.go
  • go/pkg/db/queries/identity_find_with_ratelimits.sql
🧰 Additional context used
🧠 Learnings (16)
📚 Learning: 2025-08-27T13:48:54.016Z
Learnt from: Flo4604
PR: unkeyed/unkey#3841
File: go/pkg/db/queries/key_find_for_verification.sql:15-15
Timestamp: 2025-08-27T13:48:54.016Z
Learning: The pending_migration_id field in FindKeyForVerification is only used internally by get_migrated.go for migration logic and doesn't need to be exposed in KeyVerifier struct or API response DTOs since it's an internal implementation detail.

Applied to files:

  • go/internal/services/keys/verifier.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
📚 Learning: 2025-08-21T15:54:45.198Z
Learnt from: chronark
PR: unkeyed/unkey#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:

  • go/internal/services/keys/verifier.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/queries/credits_update_decrement.sql
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/queries/credits_upsert.sql
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/credits_find_by_key_id.sql_generated.go
  • go/pkg/db/queries/credits_update_increment.sql
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/querier_generated.go
📚 Learning: 2025-07-17T14:24:20.403Z
Learnt from: Flo4604
PR: unkeyed/unkey#3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.403Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.

Applied to files:

  • go/pkg/db/querier_bulk_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/queries/credits_insert.sql
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/queries/credits_upsert.sql
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/queries/credits_find_by_key_id.sql
  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/queries/keys_find_without_credits.sql
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
  • go/pkg/db/queries/key_find_remaining.sql
📚 Learning: 2025-09-12T08:01:20.792Z
Learnt from: Flo4604
PR: unkeyed/unkey#3944
File: go/pkg/db/acme_challenge_update_verified_with_expiry.sql_generated.go:31-39
Timestamp: 2025-09-12T08:01:20.792Z
Learning: Do not review or suggest changes to files with sql_generated.go suffix or other files marked as auto-generated (containing "Code generated by" comments), as these are generated by tools like sqlc and changes would be overwritten on regeneration.

Applied to files:

  • go/pkg/db/credits_find_remaining.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/credits_delete.sql_generated.go
  • go/pkg/db/queries/credits_insert.sql
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/queries/credits_delete.sql
  • go/pkg/db/identity_find.sql_generated.go
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go
  • go/pkg/db/queries/credits_update_set.sql
  • go/pkg/db/credits_update_set.sql_generated.go
📚 Learning: 2024-10-20T07:05:55.471Z
Learnt from: chronark
PR: unkeyed/unkey#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:

  • go/pkg/db/queries/key_find_live_by_id.sql
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/queries/key_find_live_by_hash.sql
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/querier_generated.go
  • internal/db/src/schema/keys.ts
📚 Learning: 2025-08-18T08:41:37.689Z
Learnt from: Flo4604
PR: unkeyed/unkey#3804
File: go/pkg/db/key_update_credits_decrement.sql_generated.go:13-20
Timestamp: 2025-08-18T08:41:37.689Z
Learning: In MySQL queries for decrementing key credits, using GREATEST(remaining_requests - ?, 0) is slower than using a CASE expression with duplicate placeholders (CASE WHEN remaining_requests - ? > 0 THEN remaining_requests - ? ELSE 0 END), even though GREATEST reduces parameter count.

Applied to files:

  • go/pkg/db/queries/credits_update_decrement.sql
  • go/pkg/db/queries/credits_find_remaining.sql
  • go/pkg/db/queries/keys_find_without_credits.sql
📚 Learning: 2025-09-11T14:24:40.988Z
Learnt from: Flo4604
PR: unkeyed/unkey#3944
File: go/apps/ctrl/services/deployment/deploy_workflow.go:326-334
Timestamp: 2025-09-11T14:24:40.988Z
Learning: The InsertDomains method in the bulk queries uses ON DUPLICATE KEY UPDATE, making it an upsert operation that is idempotent and safe for retries, despite the "Insert" naming convention.

Applied to files:

  • go/pkg/db/bulk_credits_upsert.sql_generated.go
📚 Learning: 2025-08-14T16:25:48.167Z
Learnt from: Flo4604
PR: unkeyed/unkey#3785
File: go/apps/api/routes/v2_keys_reroll_key/401_test.go:52-61
Timestamp: 2025-08-14T16:25:48.167Z
Learning: User Flo4604 requested creation of a GitHub issue to track converting all test files to use table-driven test patterns as a broader codebase improvement, following the suggestion made during review of go/apps/api/routes/v2_keys_reroll_key/401_test.go.

Applied to files:

  • go/apps/api/routes/v2_keys_verify_key/200_test.go
📚 Learning: 2025-09-01T08:29:10.199Z
Learnt from: Flo4604
PR: unkeyed/unkey#3895
File: go/pkg/db/key_list_live_by_auth_id.sql_generated.go:98-105
Timestamp: 2025-09-01T08:29:10.199Z
Learning: In Unkey's ListLiveKeysByKeyAuthID query, adding ka.workspace_id = k.workspace_id constraint negatively impacts index performance. Workspace validation is handled upstream at the API level before the query is called, making the additional constraint unnecessary.

Applied to files:

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

Applied to files:

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

Applied to files:

  • internal/db/src/schema/keys.ts
📚 Learning: 2025-08-21T12:37:40.996Z
Learnt from: Flo4604
PR: unkeyed/unkey#3821
File: apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts:74-74
Timestamp: 2025-08-21T12:37:40.996Z
Learning: Root keys in Unkey have two workspace fields: `workspaceId` (always set to env().UNKEY_WORKSPACE_ID for the Unkey workspace that owns the key) and `forWorkspaceId` (set to ctx.workspace.id for the user's workspace that the key is for). When querying root keys, the system filters by forWorkspaceId to get keys for the current user's workspace, but the returned rootKey.workspaceId is always the Unkey workspace ID.

Applied to files:

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

Applied to files:

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

Applied to files:

  • internal/db/src/schema/keys.ts
📚 Learning: 2025-08-25T17:40:51.836Z
Learnt from: mcstepp
PR: unkeyed/unkey#3830
File: go/pkg/db/project_find_by_workspace_slug.sql_generated.go:0-0
Timestamp: 2025-08-25T17:40:51.836Z
Learning: In database schema analysis: UNIQUE constraints automatically create composite indexes in most SQL databases (PostgreSQL, MySQL, etc.), so when analyzing query performance, check for both explicit CREATE INDEX statements and UNIQUE constraints that cover the query's filter columns.

Applied to files:

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

Applied to files:

  • go/pkg/db/queries/identity_delete_old_by_external_id.sql
  • go/pkg/db/identity_delete_old_by_external_id.sql_generated.go
🧬 Code graph analysis (19)
go/internal/services/keys/verifier.go (1)
go/pkg/db/models_generated.go (1)
  • Credit (569-580)
go/pkg/db/querier_bulk_generated.go (3)
internal/db/src/types.ts (1)
  • InsertCredits (59-59)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (42-56)
go/pkg/db/key_data.go (1)
go/pkg/db/models_generated.go (1)
  • Credit (569-580)
go/pkg/db/credits_update_decrement.sql_generated.go (1)
internal/db/src/types.ts (1)
  • Credits (58-58)
go/pkg/db/bulk_credits_insert.sql_generated.go (1)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
internal/db/src/schema/credits.ts (3)
internal/db/src/schema/workspaces.ts (1)
  • workspaces (16-104)
internal/db/src/schema/keys.ts (1)
  • keys (22-116)
internal/db/src/schema/identity.ts (1)
  • identities (16-38)
go/apps/api/routes/v2_identities_delete_identity/handler.go (4)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (52-56)
go/pkg/db/models_generated.go (1)
  • Identity (642-651)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/apps/api/routes/v2_identities_update_identity/handler.go (4)
go/pkg/db/identity_find.sql_generated.go (2)
  • FindIdentityParams (52-56)
  • FindIdentityRow (58-73)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/db/custom_types.go (1)
  • RatelimitInfo (23-31)
internal/db/src/types.ts (1)
internal/db/src/index.ts (1)
  • schema (8-8)
go/pkg/db/key_find_live_by_hash.sql_generated.go (1)
go/pkg/db/models_generated.go (5)
  • Environment (631-640)
  • Api (514-525)
  • KeyAuth (679-690)
  • Workspace (828-846)
  • EncryptedKey (622-629)
go/pkg/db/identity_find.sql_generated.go (3)
go/pkg/db/models_generated.go (2)
  • Identity (642-651)
  • Environment (631-640)
internal/db/src/types.ts (1)
  • Identity (46-46)
go/apps/api/openapi/gen.go (2)
  • Identity (161-173)
  • Meta (279-282)
go/pkg/db/models_generated.go (1)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
go/apps/api/routes/v2_identities_get_identity/handler.go (5)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (52-56)
go/pkg/db/models_generated.go (1)
  • Identity (642-651)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/codes/unkey_data.go (1)
  • Data (95-139)
go/pkg/db/bulk_credits_upsert.sql_generated.go (1)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (42-56)
go/pkg/db/credits_find_by_key_id.sql_generated.go (2)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
go/pkg/db/models_generated.go (1)
  • Credit (569-580)
go/pkg/db/credits_find_by_identity_id.sql_generated.go (2)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
go/pkg/db/models_generated.go (1)
  • Credit (569-580)
go/pkg/db/identity_list.sql_generated.go (1)
go/pkg/db/models_generated.go (1)
  • Environment (631-640)
go/pkg/db/querier_generated.go (10)
go/pkg/db/identity_delete_old_by_external_id.sql_generated.go (1)
  • DeleteOldIdentityByExternalIDParams (23-27)
go/pkg/db/models_generated.go (2)
  • Credit (569-580)
  • Identity (642-651)
go/pkg/db/identity_find.sql_generated.go (2)
  • FindIdentityParams (52-56)
  • FindIdentityRow (58-73)
go/pkg/db/keys_find_without_credits.sql_generated.go (2)
  • FindKeysWithoutCreditsParams (36-39)
  • FindKeysWithoutCreditsRow (41-50)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/identity_list.sql_generated.go (2)
  • ListIdentitiesParams (29-34)
  • ListIdentitiesRow (36-49)
go/pkg/db/credits_update_decrement.sql_generated.go (1)
  • UpdateCreditDecrementParams (23-27)
go/pkg/db/credits_update_increment.sql_generated.go (1)
  • UpdateCreditIncrementParams (20-24)
go/pkg/db/credits_update_set.sql_generated.go (1)
  • UpdateCreditSetParams (20-24)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (42-56)
internal/db/src/schema/keys.ts (1)
internal/db/src/schema/credits.ts (1)
  • credits (16-64)
🔇 Additional comments (36)
go/apps/api/routes/v2_keys_verify_key/200_test.go (1)

223-236: LGTM - Formatting alignment.

The spacing adjustments to header map literals improve readability without changing functionality.

go/pkg/uid/uid.go (1)

30-30: LGTM - Credit prefix follows established pattern.

The new CreditPrefix constant is well-named and consistent with existing resource type prefixes in the system.

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

1-2: LGTM - Standard query pattern.

The query follows the established pattern for single-row lookups and correctly filters credits by key_id.

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

1-2: LGTM - Standard delete pattern.

The delete query correctly removes a credit by ID and follows the established pattern for sqlc exec operations.

internal/db/src/schema/index.ts (1)

5-5: LGTM - Credits schema export.

The new re-export follows the established pattern and properly exposes the credits schema module.

internal/metrics/src/index.ts (1)

88-92: Schema allows both identifiers absent, but keyId is always present in practice.

The metric.usagelimit schema now allows both keyId and creditId to be optional. However, LimitRequest requires keyId as a mandatory field (not optional), so the metric will always include keyId when emitted. creditId is never included in any emission point and has no usage in the usagelimit package. While the schema theoretically allows both absent, the code ensures keyId is always populated. If creditId is not currently needed, consider removing it from the schema to avoid confusion.

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

1-9: LGTM! Credits cascade deletion implemented correctly.

The addition of credits to the DELETE statement and LEFT JOIN ensures that associated credits are properly cleaned up when deleting old identities. The LEFT JOIN is appropriate since credits may not exist for all identities.

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

1-2: LGTM! Simple and correct query.

The query correctly retrieves the remaining credits by ID. The :one annotation is appropriate since id should be the primary key.

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

1-2: LGTM! Simple and correct query.

The query correctly retrieves the remaining requests for a key by ID. The :one annotation is appropriate for a primary key lookup.

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

12-39: LGTM! Generated code correctly reflects the credits cascade deletion.

The generated code properly extends the delete operation to include the credits table with a LEFT JOIN, maintaining consistency with the identity deletion pattern. Parameter bindings are correct.

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

12-42: LGTM! Generated code correctly implements credits cascade deletion.

The generated code properly extends the delete operation to include the credits table. The parameter bindings and LEFT JOIN logic are correct, ensuring credits are deleted when present.

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

57-59: LGTM! Type exports follow established patterns.

The Credits and InsertCredits type exports are consistent with the existing type definitions in the file, using the standard drizzle-orm inference utilities. The placement and naming conventions are appropriate.

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

1-2: No issues found—unique constraint on identity_id is properly enforced.

The schema verification confirms that the credits table has a UNIQUE constraint on identity_id (line 139 in schema.sql). This constraint enforces the PR's stated requirement that credits exist either on an identity or on a key, never both, making the :one annotation safe and appropriate.

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

2-5: LGTM! Credits cleanup integrated correctly.

The addition of the credits table to the multi-table DELETE statement with a LEFT JOIN ensures that any credits associated with the identity are properly cleaned up during deletion.

internal/id/src/generate.ts (1)

7-7: LGTM! Credit prefix addition is correct.

The new "cr" prefix enables credit ID generation consistent with the existing ID system.

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

1-21: LGTM! Migration query correctly identifies keys without credits.

The LEFT JOIN with c.id IS NULL filter correctly identifies keys that have legacy remaining_requests values but haven't been migrated to the new credits table. The CASE expression properly handles NULL timestamps, and pagination support is appropriate for large-scale migrations.

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

2-14: Database schema correctly enforces uniqueness—no action needed.

The verification confirms that go/pkg/db/schema.sql includes the constraint CONSTRAINT unique_per_identity_idx UNIQUE(identity_id) at line 139 on the credits table. The LEFT JOIN in the query is safe and will not produce duplicate rows for any identity, as the database enforces at most one credit record per identity_id.

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

1-13: Add CHECK constraint to enforce key_id and identity_id mutual exclusivity.

The credits table schema is missing the required CHECK constraint. Current UNIQUE constraints on key_id and identity_id are insufficient—they allow both columns to be NULL or both to be populated simultaneously, violating the PR objective that credits must exist on either an identity or a key, never both.

The schema file at go/pkg/db/schema.sql:126–140 must be updated to include: CONSTRAINT credits_check CHECK ((key_id IS NULL) <> (identity_id IS NULL))

⛔ Skipped due to learnings
Learnt from: Flo4604
PR: unkeyed/unkey#3841
File: go/pkg/db/schema.sql:122-122
Timestamp: 2025-08-27T13:50:49.819Z
Learning: The unkey project does not use foreign key constraints in their database schema. They handle referential integrity at the application level instead of relying on database-level foreign keys.
go/pkg/db/credits_delete.sql_generated.go (1)

1-22: Auto-generated file - skipping review.

Based on learnings, files with sql_generated.go suffix are auto-generated by sqlc and should not be reviewed as changes would be overwritten on regeneration.

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

1-24: Auto-generated file - skipping review.

Based on learnings, files with sql_generated.go suffix are auto-generated by sqlc and should not be reviewed as changes would be overwritten on regeneration.

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

1-8: LGTM! Correct use of CASE expression for performance.

The CASE expression correctly decrements credits and clamps to 0, aligning with the learning that CASE with duplicate placeholders is faster than GREATEST in MySQL for decrement operations.

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

1-35: Auto-generated file - skipping review.

Based on learnings, files with sql_generated.go suffix are auto-generated by sqlc and should not be reviewed.

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

1-359: Auto-generated file - skipping review.

Based on learnings, files with sql_generated.go suffix are auto-generated by sqlc and should not be reviewed.

go/pkg/db/key_data.go (2)

167-192: LGTM! Credit population logic correctly distinguishes key vs identity credits.

The credit population logic correctly constructs separate Credit instances with appropriate KeyID and IdentityID values for key-level and identity-level credits respectively.


15-16: Enforce mutual exclusivity of KeyCredits and IdentityCredits via CHECK constraint or validation.

The schema allows both key_id and identity_id to be non-NULL simultaneously. The existing UNIQUE constraints on individual columns only prevent duplicate credits per key/identity—they do not enforce the mutual exclusivity requirement. Add a CHECK (key_id IS NOT NULL XOR identity_id IS NOT NULL) constraint at the schema level, or implement validation during credit creation in the application layer to ensure credits exist only on an identity or a key, never both.

⛔ Skipped due to learnings
Learnt from: Flo4604
PR: unkeyed/unkey#3841
File: go/pkg/db/schema.sql:122-122
Timestamp: 2025-08-27T13:50:49.819Z
Learning: The unkey project does not use foreign key constraints in their database schema. They handle referential integrity at the application level instead of relying on database-level foreign keys.
go/apps/api/routes/v2_identities_delete_identity/handler.go (1)

74-91: LGTM! Ratelimits properly included in refactored query.

Verification confirms the FindIdentity query includes ratelimits as a JSON array via JSON_ARRAYAGG (go/pkg/db/queries/identity_find.sql:10-23), with all expected fields (id, name, key_id, identity_id, limit, duration, auto_apply). The refactoring from FindIdentityWithRatelimits to FindIdentity maintains data completeness with no regression.

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

69-92: Now I need to search for credit creation logic and any application-level validation:

Based on the initial script output and web search findings, I can now provide a comprehensive rewritten review comment:

The credits table schema lacks database-level enforcement of the mutual exclusivity constraint.

As of MySQL 8.0.16, MySQL supports table and column CHECK constraints for all storage engines. However, the schema at go/pkg/db/schema.sql lines 126-140 shows the credits table with key_id and identity_id as nullable columns with only UNIQUE constraints—no CHECK constraint enforcing mutual exclusivity exists.

Currently, the schema permits invalid states:

  • Both key_id AND identity_id populated
  • Both columns NULL

To align with the PR objective that credits exist either on an identity or a key (never both), add a CHECK constraint like:

CHECK ((key_id IS NOT NULL AND identity_id IS NULL) OR (key_id IS NULL AND identity_id IS NOT NULL))

Verify that:

  1. Credit creation handlers validate this constraint at the application level if the database version predates 8.0.16
  2. Existing credits in the database comply with this mutual exclusivity requirement before deploying
go/pkg/db/schema.sql (1)

418-419: Index strategy looks appropriate.

The indexes support the expected query patterns:

  • workspace_id_idx for workspace-scoped queries
  • refill_lookup_idx for refill workflow operations
  • UNIQUE constraints on key_id and identity_id automatically provide index coverage for lookups
go/pkg/db/models_generated.go (1)

569-580: Auto-generated code follows expected patterns.

The Credit struct correctly maps the schema with appropriate use of sql.Null* types for nullable columns.

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

1-36: Generated query implementation looks correct.

Standard sqlc-generated query to find credits by key_id. No issues identified.

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

1-36: Generated query implementation looks correct.

Standard sqlc-generated query to find credits by identity_id, mirrors the find_by_key_id pattern.

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

13-21: Decrement logic correctly prevents negative values.

The CASE statement ensures remaining is clamped to 0 if the decrement would result in a negative value, which is appropriate defensive programming for credit management.

Note: This operation is non-idempotent and should not be retried automatically. Based on learnings, the retry concern should be handled at call sites.

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

1-47: Bulk insert follows established patterns.

Standard implementation consistent with other bulk insert operations in the codebase. The 10 placeholders per row correctly match the 10 columns being inserted.

internal/db/src/schema/keys.ts (2)

52-90: Deprecation strategy preserves backwards compatibility.

The existing credit-related fields (refillDay, refillAmount, lastRefillAt, remaining) are marked as deprecated while remaining in the schema, allowing for a gradual migration to the new credits table. This is a sound approach for avoiding breaking changes.

Consider documenting the migration timeline and whether these fields will eventually be removed in a future major version.


140-143: Credits relation correctly establishes one-to-one link.

The new relation maps keys.id to credits.keyId, which aligns with the UNIQUE constraint on credits.key_id in the schema, ensuring a proper one-to-one relationship.

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

58-62: Verify intent with the developer or schema design documentation.

The search found no call sites to UpsertCredit in the repository, so the usage pattern cannot be verified through code inspection. This is a generated file, and the design constraint (uniform Specified flags from args[0] applied to all rows) may reflect either:

  • An intentional design for bulk operations (all rows update consistently)
  • A limitation of the code generator (sqlc)
  • Future usage not yet in the codebase

Without call sites or explicit documentation, it's unclear whether callers are expected to enforce uniform flags or if this constraint should be documented.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 30, 2025

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

@Flo4604 Flo4604 force-pushed the feat/identity-credits-schema branch from 62afa56 to 7017b5a Compare November 3, 2025 09:49
@vercel vercel bot temporarily deployed to Preview – engineering November 3, 2025 09:50 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard November 3, 2025 09:50 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62afa56 and 7017b5a.

📒 Files selected for processing (57)
  • go/apps/api/routes/v2_identities_delete_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_identities_get_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_identities_update_identity/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_verify_key/200_test.go (2 hunks)
  • go/internal/services/keys/verifier.go (1 hunks)
  • go/pkg/db/bulk_credits_insert.sql_generated.go (1 hunks)
  • go/pkg/db/bulk_credits_upsert.sql_generated.go (1 hunks)
  • go/pkg/db/credits_delete.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_by_key_id.sql_generated.go (1 hunks)
  • go/pkg/db/credits_find_remaining.sql_generated.go (1 hunks)
  • go/pkg/db/credits_insert.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_decrement.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_increment.sql_generated.go (1 hunks)
  • go/pkg/db/credits_update_set.sql_generated.go (1 hunks)
  • go/pkg/db/credits_upsert.sql_generated.go (1 hunks)
  • go/pkg/db/identity_delete_old_by_external_id.sql_generated.go (2 hunks)
  • go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go (2 hunks)
  • go/pkg/db/identity_find.sql_generated.go (1 hunks)
  • go/pkg/db/identity_find_with_ratelimits.sql_generated.go (0 hunks)
  • go/pkg/db/identity_list.sql_generated.go (4 hunks)
  • go/pkg/db/key_data.go (2 hunks)
  • go/pkg/db/key_find_for_verification.sql_generated.go (3 hunks)
  • go/pkg/db/key_find_live_by_hash.sql_generated.go (4 hunks)
  • go/pkg/db/key_find_live_by_id.sql_generated.go (4 hunks)
  • go/pkg/db/key_find_remaining.sql_generated.go (1 hunks)
  • go/pkg/db/keys_find_without_credits.sql_generated.go (1 hunks)
  • go/pkg/db/models_generated.go (1 hunks)
  • go/pkg/db/querier_bulk_generated.go (1 hunks)
  • go/pkg/db/querier_generated.go (14 hunks)
  • go/pkg/db/queries/credits_delete.sql (1 hunks)
  • go/pkg/db/queries/credits_find_by_identity_id.sql (1 hunks)
  • go/pkg/db/queries/credits_find_by_key_id.sql (1 hunks)
  • go/pkg/db/queries/credits_find_remaining.sql (1 hunks)
  • go/pkg/db/queries/credits_insert.sql (1 hunks)
  • go/pkg/db/queries/credits_update_decrement.sql (1 hunks)
  • go/pkg/db/queries/credits_update_increment.sql (1 hunks)
  • go/pkg/db/queries/credits_update_set.sql (1 hunks)
  • go/pkg/db/queries/credits_upsert.sql (1 hunks)
  • go/pkg/db/queries/identity_delete_old_by_external_id.sql (1 hunks)
  • go/pkg/db/queries/identity_delete_old_with_ratelimits.sql (1 hunks)
  • go/pkg/db/queries/identity_find.sql (1 hunks)
  • go/pkg/db/queries/identity_find_with_ratelimits.sql (0 hunks)
  • go/pkg/db/queries/identity_list.sql (1 hunks)
  • go/pkg/db/queries/key_find_for_verification.sql (1 hunks)
  • go/pkg/db/queries/key_find_live_by_hash.sql (1 hunks)
  • go/pkg/db/queries/key_find_live_by_id.sql (1 hunks)
  • go/pkg/db/queries/key_find_remaining.sql (1 hunks)
  • go/pkg/db/queries/keys_find_without_credits.sql (1 hunks)
  • go/pkg/db/schema.sql (2 hunks)
  • go/pkg/uid/uid.go (1 hunks)
  • internal/db/src/schema/credits.ts (1 hunks)
  • internal/db/src/schema/index.ts (1 hunks)
  • internal/db/src/schema/keys.ts (4 hunks)
  • internal/db/src/types.ts (1 hunks)
  • internal/id/src/generate.ts (1 hunks)
  • internal/metrics/src/index.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • go/pkg/db/identity_find_with_ratelimits.sql_generated.go
  • go/pkg/db/queries/identity_find_with_ratelimits.sql
🚧 Files skipped from review as they are similar to previous changes (20)
  • go/pkg/db/queries/credits_insert.sql
  • go/pkg/db/queries/credits_find_remaining.sql
  • go/pkg/uid/uid.go
  • go/pkg/db/queries/identity_delete_old_with_ratelimits.sql
  • go/pkg/db/credits_find_by_key_id.sql_generated.go
  • go/pkg/db/queries/credits_find_by_identity_id.sql
  • go/pkg/db/queries/key_find_live_by_hash.sql
  • go/pkg/db/queries/key_find_remaining.sql
  • go/apps/api/routes/v2_keys_verify_key/200_test.go
  • go/pkg/db/queries/credits_update_decrement.sql
  • go/pkg/db/credits_find_by_identity_id.sql_generated.go
  • go/pkg/db/queries/key_find_for_verification.sql
  • go/pkg/db/schema.sql
  • go/pkg/db/credits_update_increment.sql_generated.go
  • go/pkg/db/identity_delete_old_by_external_id.sql_generated.go
  • go/pkg/db/queries/credits_upsert.sql
  • go/pkg/db/queries/credits_find_by_key_id.sql
  • go/pkg/db/queries/credits_delete.sql
  • internal/id/src/generate.ts
  • go/pkg/db/queries/identity_list.sql
🧰 Additional context used
🧠 Learnings (20)
📓 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.710Z
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.
📚 Learning: 2025-10-30T15:10:52.710Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 4190
File: go/internal/services/keys/verifier.go:51-53
Timestamp: 2025-10-30T15:10:52.710Z
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:

  • go/internal/services/keys/verifier.go
  • internal/db/src/types.ts
  • go/pkg/db/models_generated.go
  • go/pkg/db/credits_delete.sql_generated.go
  • go/pkg/db/queries/keys_find_without_credits.sql
  • go/pkg/db/credits_insert.sql_generated.go
  • internal/db/src/schema/keys.ts
  • go/pkg/db/queries/key_find_live_by_id.sql
  • go/pkg/db/queries/identity_find.sql
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/identity_list.sql_generated.go
  • go/pkg/db/queries/credits_update_set.sql
  • go/pkg/db/credits_find_remaining.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/key_data.go
  • go/pkg/db/identity_find.sql_generated.go
  • internal/db/src/schema/credits.ts
  • go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • internal/metrics/src/index.ts
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/querier_bulk_generated.go
  • go/pkg/db/queries/identity_delete_old_by_external_id.sql
  • go/pkg/db/queries/credits_update_increment.sql
  • go/pkg/db/querier_generated.go
📚 Learning: 2025-08-27T13:48:54.016Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3841
File: go/pkg/db/queries/key_find_for_verification.sql:15-15
Timestamp: 2025-08-27T13:48:54.016Z
Learning: The pending_migration_id field in FindKeyForVerification is only used internally by get_migrated.go for migration logic and doesn't need to be exposed in KeyVerifier struct or API response DTOs since it's an internal implementation detail.

Applied to files:

  • go/internal/services/keys/verifier.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
📚 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:

  • go/internal/services/keys/verifier.go
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/queries/credits_update_increment.sql
  • go/pkg/db/querier_generated.go
📚 Learning: 2025-04-22T14:43:11.724Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 3161
File: go/pkg/clickhouse/schema/databases/002_ratelimits/006_ratelimits_per_day_v1.sql:1-13
Timestamp: 2025-04-22T14:43:11.724Z
Learning: In the unkey project, the SQL files in clickhouse/schema/databases represent the current production schema and shouldn't be modified directly in PRs. Schema changes require dedicated migration scripts.

Applied to files:

  • go/internal/services/keys/verifier.go
📚 Learning: 2025-09-12T08:01:20.792Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3944
File: go/pkg/db/acme_challenge_update_verified_with_expiry.sql_generated.go:31-39
Timestamp: 2025-09-12T08:01:20.792Z
Learning: Do not review or suggest changes to files with sql_generated.go suffix or other files marked as auto-generated (containing "Code generated by" comments), as these are generated by tools like sqlc and changes would be overwritten on regeneration.

Applied to files:

  • go/pkg/db/credits_delete.sql_generated.go
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/credits_update_set.sql_generated.go
  • go/pkg/db/queries/credits_update_increment.sql
📚 Learning: 2025-07-17T14:24:20.403Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.403Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.

Applied to files:

  • go/pkg/db/queries/keys_find_without_credits.sql
  • go/pkg/db/credits_insert.sql_generated.go
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/key_find_for_verification.sql_generated.go
  • go/pkg/db/credits_upsert.sql_generated.go
  • go/pkg/db/key_find_remaining.sql_generated.go
  • go/pkg/db/credits_update_decrement.sql_generated.go
  • go/pkg/db/keys_find_without_credits.sql_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/bulk_credits_insert.sql_generated.go
  • go/pkg/db/bulk_credits_upsert.sql_generated.go
  • go/pkg/db/querier_bulk_generated.go
📚 Learning: 2025-08-18T08:41:37.689Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3804
File: go/pkg/db/key_update_credits_decrement.sql_generated.go:13-20
Timestamp: 2025-08-18T08:41:37.689Z
Learning: In MySQL queries for decrementing key credits, using GREATEST(remaining_requests - ?, 0) is slower than using a CASE expression with duplicate placeholders (CASE WHEN remaining_requests - ? > 0 THEN remaining_requests - ? ELSE 0 END), even though GREATEST reduces parameter count.

Applied to files:

  • go/pkg/db/queries/keys_find_without_credits.sql
📚 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:

  • internal/db/src/schema/keys.ts
  • go/pkg/db/queries/key_find_live_by_id.sql
  • go/pkg/db/key_find_live_by_hash.sql_generated.go
  • go/pkg/db/key_find_live_by_id.sql_generated.go
  • go/pkg/db/querier_generated.go
📚 Learning: 2024-11-29T15:15:47.308Z
Learnt from: chronark
Repo: unkeyed/unkey PR: 2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In `apps/api/src/routes/v1_keys_updateKey.ts`, the code intentionally handles `externalId` and `ownerId` separately for clarity. The `ownerId` field will be removed in the future, simplifying the code.

Applied to files:

  • internal/db/src/schema/keys.ts
  • internal/db/src/schema/credits.ts
📚 Learning: 2025-08-21T12:37:40.996Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3821
File: apps/dashboard/lib/trpc/routers/key/updateRootKeyPermissions.ts:74-74
Timestamp: 2025-08-21T12:37:40.996Z
Learning: Root keys in Unkey have two workspace fields: `workspaceId` (always set to env().UNKEY_WORKSPACE_ID for the Unkey workspace that owns the key) and `forWorkspaceId` (set to ctx.workspace.id for the user's workspace that the key is for). When querying root keys, the system filters by forWorkspaceId to get keys for the current user's workspace, but the returned rootKey.workspaceId is always the Unkey workspace ID.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • internal/db/src/schema/keys.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:

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

Applied to files:

  • internal/db/src/schema/credits.ts
  • go/pkg/db/queries/identity_delete_old_by_external_id.sql
📚 Learning: 2025-08-27T13:50:49.819Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3841
File: go/pkg/db/schema.sql:122-122
Timestamp: 2025-08-27T13:50:49.819Z
Learning: The unkey project does not use foreign key constraints in their database schema. They handle referential integrity at the application level instead of relying on database-level foreign keys.

Applied to files:

  • internal/db/src/schema/credits.ts
📚 Learning: 2025-09-01T08:27:27.709Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3895
File: go/pkg/db/querier_generated.go:1293-1307
Timestamp: 2025-09-01T08:27:27.709Z
Learning: In the unkey codebase, for JSON_ARRAYAGG queries in ListLiveKeysByKeyAuthID and similar database queries, deterministic ordering is not a priority requirement. The team is fine with non-deterministic ordering for permissions and roles aggregation.

Applied to files:

  • internal/db/src/schema/credits.ts
📚 Learning: 2025-09-01T08:26:57.944Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3895
File: go/pkg/db/key_list_live_by_auth_id.sql_generated.go:53-67
Timestamp: 2025-09-01T08:26:57.944Z
Learning: For the ListLiveKeysByKeyAuthID query in the unkeyed/unkey repository, the user Flo4604 confirmed that ordering of role permissions in the JSON array output is not required.

Applied to files:

  • internal/db/src/schema/credits.ts
📚 Learning: 2025-09-11T14:24:40.988Z
Learnt from: Flo4604
Repo: unkeyed/unkey PR: 3944
File: go/apps/ctrl/services/deployment/deploy_workflow.go:326-334
Timestamp: 2025-09-11T14:24:40.988Z
Learning: The InsertDomains method in the bulk queries uses ON DUPLICATE KEY UPDATE, making it an upsert operation that is idempotent and safe for retries, despite the "Insert" naming convention.

Applied to files:

  • go/pkg/db/bulk_credits_upsert.sql_generated.go
🧬 Code graph analysis (17)
go/internal/services/keys/verifier.go (1)
go/pkg/db/models_generated.go (1)
  • Credit (569-580)
internal/db/src/types.ts (1)
internal/db/src/index.ts (1)
  • schema (8-8)
go/apps/api/routes/v2_identities_delete_identity/handler.go (4)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (52-56)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/codes/unkey_data.go (1)
  • Data (95-139)
go/pkg/db/credits_insert.sql_generated.go (1)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
internal/db/src/schema/keys.ts (1)
internal/db/src/schema/credits.ts (1)
  • credits (16-64)
go/apps/api/routes/v2_identities_update_identity/handler.go (4)
go/pkg/db/identity_find.sql_generated.go (2)
  • FindIdentityParams (52-56)
  • FindIdentityRow (58-73)
go/apps/api/openapi/gen.go (1)
  • Identity (161-173)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/db/custom_types.go (1)
  • RatelimitInfo (23-31)
go/apps/api/routes/v2_identities_get_identity/handler.go (3)
go/pkg/db/identity_find.sql_generated.go (1)
  • FindIdentityParams (52-56)
go/pkg/db/handle_err_no_rows.go (1)
  • IsNotFound (8-10)
go/pkg/codes/unkey_data.go (1)
  • Data (95-139)
go/pkg/db/key_find_live_by_hash.sql_generated.go (1)
go/pkg/db/models_generated.go (4)
  • Api (514-525)
  • KeyAuth (679-690)
  • Workspace (828-846)
  • EncryptedKey (622-629)
go/pkg/db/key_find_for_verification.sql_generated.go (1)
go/pkg/db/types/null_string.go (1)
  • NullString (10-10)
go/pkg/db/identity_list.sql_generated.go (1)
go/pkg/db/models_generated.go (1)
  • Environment (631-640)
go/pkg/db/key_data.go (1)
go/pkg/db/models_generated.go (1)
  • Credit (569-580)
go/pkg/db/identity_find.sql_generated.go (1)
go/pkg/db/models_generated.go (2)
  • Identity (642-651)
  • Environment (631-640)
internal/db/src/schema/credits.ts (3)
internal/db/src/schema/workspaces.ts (1)
  • workspaces (16-104)
internal/db/src/schema/keys.ts (1)
  • keys (22-116)
internal/db/src/schema/identity.ts (1)
  • identities (16-38)
go/pkg/db/bulk_credits_insert.sql_generated.go (1)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/bulk_credits_upsert.sql_generated.go (1)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (46-61)
go/pkg/db/querier_bulk_generated.go (3)
internal/db/src/types.ts (1)
  • InsertCredits (59-59)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (46-61)
go/pkg/db/querier_generated.go (10)
go/pkg/db/identity_delete_old_by_external_id.sql_generated.go (1)
  • DeleteOldIdentityByExternalIDParams (23-27)
go/pkg/db/models_generated.go (2)
  • Credit (569-580)
  • Identity (642-651)
go/pkg/db/identity_find.sql_generated.go (2)
  • FindIdentityParams (52-56)
  • FindIdentityRow (58-73)
go/pkg/db/keys_find_without_credits.sql_generated.go (2)
  • FindKeysWithoutCreditsParams (36-39)
  • FindKeysWithoutCreditsRow (41-50)
go/pkg/db/credits_insert.sql_generated.go (1)
  • InsertCreditParams (28-39)
go/pkg/db/identity_list.sql_generated.go (2)
  • ListIdentitiesParams (29-34)
  • ListIdentitiesRow (36-49)
go/pkg/db/credits_update_decrement.sql_generated.go (1)
  • UpdateCreditDecrementParams (23-27)
go/pkg/db/credits_update_increment.sql_generated.go (1)
  • UpdateCreditIncrementParams (20-24)
go/pkg/db/credits_update_set.sql_generated.go (1)
  • UpdateCreditSetParams (20-24)
go/pkg/db/credits_upsert.sql_generated.go (1)
  • UpsertCreditParams (46-61)
⏰ 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). (5)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: autofix
🔇 Additional comments (17)
go/pkg/db/queries/identity_delete_old_by_external_id.sql (1)

2-5: LGTM! Credits cleanup properly integrated.

The expansion of the DELETE operation to include the credits table is correct. The LEFT JOIN ensures identities are deleted even when no credits exist, which is the right behavior for cleaning up soft-deleted identities and their associated data.

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

13-16: LGTM! Generated code correctly reflects SQL changes.

The sqlc-generated code properly includes the credits table in the DELETE statement and LEFT JOIN, matching the SQL source file changes. The function signature remains unchanged, maintaining backward compatibility.

internal/metrics/src/index.ts (1)

88-92: LGTM! Metric schema updated to support credit-based usage limits.

The changes correctly reflect the new credits functionality by making keyId optional and adding an optional creditId field. This allows the metric.usagelimit to track usage limits for both key-based and credit-based scenarios.

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

1-5: LGTM! Credit update query is correctly structured.

The query provides a clean path to set the remaining credits and updated_at timestamp by ID. The use of parameterized queries and sqlc.arg annotation follows best practices.

internal/db/src/schema/index.ts (1)

5-5: LGTM! Credits schema properly exported.

The new export follows the established pattern and correctly exposes the credits schema alongside other schema modules.

go/internal/services/keys/verifier.go (1)

51-53: LGTM! Credit fields added to support future priority enforcement.

The new IdentityCredits and KeyCredits fields correctly establish the data structure for the credits feature. As confirmed in previous review, this PR focuses solely on schema and query changes—the priority enforcement logic for these credits will be implemented in a subsequent PR in the stack.

Based on learnings

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

58-59: LGTM! Credit types follow established patterns.

The new Credits and InsertCredits type exports correctly mirror the pattern used for other entities (e.g., Quotas above) and properly leverage Drizzle ORM's type inference.

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

74-91: LGTM! Identity query refactored with improved error handling.

The migration from FindIdentityWithRatelimits (multi-row) to FindIdentity (single-row) is well-executed. The explicit not-found handling using db.IsNotFound(err) provides clearer error semantics and returns an appropriate fault code before falling back to generic database errors.

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

116-134: LGTM: Clean refactor to single-row identity query.

The migration from FindIdentityWithRatelimits to FindIdentity is well-executed, with proper not-found handling using db.IsNotFound(err) and appropriate fault wrapping.

go/pkg/db/key_data.go (2)

15-16: LGTM: Credits fields added to KeyData.

The new KeyCredits and IdentityCredits fields properly extend KeyData to surface credit information for keys and their associated identities.


166-192: LGTM: Credit population logic is correct.

The implementation correctly:

  • Populates KeyCredits when CreditID.Valid is true, with KeyID set and IdentityID left invalid
  • Populates IdentityCredits when IdentityCreditID.Valid is true, with IdentityID set and KeyID left invalid

This aligns with the schema constraint that credits belong to either a key or an identity, not both.

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

1-72: Skipping review of generated file.

This file is auto-generated by the sqlc bulk insert plugin and should not be manually modified.

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

1-37: LGTM: Well-structured identity query with credits and ratelimits.

The query efficiently retrieves:

  • Identity data from the main table
  • Associated credit information via LEFT JOIN
  • Ratelimits as a JSON array with proper fallback

The UNION ALL pattern for matching by either id or external_id is appropriate, and the LIMIT 1 ensures a single-row result.

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

54-72: LGTM: Consistent migration to single-row identity query.

The refactor properly uses FindIdentity with explicit not-found handling via db.IsNotFound(err), matching the pattern in the update handler.

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

1-113: Skipping review of generated file.

This file is auto-generated by sqlc and should not be manually modified.

internal/db/src/schema/credits.ts (1)

16-64: LGTM: Credits schema is well-structured.

The table definition includes:

  • Appropriate primary key and indexes
  • Unique constraints on both keyId and identityId to ensure at most one credit per owner
  • Proper documentation of the exclusivity constraint
  • Refill-related indexes for efficient queries

The mutual exclusivity of keyId and identityId will be enforced at the application level, as previously discussed.

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

1-24: Skipping review of generated file.

This file is auto-generated by sqlc and should not be manually modified.

Comment on lines +1 to +21
-- name: FindKeysWithoutCredits :many
SELECT
k.id,
k.workspace_id,
k.remaining_requests,
k.refill_day,
k.refill_amount,
CASE
WHEN k.last_refill_at IS NULL THEN NULL
ELSE UNIX_TIMESTAMP(k.last_refill_at) * 1000
END as last_refill_at_unix,
k.created_at_m,
k.updated_at_m
FROM `keys` k
LEFT JOIN `credits` c ON c.key_id = k.id
WHERE k.deleted_at_m IS NULL
AND k.remaining_requests IS NOT NULL
AND c.id IS NULL
ORDER BY k.created_at_m DESC
LIMIT ?
OFFSET ?; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid returning keys whose identity already owns credits

As written, this query flags every key missing a key-scoped credit, even when its identity already has a credit row. Using it to backfill credits would create both key- and identity-level credits for the same identity, violating the “one or the other” rule this PR is introducing. Please exclude keys whose identity already has a credit record before they reach the migration.

[suggested fix]

 FROM `keys` k
-LEFT JOIN `credits` c ON c.key_id = k.id
+LEFT JOIN `credits` c ON c.key_id = k.id
+LEFT JOIN `credits` ic ON ic.identity_id = k.identity_id
 WHERE k.deleted_at_m IS NULL
     AND k.remaining_requests IS NOT NULL
-    AND c.id IS NULL
+    AND c.id IS NULL
+    AND (k.identity_id IS NULL OR ic.id IS NULL)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- name: FindKeysWithoutCredits :many
SELECT
k.id,
k.workspace_id,
k.remaining_requests,
k.refill_day,
k.refill_amount,
CASE
WHEN k.last_refill_at IS NULL THEN NULL
ELSE UNIX_TIMESTAMP(k.last_refill_at) * 1000
END as last_refill_at_unix,
k.created_at_m,
k.updated_at_m
FROM `keys` k
LEFT JOIN `credits` c ON c.key_id = k.id
WHERE k.deleted_at_m IS NULL
AND k.remaining_requests IS NOT NULL
AND c.id IS NULL
ORDER BY k.created_at_m DESC
LIMIT ?
OFFSET ?;
-- name: FindKeysWithoutCredits :many
SELECT
k.id,
k.workspace_id,
k.remaining_requests,
k.refill_day,
k.refill_amount,
CASE
WHEN k.last_refill_at IS NULL THEN NULL
ELSE UNIX_TIMESTAMP(k.last_refill_at) * 1000
END as last_refill_at_unix,
k.created_at_m,
k.updated_at_m
FROM `keys` k
LEFT JOIN `credits` c ON c.key_id = k.id
LEFT JOIN `credits` ic ON ic.identity_id = k.identity_id
WHERE k.deleted_at_m IS NULL
AND k.remaining_requests IS NOT NULL
AND c.id IS NULL
AND (k.identity_id IS NULL OR ic.id IS NULL)
ORDER BY k.created_at_m DESC
LIMIT ?
OFFSET ?;
🤖 Prompt for AI Agents
In go/pkg/db/queries/keys_find_without_credits.sql around lines 1 to 21, the
query currently selects keys that lack a key-scoped credit but does not exclude
keys whose identity already has a credit; add a condition to exclude any key
whose identity_id has an existing (non-deleted) credit record. Implement this by
adding a NOT EXISTS subquery (or an additional LEFT JOIN + WHERE c2.id IS NULL)
that checks credits for c2.identity_id = k.identity_id (and c2.deleted_at_m IS
NULL) so keys belonging to identities with credits are filtered out before
migration.

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.

Looking good.

@@ -113,36 +113,34 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error {
}

// Use UNION query to find identity + ratelimits in one query (fast!)
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably outdated comment

@@ -0,0 +1,2 @@
-- name: FindCreditsByKeyID :one
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't this have a LIMIT 1?

Copy link
Collaborator

Choose a reason for hiding this comment

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

same for the one above btw

Copy link
Collaborator

Choose a reason for hiding this comment

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

and below lol

) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
remaining = CASE
WHEN CAST(sqlc.arg('remaining_specified') AS UNSIGNED) = 1 THEN VALUES(remaining)
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this necessary?

Copy link
Member Author

Choose a reason for hiding this comment

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

because when we upsert and hit a race condition we only want to overwrite the data that we know has been given to us in the api.

for example you send updateIdentity at the same time, whoever writes last will win, but we shouldn't set data that wasnt sent to us in the first place. e.g only sets refill but not remaining etc

Since SQLC only has a state of NULL or not null value

Copy link
Member Author

Choose a reason for hiding this comment

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

hope that makes sense

`remaining` int NOT NULL,
`refill_day` tinyint,
`refill_amount` int,
`refilled_at` bigint unsigned,
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this one unsigned, yet the other dates are signed?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah didnt think about it too much but we can change that

@graphite-app
Copy link

graphite-app bot commented Nov 3, 2025

Sesame Street gif. Ernie looks at us, smiling widely and giving a big thumbs up. (Added via Giphy)

@graphite-app
Copy link

graphite-app bot commented Nov 3, 2025

Graphite Automations

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

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

@Flo4604 Flo4604 force-pushed the feat/identity-credits-schema branch from 7017b5a to 84cf2ac Compare November 4, 2025 17:08
@vercel vercel bot temporarily deployed to Preview – engineering November 4, 2025 17:09 Inactive
- Add new credits table supporting both key-based and identity-based credits
- Update keys schema to include credits relation
- Add credit type to types and ID generation
- Add SQL queries for credit operations (find remaining, decrement)
- Update identity list query to include credit information
@vercel vercel bot temporarily deployed to Preview – dashboard November 4, 2025 17:11 Inactive
@Flo4604 Flo4604 force-pushed the feat/identity-credits-schema branch from 84cf2ac to 8277f35 Compare November 4, 2025 17:11
@vercel vercel bot temporarily deployed to Preview – engineering November 4, 2025 17:12 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard November 4, 2025 17:15 Inactive
@Flo4604 Flo4604 closed this Nov 4, 2025
@Flo4604 Flo4604 deleted the feat/identity-credits-schema branch November 4, 2025 18:19
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