feat(api-keys): surface wallet policy access#545
Conversation
|
@greptileai review |
Greptile SummaryThis PR surfaces wallet binding and policy binding metadata in the API key list and detail responses, and reflects that data in both the dashboard table and the create-key review modal. The core change replaces per-key wallet-binding lookups with a single
Confidence Score: 4/5The PR is safe to merge; the batching improvement is correct and the new UI surfaces accurate data for the common case. The apps/sdp-api/src/services/api-key.service.ts — the CASE WHEN EXISTS subquery is the only source of truth for wallet scope; a future migration adding a stored Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant C as Client
participant H as Handler (listApiKeys / getApiKey)
participant AKS as ApiKeyService
participant BAS as buildApiKeyAccessSummaries
participant WB as api_key_wallet_permissions
participant PB as api_key_wallet_policy_bindings
participant WR as wallet_control_profiles
participant AR as api_key_control_profiles
C->>H: GET /v1/api-keys (or /v1/api-keys/:id)
H->>AKS: listForProject(projectId)
AKS->>AKS: SQL: CASE WHEN EXISTS wallet_permissions → wallet_scope
AKS-->>H: ApiKeyListItem[]
H->>BAS: buildApiKeyAccessSummaries(env, db, keyIds)
par Round 1 (parallel)
BAS->>WB: listApiKeyWalletBindingsForApiKeys(keyIds)
WB-->>BAS: ApiKeyWalletBindingForKey[]
BAS->>PB: listApiKeyWalletPolicyBindingsForApiKeys(keyIds)
PB-->>BAS: ApiKeyWalletPolicyBindingRow[]
end
par Round 2 (parallel, driven by profile IDs from Round 1)
BAS->>WR: listActiveWalletControlProfileRevisionRefs(profileIds)
WR-->>BAS: ActivePolicyProfileRevisionRefRow[]
BAS->>AR: listActiveApiKeyControlProfileRevisionRefs(profileIds)
AR-->>BAS: ActivePolicyProfileRevisionRefRow[]
end
BAS-->>H: "Map<keyId, ApiKeyAccessSummary>"
H-->>C: Response with walletBindings + policyBindings + walletScope
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant C as Client
participant H as Handler (listApiKeys / getApiKey)
participant AKS as ApiKeyService
participant BAS as buildApiKeyAccessSummaries
participant WB as api_key_wallet_permissions
participant PB as api_key_wallet_policy_bindings
participant WR as wallet_control_profiles
participant AR as api_key_control_profiles
C->>H: GET /v1/api-keys (or /v1/api-keys/:id)
H->>AKS: listForProject(projectId)
AKS->>AKS: SQL: CASE WHEN EXISTS wallet_permissions → wallet_scope
AKS-->>H: ApiKeyListItem[]
H->>BAS: buildApiKeyAccessSummaries(env, db, keyIds)
par Round 1 (parallel)
BAS->>WB: listApiKeyWalletBindingsForApiKeys(keyIds)
WB-->>BAS: ApiKeyWalletBindingForKey[]
BAS->>PB: listApiKeyWalletPolicyBindingsForApiKeys(keyIds)
PB-->>BAS: ApiKeyWalletPolicyBindingRow[]
end
par Round 2 (parallel, driven by profile IDs from Round 1)
BAS->>WR: listActiveWalletControlProfileRevisionRefs(profileIds)
WR-->>BAS: ActivePolicyProfileRevisionRefRow[]
BAS->>AR: listActiveApiKeyControlProfileRevisionRefs(profileIds)
AR-->>BAS: ActivePolicyProfileRevisionRefRow[]
end
BAS-->>H: "Map<keyId, ApiKeyAccessSummary>"
H-->>C: Response with walletBindings + policyBindings + walletScope
Reviews (5): Last reviewed commit: "Merge branch 'main' into codex/pro-1364-..." | Re-trigger Greptile |
|
@greptileai review |
|
@greptileai review |
|
Ready for QA. Validation:
Focus areas for QA:
|
Summary
Screenshots
Captured locally before opening the PR:
/tmp/pro1364-api-keys-table-desktop.png/tmp/pro1364-api-key-review-modal.pngNote:
gh gist createrejected PNG uploads as binary files, so these are recorded as local artifacts rather than embedded images.Validation
pnpm exec biome check --write apps/sdp-api/src/openapi/schemas/api-keys.ts apps/sdp-api/src/routes/api-keys-wallet-scope.test.ts apps/sdp-api/src/routes/api-keys/handlers.ts apps/sdp-api/src/routes/api-keys/access-response.ts apps/sdp-api/src/routes/projects/handlers/api-keys.ts apps/sdp-api/src/services/api-key.service.ts apps/sdp-web/playwright.config.ts apps/sdp-web/playwright/tests/api-keys.e2e.spec.ts apps/sdp-web/src/app/dashboard/api-keys/api-keys-table-client.tsx apps/sdp-web/src/app/dashboard/api-keys/create-api-key-modal.tsx apps/sdp-web/src/app/dashboard/api-keys/page.tsx packages/sdp-types/src/api-keys.tspnpm --filter @sdp/types typecheckpnpm --filter sdp-web typecheckpnpm --filter @sdp/api typecheckpnpm --filter @sdp/api test:node -- src/routes/api-keys-wallet-scope.test.tspnpm -C apps/sdp-api openapi:generate(no tracked generated diff)doppler run --project solana-developer-platform --config "${DOPPLER_CONFIG:-dev_personal}" -- env DATABASE_URL="${DATABASE_URL:-postgresql://sdp:sdp@127.0.0.1:5432/sdp}" CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="${DATABASE_URL:-postgresql://sdp:sdp@127.0.0.1:5432/sdp}" PLAYWRIGHT_API_PORT=8794 PLAYWRIGHT_API_URL=http://127.0.0.1:8794 PLAYWRIGHT_API_PERSIST_PATH=.wrangler/state-playwright-pro1364-pr PLAYWRIGHT_BASE_URL=http://localhost:3114 PLAYWRIGHT_NEXT_DIST_DIR=.next-playwright-pro1364-pr SDP_API_BASE_URL=http://127.0.0.1:8794 NEXT_PUBLIC_SDP_API_BASE_URL=http://127.0.0.1:8794 pnpm -C apps/sdp-web exec playwright test --config=playwright.config.ts --project=dashboard playwright/tests/api-keys.e2e.spec.tsLinear: PRO-1364