feat: add content throttle and dashboard APIs#36
Conversation
# Conflicts: # apps/api/src/index.ts # packages/db/src/local-repository.ts # packages/db/src/postgres/repository.ts
|
Important Review skippedThis PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR adds member-scoped web API key management (POST /v1/web/keys and POST /v1/web/keys/:id/revoke), cursor-based pagination for web audit events (GET /v1/web/audit with limit/cursor), and per-artifact read rate limiting (ARTIFACT_RATE_LIMIT binding). Changes span contracts/OpenAPI, API routing and validation, DB schema/migrations/queries/repositories, tests across API/content/db, and tooling/config (wrangler, vitest). Sequence Diagram(s)sequenceDiagram
participant Client
participant API_Server
participant DB
participant RateLimiter
Client->>API_Server: POST /v1/web/keys (idempotency-key, body)
API_Server->>DB: createWebApiKey(member, idempotencyKey, name)
DB->>DB: persist key, emit api_key.created event
DB-->>API_Server: {api_key_id, public, secret}
API_Server-->>Client: 201 Created
Client->>API_Server: GET /v1/web/audit?limit=...&cursor=...
API_Server->>DB: listWebAuditEvents(member, {limit,cursor})
DB->>DB: decode cursor, query page, encode next_cursor
DB-->>API_Server: {items[], page_info}
API_Server-->>Client: 200 {items,page_info}
Client->>API_Server: GET /v/{token}/{path}
API_Server->>RateLimiter: rateLimitArtifactRead(key=artifact)
alt allowed
RateLimiter-->>API_Server: allowed
API_Server-->>Client: 200 Content
else denied
RateLimiter-->>API_Server: limited
API_Server-->>Client: 429 rate_limited_artifact (Retry-After)
end
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/index.ts`:
- Around line 426-431: Wrap the call to readJsonObject(...) in a try/catch and
convert JSON parse errors into a 400 invalid_request response instead of letting
them bubble to a 500; specifically, in the handler around readJsonObject and
CreateApiKeyRequest.safeParse, catch any error thrown by readJsonObject and call
errorResponse(context, "invalid_request", 400), otherwise proceed to validate
with CreateApiKeyRequest.safeParse and call runIdempotent(..., () =>
createWebApiKey(...), 201) as before.
In `@packages/db/snapshot/schema.sql`:
- Around line 69-70: The snapshot is missing a composite index that matches the
audit cursor ordering; add a new index on (workspace_id, occurred_at, id) for
the operation_events table so keyset pagination using (workspace_id,
occurred_at, id) is efficient. Create the index with a clear name (e.g.
operation_events_workspace_occurred_id_idx) and ensure it uses the same sort
directions as your queries (default ascending unless you need DESC on
occurred_at), keeping the existing CONSTRAINT
"operation_events_actor_type_check" unchanged. Run the snapshot migration after
adding the index so the schema includes the new composite key for audit reads.
In `@packages/db/src/schema.ts`:
- Around line 170-171: The composite index
operation_events_workspace_occurred_idx currently indexes table.workspaceId and
table.occurredAt but the web audit query paginates and orders by occurred_at
DESC, id DESC; update the index definition
(operation_events_workspace_occurred_idx) to include table.id as the final
column (i.e., index on workspace_id, occurred_at, id) so the DB can satisfy
ordering and cursor scans without an extra sort/scan.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 54adb2ea-0524-4e14-9804-dbea43b6a848
📒 Files selected for processing (33)
apps/api/src/index.test.tsapps/api/src/index.tsapps/cli/src/index.tsapps/content/src/index.test.tsapps/content/src/index.tsapps/content/src/worker-configuration.d.tsapps/content/wrangler.jsoncdocs/ops/project-status.mddocs/ops/web-app-todo.mdpackages/auth/src/request-id.tspackages/commands/src/index.tspackages/contracts/openapi/api.jsonpackages/contracts/openapi/content.jsonpackages/contracts/openapi/upload.jsonpackages/contracts/package.jsonpackages/contracts/src/apiKeys.tspackages/contracts/src/common.tspackages/contracts/src/enums.tspackages/contracts/src/mvp-contracts.test.tspackages/contracts/src/openapi/api.tspackages/contracts/src/openapi/content.tspackages/contracts/src/openapi/responses.tspackages/contracts/src/routes.tspackages/db/migrations/0005_member_actor_events.sqlpackages/db/snapshot/schema.sqlpackages/db/src/index.test.tspackages/db/src/local-repository.tspackages/db/src/postgres/repository.tspackages/db/src/postgres/rls.test.tspackages/db/src/queries/operation-events.tspackages/db/src/schema.tspackages/db/src/types.tsvitest.config.ts
💤 Files with no reviewable changes (1)
- packages/contracts/src/apiKeys.ts
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/db/migrations/0006_operation_events_web_audit_cursor_index.sql`:
- Around line 3-4: The migration creates the index
operation_events_workspace_occurred_id_idx with "occurred_at desc, id desc" but
the schema sources define it without DESC; pick one canonical ordering and make
all three definitions identical. Update the migration SQL
(operation_events_workspace_occurred_id_idx), the in-code schema definition in
schema.ts, and the snapshot/schema.sql so the index columns and ordering (either
plain ascending/no DESC or explicit DESC for occurred_at and id) match exactly
to avoid drift.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0bcbb7ba-8694-4fe5-ad31-214d1c1f6121
📒 Files selected for processing (5)
apps/api/src/index.test.tsapps/api/src/index.tspackages/db/migrations/0006_operation_events_web_audit_cursor_index.sqlpackages/db/snapshot/schema.sqlpackages/db/src/schema.ts
| create index if not exists operation_events_workspace_occurred_id_idx | ||
| on operation_events(workspace_id, occurred_at desc, id desc); |
There was a problem hiding this comment.
Keep the index definition consistent across migration and schema sources.
packages/db/src/schema.ts and packages/db/snapshot/schema.sql define operation_events_workspace_occurred_id_idx without DESC, while this migration creates (workspace_id, occurred_at desc, id desc). Reusing the same index name for different definitions leaves fresh schemas out of sync with migrated databases and will create drift in future schema checks. Pick one ordering and align all three files.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/db/migrations/0006_operation_events_web_audit_cursor_index.sql`
around lines 3 - 4, The migration creates the index
operation_events_workspace_occurred_id_idx with "occurred_at desc, id desc" but
the schema sources define it without DESC; pick one canonical ordering and make
all three definitions identical. Update the migration SQL
(operation_events_workspace_occurred_id_idx), the in-code schema definition in
schema.ts, and the snapshot/schema.sql so the index columns and ordering (either
plain ascending/no DESC or explicit DESC for occurred_at and id) match exactly
to avoid drift.
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
# Conflicts: # docs/ops/project-status.md
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/contracts/openapi/upload.json`:
- Line 649: Update the 429 response descriptions for the upload endpoints to
reference the correct schema: replace mentions of ArtifactRateLimitErrorEnvelope
with RateLimitErrorEnvelope in the response "description" fields (the 429
responses for upload routes), so the documentation and SDKs reflect the actual
response schema; search for occurrences of ArtifactRateLimitErrorEnvelope in
upload-related 429 descriptions and change them to RateLimitErrorEnvelope.
In `@packages/contracts/src/mvp-contracts.test.ts`:
- Around line 47-86: The test only asserts OpenAPI responses for content.get;
add equivalent assertions for content.head: find the routeContracts entry with
id "content.head" and assert it's defined and its .errors includes
"rate_limited_artifact"; read the HEAD responses from
buildContentOpenApiDocument() via
contentOpenApi.paths?.["/v/{token}/{path}"]?.head?.responses? and assert the 429
response exists, has a "Retry-After" header and uses the
ArtifactRateLimitErrorEnvelope $ref, and assert the 404 response uses
ContentNotFoundErrorEnvelope; mirror the same component schema enum checks you
already have for GET so HEAD is validated against the same envelopes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5b7e00c7-6249-45d1-a06e-58558053067a
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (15)
apps/api/src/index.test.tsapps/upload/src/index.test.tsdocs/ops/project-status.mdpackages/contracts/openapi/api.jsonpackages/contracts/openapi/content.jsonpackages/contracts/openapi/upload.jsonpackages/contracts/package.jsonpackages/contracts/src/mvp-contracts.test.tspackages/contracts/src/openapi/api.tspackages/contracts/src/openapi/content.tspackages/contracts/src/openapi/responses.tspackages/db/migrations/0006_operation_events_web_audit_cursor_index.sqlpackages/db/snapshot/schema.sqlpackages/db/src/index.test.tspackages/db/src/schema.ts
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
# Conflicts: # apps/api/src/index.ts # docs/ops/project-status.md # packages/db/src/local-repository.ts # packages/db/src/postgres/repository.ts
|
agent-paste PR preview is ready. API: https://agent-paste-api-pr-36.isaac-a46.workers.dev |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/api/src/index.ts (1)
504-507:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandle malformed JSON as
invalid_requestin admin API key creation.
readJsonObject(request)can throw here; right now malformed JSON becomes a 500 instead of a 400 client error.Proposed fix
- const body = await readJsonObject(request); + let body: Record<string, unknown>; + try { + body = await readJsonObject(request); + } catch { + return errorResponse(context, "invalid_request", 400); + } const parsed = CreateApiKeyRequest.safeParse(body); if (!parsed.success) { return errorResponse(context, "invalid_request", 400); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/src/index.ts` around lines 504 - 507, Wrap the call to readJsonObject(request) in a try/catch in the admin API key creation flow so malformed JSON is caught and returned as a 400: call readJsonObject inside the try, assign to body used by CreateApiKeyRequest.safeParse, and in the catch call errorResponse(context, "invalid_request", 400) (same signature already used) to ensure malformed JSON doesn't result in a 500; update the logic around readJsonObject, CreateApiKeyRequest.safeParse, and errorResponse accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/api/src/index.ts`:
- Around line 504-507: Wrap the call to readJsonObject(request) in a try/catch
in the admin API key creation flow so malformed JSON is caught and returned as a
400: call readJsonObject inside the try, assign to body used by
CreateApiKeyRequest.safeParse, and in the catch call errorResponse(context,
"invalid_request", 400) (same signature already used) to ensure malformed JSON
doesn't result in a 500; update the logic around readJsonObject,
CreateApiKeyRequest.safeParse, and errorResponse accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2445611a-124a-4f83-b39c-d4c14e2e0b8d
📒 Files selected for processing (15)
apps/api/src/index.tsapps/upload/src/index.test.tsdocs/ops/project-status.mdpackages/contracts/openapi/api.jsonpackages/contracts/openapi/upload.jsonpackages/contracts/src/mvp-contracts.test.tspackages/contracts/src/openapi/responses.tspackages/contracts/src/routes.tspackages/db/src/index.test.tspackages/db/src/repository/core.tspackages/db/src/repository/interface.tspackages/db/src/repository/local-entities.tspackages/db/src/repository/ports.tspackages/db/src/repository/postgres-entities.tspackages/db/src/repository/web-transforms.ts
|
agent-paste PR preview resources were cleaned up. The pr-preview-${context.issue.number} environment is left in place; remove it from the GitHub UI if desired. |
Summary
rate_limited_artifactcontracts.Changes
artifact_id, then reads R2; 429 responses includeRetry-After: 60.limitand opaque cursor parameters, ordered byoccurred_at desc, id desc.Risk
High: touches auth-scoped web API endpoints, DB actor constraints, OpenAPI contracts, and Cloudflare preview worker bindings. Mitigated with focused unit/integration tests, DB snapshot checks, local smoke, preview migration/deploy, and local CodeRabbit review.
Test plan
pnpm --filter @agent-paste/content testpnpm --filter @agent-paste/content typecheckpnpm --filter @agent-paste/api testpnpm --filter @agent-paste/api typecheckpnpm --filter @agent-paste/db testpnpm --filter @agent-paste/db typecheckpnpm --filter @agent-paste/db db:checkpnpm --filter @agent-paste/contracts openapi:checkpnpm smoke:localpnpm migrate:previewpnpm deploy:previewpnpm verifyPreview hosted smoke was not run locally because
AGENT_PASTE_PREVIEW_ADMIN_TOKEN/AGENT_PASTE_ADMIN_TOKENis not configured in this shell.CodeRabbit notes
rate_limited_artifactentry in the shared OpenAPI rate-limit envelope; fixed and verified.Summary by CodeRabbit
New Features
Updates
Documentation