297 finish backend typescript migration and command pattern#300
Conversation
📝 WalkthroughWalkthroughThis PR migrates many backend modules to TypeScript, centralizes validation via a shared hook, introduces a command-pattern for billing (checkout/session processing), tightens typings across policies, commands, routes, webhook handlers, and updates documentation and package manifests. Many barrel exports were reorganized. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Route as Checkout Route
participant Cmd as createSingleProjectCheckout
participant Stripe as Stripe API
participant DB as Database
Client->>Route: POST /checkout
Route->>Route: validate request & auth
Route->>Cmd: createSingleProjectCheckout(env, actor, params)
Cmd->>Cmd: validate config & build idempotency key
Cmd->>Stripe: create Checkout Session (idempotent)
Stripe-->>Cmd: session (url, id)
Cmd-->>Route: { url, sessionId }
Route-->>Client: 200 { url, sessionId }
Client->>Route: POST /webhooks/checkout.session.completed
Route->>Route: verify signature
Route->>Cmd: processCheckoutSession(session, { db, logger })
activate Cmd
Cmd->>DB: read/write grants/subscriptions (idempotent)
DB-->>Cmd: existing records
Cmd-->>Route: { handled, result, ledgerContext }
Route-->>Client: 200 { success }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
corates | 1ec101e | Commit Preview URL | Jan 17 2026, 12:19 AM |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/workers/src/commands/lib/doSync.ts (1)
17-29: Missing response validation - silent failures possible.Unlike
syncMemberToDOandsyncProjectToDOinproject-sync.ts, this function does not checkresponse.ok. If the Durable Object returns an error status, it will be silently ignored.Proposed fix to add response validation
export async function disconnectAllFromProject(env: Env, projectId: string): Promise<void> { const projectDoc = getProjectDocStub(env, projectId); - await projectDoc.fetch( + const response = await projectDoc.fetch( new Request('https://internal/disconnect-all', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Internal-Request': 'true', }, }), ); + + if (!response.ok) { + const text = await response.text(); + throw new Error( + `[ProjectSync] disconnect-all failed for project ${projectId}: ${response.status} ${text}`, + ); + } }packages/workers/src/commands/members/updateMemberRole.ts (1)
42-45: Wrap database update in try-catch with domain error.Per coding guidelines, database operations should be wrapped in try-catch blocks and return domain errors using
createDomainErrorwithSYSTEM_ERRORS.DB_ERROR. ThedeleteProject.tscommand in this PR demonstrates this pattern.♻️ Proposed fix
- await db - .update(projectMembers) - .set({ role }) - .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId))); + try { + await db + .update(projectMembers) + .set({ role }) + .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId))); + } catch (err) { + throw createDomainError( + SYSTEM_ERRORS.DB_ERROR, + { operation: 'update_member_role', projectId, userId, originalError: (err as Error).message }, + 'Failed to update member role', + ); + }You'll also need to add the import:
+import { createDomainError, SYSTEM_ERRORS } from '@corates/shared';
🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 45-46: The package.json scripts "fmt" and "fmt:check" reference
oxfmt but oxfmt is missing from devDependencies; add an entry for "oxfmt" under
devDependencies (matching the project's versioning style used for tools like
"prettier" and "eslint") so running the npm scripts works for all
developers—either add the dependency manually in package.json or run the
project’s preferred add-dev-dep command to install it as a devDependency.
In `@packages/docs/audits/billing-orgs-command-pattern.md`:
- Around line 145-160: Update the documented function signature for
processCheckoutSession to match the implementation by removing the first Env
parameter; change the declaration from processCheckoutSession(env, session, ctx)
to processCheckoutSession(session, ctx) and keep the existing
CheckoutSessionResult and ctx type (ctx: { db: Database; logger: Logger }) so
the docs reflect the actual function signature used in code.
In `@packages/docs/plans/shared-zod-api-types.md`:
- Around line 146-153: ProjectMemberSchema's role enum (in ProjectMemberSchema)
omits the 'owner' value used by the existing createProject.ts command; update
the z.enum in ProjectMemberSchema to include 'owner' (i.e.,
['owner','lead','reviewer','viewer']) to match current runtime values, or
alternatively add a clear migration note in the docs referencing
createProject.ts and the planned switch from 'owner' to 'lead' so consumers know
existing records contain 'owner'.
In `@packages/workers/src/commands/projects/updateProject.ts`:
- Line 45: Wrap the database update call in a try-catch inside updateProject
(the await db.update(projects).set(updateData).where(eq(projects.id, projectId))
line); on catch, convert the raw error into a domain error by calling
createDomainError with SYSTEM_ERRORS.DB_ERROR (include the caught error as
context) and rethrow that domain error so the function honors the documented
`@throws` DomainError DB_ERROR contract.
In `@packages/workers/src/policies/lib/roles.ts`:
- Around line 1-16: The ProjectRole union and PROJECT_ROLES array are missing
actual project roles used by the members endpoint, causing type mismatches in
project role checks (e.g., hasProjectRole); update the ProjectRole type to
include 'member' | 'collaborator' | 'viewer' | 'owner' and replace PROJECT_ROLES
with a readonly array containing those values in ascending order (member,
collaborator, viewer, owner) so runtime checks and TypeScript types align with
the members endpoint.
In `@packages/workers/src/routes/billing/checkout.ts`:
- Around line 247-249: The code uses an unnecessary double type assertion to
access the coupon: replace the cast "(promo as unknown as { coupon:
Stripe.Coupon }).coupon" with a direct property access on the PromotionCode
object; update the line that assigns coupon (currently using promo and
promoCodes) to simply use promo.coupon (or promoCodes.data[0].coupon) and remove
the extra type coercion to rely on Stripe's PromotionCode type.
🧹 Nitpick comments (22)
packages/workers/src/commands/lib/notifications.ts (2)
29-47: Consider adding theX-Internal-Requestheader for consistency.Other internal DO requests in this codebase (e.g.,
syncMemberToDO,disconnectAllFromProject) include theX-Internal-Request: trueheader. This endpoint should follow the same pattern for consistency and to enable the DO to distinguish internal from external requests.Proposed fix
await userSession.fetch( new Request('https://internal/notify', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'X-Internal-Request': 'true', + }, body: JSON.stringify({ ...notification, timestamp: Date.now(), }), }), );
54-76: Sequential notification processing - consider parallelization for scale.The current implementation awaits each notification sequentially in the loop. For large user lists, this could be slow. However, the per-user error handling is valuable.
Consider using
Promise.allSettledto parallelize while still handling individual failures:Optional parallel implementation
export async function notifyUsers( env: Env, userIds: string[], notification: Notification, excludeUserId: string | null = null, ): Promise<number> { - let notifiedCount = 0; - - for (const userId of userIds) { - if (excludeUserId && userId === excludeUserId) { - continue; - } - - try { - await notifyUser(env, userId, notification); - notifiedCount++; - } catch (err) { - console.error(`Failed to send notification to user ${userId}:`, err); - } - } - - return notifiedCount; + const filteredUserIds = userIds.filter(userId => userId !== excludeUserId); + + const results = await Promise.allSettled( + filteredUserIds.map(userId => notifyUser(env, userId, notification)) + ); + + let notifiedCount = 0; + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + notifiedCount++; + } else { + console.error(`Failed to send notification to user ${filteredUserIds[index]}:`, result.reason); + } + }); + + return notifiedCount; }packages/workers/src/commands/billing/processCheckoutSession.ts (4)
26-31: ExportProcessCheckoutSessionContextfor external consumers.The interface is defined but not exported, which may limit reusability if callers need to construct the context object with proper typing.
♻️ Suggested fix
-interface ProcessCheckoutSessionContext { +export interface ProcessCheckoutSessionContext { db: Database; logger: { stripe: (_event: string, _context: Record<string, unknown>) => void; }; }
128-139: Consider simplifying the timestamp handling.The type coercion for
expiresAtis defensive but adds complexity. SincegetGrantByOrgIdAndTypereturnsOrgAccessGrantwhich should have a consistent type forexpiresAt, this could indicate a schema type issue.#!/bin/bash # Check the OrgAccessGrant type definition to understand expiresAt type ast-grep --pattern 'interface OrgAccessGrant { $$$ }' # Also check the schema definition rg -n "expiresAt" packages/workers/src/db/schema
166-178: Wrap database operation in try-catch for consistent error handling.Per coding guidelines, database operations should be wrapped in try-catch blocks and return domain errors using
createDomainErrorwithSYSTEM_ERRORS.DB_ERROR. ThecreateGrantcall could fail and should be handled consistently.♻️ Suggested approach
+import { createDomainError, SYSTEM_ERRORS } from '@corates/shared'; // In the createGrant section: + try { const grantId = crypto.randomUUID(); await createGrant(db, { id: grantId, orgId, type: grantType as 'single_project' | 'trial', startsAt: now, expiresAt, stripeCheckoutSessionId: session.id, metadata: { purchaserUserId, stripeCheckoutSessionId: session.id, stripePaymentIntentId: session.payment_intent as string | undefined, }, }); + } catch (err) { + logger.stripe('checkout_grant_creation_failed', { + sessionId: session.id, + orgId, + error: err instanceof Error ? err.message : String(err), + }); + return { + handled: false, + result: 'grant_creation_failed', + error: 'database_error', + ledgerContext: { stripeCheckoutSessionId: session.id, orgId }, + }; + }Based on coding guidelines, database operations should use try-catch with domain errors.
139-139: WrapupdateGrantExpiresAtin try-catch for consistency.Similar to the grant creation, the update operation should also be wrapped for consistent error handling.
packages/workers/src/commands/projects/updateProject.ts (1)
28-31: Clarify the purpose of_actorparameter.The
_actorparameter is unused. If it's intended for future authorization checks, consider adding a TODO comment. If authorization is expected to be performed by middleware before this command is called, document this assumption.export async function updateProject( env: Env, - _actor: UpdateProjectActor, + _actor: UpdateProjectActor, // TODO: Use for audit logging or authorization if needed { projectId, name, description }: UpdateProjectParams, ): Promise<UpdateProjectResult> {packages/workers/src/commands/billing/createSingleProjectCheckout.ts (1)
66-70: Idempotency window may be too short for slow network conditions.The 1-minute granularity (
Math.floor(Date.now() / 60000)) prevents duplicate sessions from rapid clicks, but could allow duplicates if a user retries after exactly 1 minute. Consider if a 5-minute window would better suit your use case, or document this limitation.packages/workers/src/commands/projects/deleteProject.ts (1)
37-47: Consider usingdb.batch()for the two database reads.Per coding guidelines, use
db.batch()for multiple related database operations. While these are read operations, batching them improves atomicity and performance.♻️ Suggested refactor
- const project = await db - .select({ name: projects.name }) - .from(projects) - .where(eq(projects.id, projectId)) - .get(); - - const members = await db - .select({ userId: projectMembers.userId }) - .from(projectMembers) - .where(eq(projectMembers.projectId, projectId)) - .all(); + const [project, members] = await db.batch([ + db + .select({ name: projects.name }) + .from(projects) + .where(eq(projects.id, projectId)), + db + .select({ userId: projectMembers.userId }) + .from(projectMembers) + .where(eq(projectMembers.projectId, projectId)), + ]);packages/workers/src/commands/members/updateMemberRole.ts (1)
32-36: Consider documenting why_actoris unused.The
_actorparameter is accepted but not used. If this is intentional (e.g., for audit logging in the future or API consistency), a brief comment would clarify intent.packages/workers/src/policies/lib/roles.ts (1)
53-55: Edge case: empty hierarchy array.If
hierarchyis an empty array,hierarchy.length - 1equals-1, andindexOfwould return-1for any role, making the comparison truthy. Consider adding a guard or documenting that hierarchies must be non-empty.♻️ Defensive check
export function isTopRole<T extends string>(hierarchy: readonly T[], role: T | string): boolean { + if (hierarchy.length === 0) return false; return hierarchy.indexOf(role as T) === hierarchy.length - 1; }packages/workers/src/commands/members/addMember.ts (1)
89-116: Consider stronger typing for batch operations.The
unknown[]type with double assertion loses type safety. You could leverage Drizzle's batch type directly.♻️ Suggested improvement
- const insertOperations: unknown[] = []; + const insertOperations: Parameters<typeof db.batch>[0] = []; // ... push operations ... - await db.batch(insertOperations as unknown as Parameters<typeof db.batch>[0]); + await db.batch(insertOperations);This preserves type checking for the operations being pushed to the array.
packages/docs/plans/frontend-version-check.md (2)
25-36: Add fallback for non-git environments.The version generation script assumes git is available. Consider adding a fallback for environments where
git rev-parsemight fail (e.g., builds from source tarballs, CI without git).// Suggested enhancement const version = { hash: (() => { try { return execSync('git rev-parse --short HEAD').toString().trim(); } catch { return process.env.BUILD_HASH || 'unknown'; } })(), timestamp: Date.now(), };
219-237: Consider brief notification before force reload.The force update feature immediately reloads without user feedback. Consider showing a brief toast or message before reloading to avoid confusion (e.g., user might think the app crashed).
// Brief notification before reload if (forceUpdate === 'true') { // Show brief notification before reload alert('A critical update is being applied...'); window.location.reload(); return; }packages/workers/src/policies/projects.ts (1)
119-171: Consider extracting shared owner-counting logic.
canChangeRoleandcanRemoveWithoutOrphaningshare nearly identical implementation for checking if the target is the last owner. Consider extracting a helper likecountProjectOwners(db, projectId)to reduce duplication. Not blocking, as the current code is clear and correct.♻️ Optional refactor to reduce duplication
async function countProjectOwners(db: Database, projectId: string): Promise<number> { const result = await db .select({ count: count() }) .from(projectMembers) .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.role, 'owner'))) .get(); return result?.count || 0; }Then both functions can use:
return (await countProjectOwners(db, projectId)) > 1;packages/workers/src/routes/billing/index.js (1)
35-41: Inconsistent import style within the file.Only line 39 (
./checkout) uses an extensionless import while lines 35-38 and 40-41 retain.jsextensions. For consistency during the TypeScript migration, consider updating all imports in this file to the same style.♻️ Proposed fix for consistency
-import { billingSubscriptionRoutes } from './subscription.js'; -import { billingValidationRoutes } from './validation.js'; -import { billingPortalRoutes } from './portal.js'; -import { billingGrantRoutes } from './grants.js'; +import { billingSubscriptionRoutes } from './subscription'; +import { billingValidationRoutes } from './validation'; +import { billingPortalRoutes } from './portal'; +import { billingGrantRoutes } from './grants'; import { billingCheckoutRoutes } from './checkout'; -import { billingWebhookRoutes } from './webhooks.js'; -import { billingInvoicesRoutes } from './invoices.js'; +import { billingWebhookRoutes } from './webhooks'; +import { billingInvoicesRoutes } from './invoices';packages/docs/audits/billing-orgs-command-pattern.md (1)
99-122: Add language specifiers to fenced code blocks.The code blocks listing file paths should specify a language (e.g.,
textorplaintext) for consistency and to satisfy linting rules.📝 Suggested fix
-``` +```text commands/billing/ index.ts createSingleProjectCheckout.ts processCheckoutSession.ts policies/ billing.ts (new)Modified Files
-
+text
routes/billing/checkout.js -> checkout.ts
routes/billing/handlers/checkoutHandlers.js (import from commands)
policies/index.js -> index.ts (add billing exports)
commands/index.js -> index.ts (add billing exports)### Deleted Files -``` +```text routes/billing/helpers/ownerGate.js (moved to policies)</details> </blockquote></details> <details> <summary>packages/workers/src/policies/billing.ts (1)</summary><blockquote> `67-85`: **Use `isOrgOwner` helper for consistency.** The `isOrgOwner` helper is already imported (line 14) and used in `canManageBilling` (line 43), but `requireOrgOwner` uses a direct string comparison. This creates an inconsistency and potential maintenance issue if the owner role value ever changes. <details> <summary>Proposed fix</summary> ```diff export function requireOrgOwner({ orgId, role, }: { orgId: string | null; role: string | null; }): void { if (!orgId) { throw createDomainError(AUTH_ERRORS.FORBIDDEN, { reason: 'no_org_found', }); } - if (role !== 'owner') { + if (!role || !isOrgOwner(role)) { throw createDomainError(AUTH_ERRORS.FORBIDDEN, { reason: 'org_owner_required', }); } }packages/workers/src/routes/billing/checkout.ts (3)
35-40: Simplify Zod issue code detection.The current approach of checking
'code' in firstIssueand casting is overly complex. Zod 4 issues always have acodeproperty on the issue object.Proposed simplification
let message = firstIssue?.message || 'Validation failed'; - // Check for missing values using code and message inspection - const issueCode = 'code' in firstIssue ? firstIssue.code : ''; const isMissing = - issueCode === 'invalid_type' || + firstIssue?.code === 'invalid_type' || message.includes('received undefined') || message.includes('Required');
291-295: Session type casting loses type safety.Casting
sessiontoRecord<string, unknown>defeats TypeScript's type checking. Consider defining a proper type for the session object or using the session's actual type from Better-Auth.This pattern appears twice (lines 291-295 and 395-399). If the
resolveOrgIdWithRolefunction expects a specific session shape, consider:
- Updating
resolveOrgIdWithRoleto accept the actual session type from Better-Auth- Or extracting only the needed properties explicitly
const { orgId, role } = await resolveOrgIdWithRole({ db, - session: session as unknown as Record<string, unknown>, + session: { activeOrganizationId: session?.activeOrganizationId }, userId: user!.id, });
339-351: Fragile type assertion for Better Auth API.The cast
(auth.api as Record<string, CallableFunction>).upgradeSubscriptionbypasses type safety. If the Better Auth plugin API changes, this will fail silently at compile time.Consider creating a typed wrapper or interface for the Better Auth Stripe plugin methods to maintain type safety:
// In a separate types file or auth config interface BetterAuthStripeApi { upgradeSubscription: (params: { headers: Headers; body: { plan: string; annual: boolean; referenceId: string | null; successUrl: string; cancelUrl: string; returnUrl: string; }; }) => Promise<{ url: string }>; }packages/workers/src/commands/members/removeMember.ts (1)
56-62: Consider structured logging for consistency.The command uses
console.errorwhile route handlers use the structuredcreateLogger. For better observability and consistency across the codebase, consider passing a logger instance or creating one in the command.Example pattern
// Option 1: Pass logger as parameter export async function removeMember( env: Env, actor: RemoveMemberActor, params: RemoveMemberParams, logger?: Logger, ): Promise<RemoveMemberResult> { // ... } catch (err) { logger?.error('member_do_sync_failed', { projectId, userId, error: err }); } } // Option 2: Create simple logger in command import { createLogger } from '@/lib/observability/logger'; // Note: would need to adapt for non-request contextThis is optional since error logging for side-effects (DO sync, notifications) is non-critical.
| "fmt": "oxfmt", | ||
| "fmt:check": "oxfmt --check" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if oxfmt is listed in any package.json in the repo
rg -n '"oxfmt"' --type json
# Check if oxfmt is in devDependencies specifically
echo "=== Checking package.json devDependencies ==="
cat package.json | jq '.devDependencies' 2>/dev/null | head -50
# Check lock files for oxfmt
echo "=== Checking lock files for oxfmt ==="
if [ -f "package-lock.json" ]; then
rg -n '"oxfmt"' package-lock.json || echo "Not found in package-lock.json"
fi
if [ -f "yarn.lock" ]; then
rg -n 'oxfmt' yarn.lock || echo "Not found in yarn.lock"
fi
if [ -f "pnpm-lock.yaml" ]; then
rg -n 'oxfmt' pnpm-lock.yaml || echo "Not found in pnpm-lock.yaml"
fiRepository: InfinityBowman/corates
Length of output: 649
Add oxfmt to devDependencies.
The fmt and fmt:check scripts reference oxfmt, but it's not listed in devDependencies. This will cause npm script failures for developers unless oxfmt is globally installed. Following the pattern of other dev tools like prettier and eslint in the project, oxfmt should be added as a dev dependency.
🤖 Prompt for AI Agents
In `@package.json` around lines 45 - 46, The package.json scripts "fmt" and
"fmt:check" reference oxfmt but oxfmt is missing from devDependencies; add an
entry for "oxfmt" under devDependencies (matching the project's versioning style
used for tools like "prettier" and "eslint") so running the npm scripts works
for all developers—either add the dependency manually in package.json or run the
project’s preferred add-dev-dep command to install it as a devDependency.
| ### processCheckoutSession | ||
|
|
||
| ```typescript | ||
| interface CheckoutSessionResult { | ||
| handled: boolean; | ||
| result: string; | ||
| ledgerContext?: Record<string, unknown>; | ||
| error?: string; | ||
| } | ||
|
|
||
| export async function processCheckoutSession( | ||
| env: Env, | ||
| session: Stripe.Checkout.Session, | ||
| ctx: { db: Database; logger: Logger }, | ||
| ): Promise<CheckoutSessionResult>; | ||
| ``` |
There was a problem hiding this comment.
**Documentation signature doesn't match implementation.**The documented signature shows processCheckoutSession(env, session, ctx) with three parameters, but based on the relevant code snippet, the actual implementation is processCheckoutSession(session, ctx) with two parameters (no env). Update the documentation to match the implementation.
📝 Suggested fix
### processCheckoutSession
```typescript
interface CheckoutSessionResult {
handled: boolean;
result: string;
ledgerContext?: Record<string, unknown>;
error?: string;
}
export async function processCheckoutSession(
- env: Env,
session: Stripe.Checkout.Session,
ctx: { db: Database; logger: Logger },
): Promise<CheckoutSessionResult>;</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @packages/docs/audits/billing-orgs-command-pattern.md around lines 145 - 160,
Update the documented function signature for processCheckoutSession to match the
implementation by removing the first Env parameter; change the declaration from
processCheckoutSession(env, session, ctx) to processCheckoutSession(session,
ctx) and keep the existing CheckoutSessionResult and ctx type (ctx: { db:
Database; logger: Logger }) so the docs reflect the actual function signature
used in code.
</details>
<!-- fingerprinting:phantom:poseidon:ocelot -->
<!-- This is an auto-generated comment by CodeRabbit -->
| /** | ||
| * Role hierarchy definitions and comparison utilities | ||
| * | ||
| * Centralizes role definitions and provides utilities for comparing roles | ||
| * across the application. Used by authorization policies to check permissions. | ||
| */ | ||
|
|
||
| // Role type definitions | ||
| export type OrgRole = 'member' | 'admin' | 'owner'; | ||
| export type ProjectRole = 'member' | 'owner'; | ||
|
|
||
| // Organization role hierarchy (lowest to highest) | ||
| export const ORG_ROLES: readonly OrgRole[] = ['member', 'admin', 'owner']; | ||
|
|
||
| // Project role hierarchy (lowest to highest) | ||
| export const PROJECT_ROLES: readonly ProjectRole[] = ['member', 'owner']; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for collaborator and viewer role references in the codebase
echo "=== Searching for collaborator role references ==="
rg "collaborator" --type ts --type js -g '!node_modules/**' -C 2
echo ""
echo "=== Searching for viewer role references ==="
rg "viewer" --type ts --type js -g '!node_modules/**' -C 2Repository: InfinityBowman/corates
Length of output: 50379
🏁 Script executed:
#!/bin/bash
# Check members.ts file context to understand which roles it's managing
echo "=== Members route context ==="
grep -B5 -A10 "AddMemberRequest\|UpdateRoleRequest" packages/workers/src/routes/members.ts | head -50
echo ""
echo "=== Check if members.ts is for org members ==="
grep -B10 "owners/members\|routes/members" packages/workers/src/routes/members.ts | head -20
echo ""
echo "=== Check where ProjectRole is used ==="
rg "ProjectRole" --type ts -A3 -B3Repository: InfinityBowman/corates
Length of output: 12070
🏁 Script executed:
#!/bin/bash
# Verify members.ts is indeed for project members (not org)
echo "=== Check memberRoutes context ==="
head -20 packages/workers/src/routes/members.ts
echo ""
echo "=== Confirm projectMembershipMiddleware is for projects ==="
grep -r "projectMembershipMiddleware" packages/workers/src/middleware --type ts -B2Repository: InfinityBowman/corates
Length of output: 922
Fix PROJECT_ROLES to match actual project member roles in members endpoint.
The members.ts endpoint uses ['owner', 'collaborator', 'viewer'] for project roles, but roles.ts only defines ProjectRole = 'member' | 'owner', missing collaborator and viewer. This creates a type mismatch where hasProjectRole() won't properly validate roles accepted by the API.
Update the ProjectRole type and PROJECT_ROLES array to include all roles used in practice:
export type ProjectRole = 'member' | 'collaborator' | 'viewer' | 'owner';
export const PROJECT_ROLES: readonly ProjectRole[] = ['member', 'collaborator', 'viewer', 'owner'];
🤖 Prompt for AI Agents
In `@packages/workers/src/policies/lib/roles.ts` around lines 1 - 16, The
ProjectRole union and PROJECT_ROLES array are missing actual project roles used
by the members endpoint, causing type mismatches in project role checks (e.g.,
hasProjectRole); update the ProjectRole type to include 'member' |
'collaborator' | 'viewer' | 'owner' and replace PROJECT_ROLES with a readonly
array containing those values in ascending order (member, collaborator, viewer,
owner) so runtime checks and TypeScript types align with the members endpoint.
| const promo = promoCodes.data[0]; | ||
| const coupon = promo.coupon; | ||
| // Access coupon through the expand or direct access | ||
| const coupon = (promo as unknown as { coupon: Stripe.Coupon }).coupon; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Stripe API promotionCodes.list coupon property TypeScript types
💡 Result:
Short answer: promotionCodes.list returns PromotionCode objects whose coupon property is a Coupon object (Stripe.Coupon) in the official TypeScript types — not a plain ID string. See the Stripe API docs for the Promotion Code object and the TypeScript declaration in stripe-node. [1][2]
Sources:
- Stripe Promotion Code object (shows coupon embedded). [1]
@stripe/stripe-nodeTypeScript types (PromotionCode.coupon: Stripe.Coupon). [2]
🏁 Script executed:
cd packages/workers/src/routes/billing && head -260 checkout.ts | tail -30Repository: InfinityBowman/corates
Length of output: 1139
Remove unnecessary double type assertion; access coupon directly.
The Stripe PromotionCode type directly exposes coupon as Stripe.Coupon, so the double assertion is unnecessary. Simplify to const coupon = promo.coupon;
🤖 Prompt for AI Agents
In `@packages/workers/src/routes/billing/checkout.ts` around lines 247 - 249, The
code uses an unnecessary double type assertion to access the coupon: replace the
cast "(promo as unknown as { coupon: Stripe.Coupon }).coupon" with a direct
property access on the PromotionCode object; update the line that assigns coupon
(currently using promo and promoCodes) to simply use promo.coupon (or
promoCodes.data[0].coupon) and remove the extra type coercion to rely on
Stripe's PromotionCode type.
…tern' of https://github.com/InfinityBowman/corates into 297-finish-backend-typescript-migration-and-command-pattern
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (16)
packages/workers/src/routes/google-drive.ts (2)
148-158: Potential null dereference when returning access token.The condition at line 152 only checks token expiry but doesn't verify
tokens.accessTokenis truthy. IfaccessTokenisnullbutaccessTokenExpiresAtis set, the non-null assertion at line 153 will incorrectly returnnull.Proposed fix
- if (expiresAt && new Date(expiresAt).getTime() - now.getTime() > bufferTime) { + if (tokens.accessToken && expiresAt && new Date(expiresAt).getTime() - now.getTime() > bufferTime) { return tokens.accessToken!; } - if (!expiresAt || Number.isNaN(new Date(expiresAt).getTime())) { - if (tokens.accessToken) return tokens.accessToken; - } + // If expiry is unknown but we have an access token, try using it + if (tokens.accessToken && (!expiresAt || Number.isNaN(new Date(expiresAt).getTime()))) { + return tokens.accessToken; + }
498-528: Silent failure on database insert creates orphaned R2 objects.If the
mediaFilesinsert fails, the error is logged but the route still returns success. This leaves an orphaned file in R2 with no database record, and the client believes the import succeeded.Proposed fix — propagate DB error or clean up R2 object
// Insert into mediaFiles table const mediaFileId = crypto.randomUUID(); - try { - await db.insert(mediaFiles).values({ - id: mediaFileId, - filename: uniqueFileName, - originalName: originalFileName, - fileType: 'application/pdf', - fileSize: fileSize, - uploadedBy: user!.id, - bucketKey: r2Key, - orgId: project.orgId, - projectId, - studyId, - createdAt: new Date(), - }); - } catch (dbError) { - console.error('Failed to insert mediaFiles record after Google Drive import:', dbError); - } + await db.insert(mediaFiles).values({ + id: mediaFileId, + filename: uniqueFileName, + originalName: originalFileName, + fileType: 'application/pdf', + fileSize: fileSize, + uploadedBy: user!.id, + bucketKey: r2Key, + orgId: project.orgId, + projectId, + studyId, + createdAt: new Date(), + });The outer catch block (lines 529-540) will handle the error and return a proper error response. Alternatively, if you want to attempt cleanup, delete the R2 object in the catch block before re-throwing.
packages/workers/src/routes/billing/helpers/orgContext.ts (1)
26-44: Wrap DB lookups in domain-error handling.Both helpers call the database without try/catch, so a DB failure bubbles as a raw error instead of a domain error. Please wrap the lookup blocks and surface
SYSTEM_ERRORS.DB_ERRORviacreateDomainError.💡 Suggested fix
import type { DrizzleD1Database } from 'drizzle-orm/d1'; import type * as schema from '@/db/schema.js'; +import { createDomainError, SYSTEM_ERRORS } from '@corates/shared'; export async function resolveOrgId({ db, session, userId, }: ResolveOrgParams): Promise<string | null> { let orgId = session?.activeOrganizationId; // If no active org in session, get user's first org if (!orgId) { - const { member } = await import('@/db/schema.js'); - const { eq } = await import('drizzle-orm'); - const firstMembership = await db - .select({ organizationId: member.organizationId }) - .from(member) - .where(eq(member.userId, userId)) - .limit(1) - .get(); - orgId = firstMembership?.organizationId; + try { + const { member } = await import('@/db/schema.js'); + const { eq } = await import('drizzle-orm'); + const firstMembership = await db + .select({ organizationId: member.organizationId }) + .from(member) + .where(eq(member.userId, userId)) + .limit(1) + .get(); + orgId = firstMembership?.organizationId; + } catch { + throw createDomainError(SYSTEM_ERRORS.DB_ERROR); + } } return orgId || null; } export async function resolveOrgIdWithRole({ db, session, userId, }: ResolveOrgParams): Promise<OrgIdWithRole> { let orgId = session?.activeOrganizationId; let role: string | null = null; - if (!orgId) { - const { member } = await import('@/db/schema.js'); - const { eq } = await import('drizzle-orm'); - const firstMembership = await db - .select({ organizationId: member.organizationId, role: member.role }) - .from(member) - .where(eq(member.userId, userId)) - .limit(1) - .get(); - orgId = firstMembership?.organizationId; - role = firstMembership?.role || null; - } else { - // Get role for active org - const { member } = await import('@/db/schema.js'); - const { eq, and } = await import('drizzle-orm'); - const membership = await db - .select({ role: member.role }) - .from(member) - .where(and(eq(member.organizationId, orgId), eq(member.userId, userId))) - .get(); - role = membership?.role || null; - } + try { + if (!orgId) { + const { member } = await import('@/db/schema.js'); + const { eq } = await import('drizzle-orm'); + const firstMembership = await db + .select({ organizationId: member.organizationId, role: member.role }) + .from(member) + .where(eq(member.userId, userId)) + .limit(1) + .get(); + orgId = firstMembership?.organizationId; + role = firstMembership?.role || null; + } else { + // Get role for active org + const { member } = await import('@/db/schema.js'); + const { eq, and } = await import('drizzle-orm'); + const membership = await db + .select({ role: member.role }) + .from(member) + .where(and(eq(member.organizationId, orgId), eq(member.userId, userId))) + .get(); + role = membership?.role || null; + } + } catch { + throw createDomainError(SYSTEM_ERRORS.DB_ERROR); + } return { orgId: orgId || null, role }; }As per coding guidelines, wrap DB operations and return domain errors.
Also applies to: 52-80
packages/workers/src/routes/orgs/pdfs.ts (4)
372-432: Protect against concurrent filename collisions during upload.
Two parallel uploads can still select the same “available” name; with the current R2-first flow, a later DB insert failure can delete or overwrite another upload’s object. Consider reserving the filename in the DB first (retry on conflict) and only uploading to R2 after a successful insert, or store objects under unique IDs and keep filenames as metadata.
520-527: Fix middleware order to match the required auth chain.
requireProjectAccessshould run before org-level write/entitlement checks to keep authorization flow consistent.🔧 Proposed fix
@@ - const writeAccessResponse = await runMiddleware(requireOrgWriteAccess(), c); - if (writeAccessResponse) return writeAccessResponse; - - const projectAccessResponse = await runMiddleware(requireProjectAccess(), c); - if (projectAccessResponse) return projectAccessResponse; + const projectAccessResponse = await runMiddleware(requireProjectAccess(), c); + if (projectAccessResponse) return projectAccessResponse; + + const writeAccessResponse = await runMiddleware(requireOrgWriteAccess(), c); + if (writeAccessResponse) return writeAccessResponse; @@ - const writeAccessResponse = await runMiddleware(requireOrgWriteAccess(), c); - if (writeAccessResponse) return writeAccessResponse; - - const projectAccessResponse = await runMiddleware(requireProjectAccess(), c); - if (projectAccessResponse) return projectAccessResponse; + const projectAccessResponse = await runMiddleware(requireProjectAccess(), c); + if (projectAccessResponse) return projectAccessResponse; + + const writeAccessResponse = await runMiddleware(requireOrgWriteAccess(), c); + if (writeAccessResponse) return writeAccessResponse;As per coding guidelines, keep the chain in requireAuth → requireOrgMembership → requireProjectAccess → requireEntitlement → validateRequest order.
Also applies to: 793-798
557-609: Accept valid PDF content types with parameters.
Strict equality will reject common variants likeapplication/pdf; charset=binary.🔧 Proposed fix
- const contentType = c.req.header('Content-Type') || ''; + const contentType = (c.req.header('Content-Type') || '').toLowerCase(); @@ - } else if (contentType === 'application/pdf') { + } else if (contentType.startsWith('application/pdf')) {
732-742: Guard against decodeURIComponent throwing on malformed filenames.
Bad percent-encoding will currently throw and surface as a 500. Return a validation error instead.🔧 Proposed fix
@@ - const { fileName: rawFileName } = c.req.valid('param'); - const fileName = decodeURIComponent(rawFileName); + const { fileName: rawFileName } = c.req.valid('param'); + let fileName: string; + try { + fileName = decodeURIComponent(rawFileName); + } catch { + const error = createDomainError( + VALIDATION_ERRORS.FIELD_INVALID_FORMAT, + { field: 'fileName', value: rawFileName }, + 'Invalid file name encoding', + ); + return c.json(error, error.statusCode as ContentfulStatusCode); + } @@ - const { fileName: rawFileName } = c.req.valid('param'); - const fileName = decodeURIComponent(rawFileName); + const { fileName: rawFileName } = c.req.valid('param'); + let fileName: string; + try { + fileName = decodeURIComponent(rawFileName); + } catch { + const error = createDomainError( + VALIDATION_ERRORS.FIELD_INVALID_FORMAT, + { field: 'fileName', value: rawFileName }, + 'Invalid file name encoding', + ); + return c.json(error, error.statusCode as ContentfulStatusCode); + }Also applies to: 813-823
packages/workers/src/routes/orgs/members.ts (1)
612-669: Wrap pre-check failures with structured error handling.
requireMemberRemovalcallsgetProjectMembership, which performs a raw database query without error wrapping. If the database operation fails, it throws an unhandled error. The first try-catch rethrows this, bypassing the structured error contract and surfacing a 500. Wrap unexpected errors intoSYSTEM_ERRORS.DB_ERRORto match the pattern used in the second try-catch block forremoveMember.Proposed fix
try { await requireMemberRemoval(db, authUser.id, projectId, memberId); } catch (err) { if (isDomainError(err)) { return c.json(err, err.statusCode as ContentfulStatusCode); } const error = err as Error; console.error('Error validating member removal:', error); const dbError = createDomainError(SYSTEM_ERRORS.DB_ERROR, { operation: 'remove_project_member_precheck', originalError: error.message, }); return c.json(dbError, dbError.statusCode as ContentfulStatusCode); }packages/workers/src/routes/admin/projects.ts (1)
339-346: Guard pagination parsing against non-numeric and invalid inputs.
parseIntcan yieldNaN(from invalid strings like"abc") or0(from"0"), both of which propagate into invalidoffset,limit, andtotalPagescalculations. The current code only handlesundefinedvia the||fallback, leaving these edge cases unguarded.🛠️ Suggested guard for pagination parsing
- const page = parseInt(query.page || '1', 10); - const limit = Math.min(parseInt(query.limit || '20', 10), 100); + const pageRaw = Number.parseInt(query.page ?? '1', 10); + const limitRaw = Number.parseInt(query.limit ?? '20', 10); + const page = Number.isFinite(pageRaw) && pageRaw > 0 ? pageRaw : 1; + const limit = Math.min( + Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : 20, + 100, + );packages/workers/src/routes/billing/handlers/customerHandlers.ts (1)
75-89: Wrap database operations in try-catch blocks and return structured domain errors.Database operations in
handleCustomerUpdatedandhandleCustomerDeletedlack try-catch blocks, violating the coding guideline to wrap database operations and return domain errors usingcreateDomainError. If any database query fails, the exception propagates without structured error handling. Additionally, thedb.query.user.findFirstcalls should also be protected.♻️ Suggested error handling pattern
+ import { createDomainError, SYSTEM_ERRORS } from '@corates/shared'; ... export async function handleCustomerUpdated( customer: Stripe.Customer, ctx: WebhookContext, ): Promise<WebhookResult> { const db = getDb(ctx); const { logger } = ctx; const stripeCustomerId = customer.id; + try { const existingUser = await db.query.user.findFirst({ where: (user, { eq }) => eq(user.stripeCustomerId, stripeCustomerId), }); + } catch (error) { + return { + handled: true, + result: 'db_error', + error: createDomainError(SYSTEM_ERRORS.DB_ERROR).message, + }; + } if (hasChanges) { + try { await db .update(user) .set({ ...updates, updatedAt: new Date(), }) .where(eq(user.id, existingUser.id)); + } catch (error) { + return { + handled: true, + result: 'db_error', + error: createDomainError(SYSTEM_ERRORS.DB_ERROR).message, + }; + } ... } }packages/workers/src/routes/admin/stats.ts (1)
562-592: Use autoPagingToArray to fetch all subscription statuses and invoices, not just the first 100.Lines 570–575 and 616–622 limit results to 100 items per request. If there are more than 100 subscriptions in any status or 100 paid invoices, the counts and revenue totals will be significantly understated. Use
autoPagingToArray({ limit: 10000 })to fetch all results across pages.Suggested fix
const statusCounts = await Promise.all([ - stripe.subscriptions.search({ query: 'status:"active"', limit: 100 }), - stripe.subscriptions.search({ query: 'status:"trialing"', limit: 100 }), - stripe.subscriptions.search({ query: 'status:"past_due"', limit: 100 }), - stripe.subscriptions.search({ query: 'status:"canceled"', limit: 100 }), + stripe.subscriptions.search({ query: 'status:"active"', limit: 100 }).autoPagingToArray({ limit: 10000 }), + stripe.subscriptions.search({ query: 'status:"trialing"', limit: 100 }).autoPagingToArray({ limit: 10000 }), + stripe.subscriptions.search({ query: 'status:"past_due"', limit: 100 }).autoPagingToArray({ limit: 10000 }), + stripe.subscriptions.search({ query: 'status:"canceled"', limit: 100 }).autoPagingToArray({ limit: 10000 }), ]); // Similar fix for invoices at line 616: -const invoices = await stripe.invoices.list({ +const invoices = await stripe.invoices.list({ status: 'paid', created: { gte: Math.floor(startDate.getTime() / 1000), }, limit: 100, -}); +}).autoPagingToArray({ limit: 10000 });packages/workers/src/commands/projects/deleteProject.ts (1)
35-71: Batch related DB operations and wrap all DB errors.Project/members reads are outside the DB-error mapping and the related DB ops aren’t batched, which can leak raw errors and break the “all succeed or all fail” guideline.
As per coding guidelines, related DB operations should be batched and wrapped with domain errors.🔧 Proposed refactor
- const project = await db - .select({ name: projects.name }) - .from(projects) - .where(eq(projects.id, projectId)) - .get(); - - const members = await db - .select({ userId: projectMembers.userId }) - .from(projectMembers) - .where(eq(projectMembers.projectId, projectId)) - .all(); + let project: { name: string } | undefined; + let members: { userId: string }[]; + + try { + const [projectRows, memberRows, _deleteResult] = await db.batch([ + db.select({ name: projects.name }).from(projects).where(eq(projects.id, projectId)), + db + .select({ userId: projectMembers.userId }) + .from(projectMembers) + .where(eq(projectMembers.projectId, projectId)), + db.delete(projects).where(eq(projects.id, projectId)), + ]); + project = (projectRows as { name: string }[])[0]; + members = memberRows as { userId: string }[]; + } catch (err) { + throw createDomainError( + SYSTEM_ERRORS.DB_ERROR, + { operation: 'delete_project', projectId, originalError: (err as Error).message }, + 'Failed to delete project', + ); + } - - try { - await db.delete(projects).where(eq(projects.id, projectId)); - } catch (err) { - throw createDomainError( - SYSTEM_ERRORS.DB_ERROR, - { operation: 'delete_project', projectId, originalError: (err as Error).message }, - 'Failed to delete project', - ); - }packages/workers/src/routes/admin/stripe-tools.ts (1)
734-805: Usenullinstead of0for missing Stripe billing period timestamps.The Stripe API's
current_period_startandcurrent_period_endfields can be missing or null for subscriptions in certain states (incomplete, immediately canceled, or not yet invoiced). Defaulting to0misrepresents this as the Unix epoch instead of indicating missing data. Update the schema to use.nullable()and change the fallback tonull.Proposed fix
- currentPeriodStart: subWithPeriods.current_period_start ?? 0, - currentPeriodEnd: subWithPeriods.current_period_end ?? 0, + currentPeriodStart: subWithPeriods.current_period_start ?? null, + currentPeriodEnd: subWithPeriods.current_period_end ?? null,Update the response schema to allow null values for these fields.
packages/workers/src/routes/orgs/invitations.ts (1)
915-923: Default invitation roles to'member'to handle nullable schema columns.The
rolecolumn inprojectInvitationsis nullable (no.notNull()constraint), allowing legacy invites to havenullroles. This nullable value is then inserted directly intoprojectMembersat line 920 without safeguarding, potentially storing null instead of a valid role. Apply a consistent default across all role usages:🛠️ Suggested fix
+ const memberRole = invitation.role ?? 'member'; + const orgMemberRole = invitation.orgRole ?? 'member'; // Always add user to project (projects are invite-only) batchOps.push( db.insert(projectMembers).values({ id: crypto.randomUUID(), projectId: invitation.projectId, userId: authUser.id, - role: invitation.role, + role: memberRole, joinedAt: nowDate, }), );Also apply to line 996 (syncMemberToDO) and line 1012 (JSON response) for consistency.
Also applies to: 992-996, 1006-1013
packages/workers/src/index.ts (1)
316-321: Point legacy ProjectDoc clients to the new endpoint.Line 320 currently directs callers to
/api/orgs/:orgId/project-doc/:projectId, which this file also returns 410 for. That sends clients to a dead end; it should point to/api/project-doc/:projectId.🔧 Suggested fix
- message: 'This endpoint has been moved. Use /api/orgs/:orgId/project-doc/:projectId instead.', + message: 'This endpoint has been moved. Use /api/project-doc/:projectId instead.',packages/workers/src/routes/billing/subscription.ts (1)
232-257: CoerceprojectCountto a number for schema consistency.Line 256 returns
projectCountResult?.countdirectly without coercion, violating thez.number()response schema (line 41). The same file demonstrates the correct pattern in theusageRoutehandler (lines 188-189) where identical count queries are wrapped withNumber(). Apply the same pattern here for consistency:- projectCount: projectCountResult?.count || 0, + projectCount: Number(projectCountResult?.count ?? 0),
🤖 Fix all issues with AI agents
In @.cursor/rules/corates.mdc:
- Line 63: Update the rule text that currently references `@ark-ui/solid` to
instead reference the centralized wrapper package `@corates/ui`; specifically
replace the entry that reads something like "**Ark UI**: UI components
(`@ark-ui/solid`)" so it reads "**Ark UI**: UI components (from `@corates/ui`
package)" to reflect that the codebase imports Ark UI via the `@corates/ui`
wrapper rather than directly from `@ark-ui/solid`.
In @.github/copilot-instructions.md:
- Line 59: Update the Libraries entry for "Ark UI" in the critical rules section
so it no longer instructs developers to import from `@ark-ui/solid`; change the
description to indicate imports should come from the wrapper package `@corates/ui`
(e.g., replace the text "`@ark-ui/solid`" with "`import from `@corates/ui``" or
add a parenthetical note that `@ark-ui/solid` is the underlying technology but all
imports must use `@corates/ui`). Ensure the entry referencing "**Ark UI**" is the
only place modified.
In @.github/instructions/api-routes.instructions.md:
- Around line 56-64: Update the example to use the project's standardized domain
errors instead of freeform strings: replace notFound/forbidden/badRequest usage
with createDomainError and the relevant error constant enums (PROJECT_ERRORS,
AUTH_ERRORS, VALIDATION_ERRORS), and pass contextual metadata (e.g., { projectId
}, { orgId }, { field }) so client handling remains consistent with the rest of
the codebase and createDomainError contract.
In `@packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md`:
- Line 28: Several fenced code blocks in
packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md are
missing language specifiers; update the three-tick fences that wrap the
directory/listing snippets (specifically the blocks containing "AGENTS.md (Entry
Point)", ".github/copilot-instructions.md (GitHub Copilot - Always Applied)",
"plan/", "packages/docs/plans/", and "refs/") to include a language token such
as text or plaintext (e.g., change ``` to ```text) so linting and syntax
highlighting work correctly.
In `@packages/docs/plans/backend-typescript.md`:
- Around line 55-61: Update the Phase 1 list in the backend TypeScript plan to
mark the "shared validation hook (honoValidationHook)" item as completed: locate
the Phase 1 section listing "Extract shared validation hook to
/lib/honoValidationHook.ts" and change its status to "Done" (or add a
check/strike-through and a brief note referencing the implemented
honoValidationHook), so readers won't attempt to re-implement it; ensure the
other Phase 1 items (migrate index.js and docs.js to TypeScript) remain
unchanged.
In `@packages/docs/plans/shared-zod-api-types.md`:
- Around line 23-36: The fenced ASCII diagram in shared-zod-api-types.md is
missing a language specifier; update the opening fence for the diagram block
from ``` to ```text (or ```plaintext) so the diagram is treated as plain
text—locate the ASCII block showing the `@corates/shared` tree (the
triple-backtick section that begins with "@corates/shared" and change its fence
accordingly.
In `@packages/docs/STATUS.md`:
- Line 3: The STATUS.md document contains an outdated date string "**Last
Updated:** 2025-01-15"; update that literal to the correct date (e.g., "**Last
Updated:** 2026-01-15") in the STATUS.md file so the last-updated header
reflects the PR context; locate and replace the date line in
packages/docs/STATUS.md where the "**Last Updated:**" heading appears.
In `@packages/workers/src/commands/billing/processCheckoutSession.ts`:
- Around line 169-177: The metadata object currently casts grantType too broadly
and over-asserts payment_intent; change the grantType assertion in the object to
only 'single_project' (since earlier logic returns unless grantType ===
'single_project') and tighten stripePaymentIntentId to a plain string (or string
| null if you prefer) instead of 'string | undefined' because session.mode ===
'payment' guarantees an ID string; update the object where grantType and
stripePaymentIntentId are set (the metadata block in processCheckoutSession.ts
referencing grantType and session.payment_intent).
In `@packages/workers/src/commands/members/updateMemberRole.ts`:
- Around line 7-15: The DB write that updates the member role should be wrapped
in a try/catch that maps any unexpected DB failure to a DomainError with
SYSTEM_ERRORS.DB_ERROR while preserving existing DomainError throws for
LAST_OWNER; locate the update call using createDb() and projectMembers (the
write likely inside the function that calls requireSafeRoleChange and then
performs the DB update and subsequent sync/notify). Surround the db
update/execute call with try { ... } catch (err) { throw new
DomainError(SYSTEM_ERRORS.DB_ERROR, { cause: err }); } so existing checks that
throw the LAST_OWNER DomainError remain unchanged and only genuine DB exceptions
are remapped. Ensure the same pattern is applied to the other DB write in the
file that the reviewer flagged (the similar update block near the
syncMemberToDO/notifyUser flow).
In `@packages/workers/src/index.ts`:
- Around line 74-88: The OpenAPI exposure is currently unconditional but the
comment says "development only"; wrap the app.doc31('/openapi.json', ...)
registration in an environment check using c.env.ENVIRONMENT (e.g. only register
when ENVIRONMENT !== 'production') so the handler is enabled in
development/local but not in production; locate the app.doc31 call in
packages/workers/src/index.ts and guard it with the ENVIRONMENT check (or
alternatively update the comment to remove "development only" if you prefer
keeping it always-on).
In `@packages/workers/src/lib/honoValidationHook.ts`:
- Around line 26-47: The validationHook currently always constructs the error
with VALIDATION_ERRORS.FIELD_REQUIRED; update validationHook to choose the error
code based on the detected failure type: use FIELD_REQUIRED when isMissing is
true, map firstIssue.code (e.g. 'too_big' -> VALIDATION_ERRORS.FIELD_TOO_LONG,
'too_small' -> VALIDATION_ERRORS.FIELD_TOO_SHORT,
'invalid_string'/'invalid_format'/'invalid_type' ->
VALIDATION_ERRORS.FIELD_INVALID_FORMAT or VALIDATION_ERRORS.INVALID_INPUT) and
fall back to VALIDATION_ERRORS.INVALID_INPUT for unknown cases; then call
createValidationError(String(field), selectedErrorCode, null) and keep the
computed message (message or customized messages) before returning the error —
reference validationHook, firstIssue, issueReceived, isMissing,
createValidationError, and VALIDATION_ERRORS to locate and implement the logic.
In `@packages/workers/src/policies/billing.ts`:
- Around line 49-67: requireOrgOwner currently checks role with a raw string
comparison; change it to use the shared helper isOrgOwner so role-checking logic
remains consistent with canManageBilling (which uses
isOrgOwner(membership.role)). Update the requireOrgOwner implementation to call
isOrgOwner(role) and throw the same FORBIDDEN domain error when it returns
false, leaving the no-org check unchanged; reference requireOrgOwner,
canManageBilling, and isOrgOwner to locate and modify the function.
In `@packages/workers/src/routes/admin/users.js`:
- Around line 33-37: Update the ValidationResult interface used by
validationHook to include the missing target property so it matches the Hook
type from `@hono/zod-openapi`; change the interface referenced by validationHook
to include target (e.g., target: string or target: keyof ValidationTargets)
alongside success and optional error, leaving the validationHook return type
(Response | undefined) unchanged to maintain compatibility with the Hook
generic.
In `@packages/workers/src/routes/billing/handlers/dunning.ts`:
- Around line 192-200: The email HTML interpolates userName directly in the
template in dunning.ts; add a small HTML-escaping helper (e.g., escapeHtml(str:
string | null): string) that replaces &, <, > and " with their entities and use
it when rendering the greeting so the template uses escapeHtml(userName) ||
'there' instead of userName directly; update any other interpolations in this
template (like invoiceUrl or amount) if they can be user-controlled to use the
same helper or strict validation.
In `@packages/workers/src/routes/billing/handlers/invoiceHandlers.ts`:
- Around line 170-175: The code builds failureReason using
invoice.last_finalization_error?.message or
invoice.status_transitions?.finalized_at (a Unix timestamp) which yields
misleading numeric strings; change the fallback to a meaningful value by
removing the timestamp fallback and instead use something like invoice.status
or, if a date is desired, format finalized_at into an ISO string via new
Date(finalized_at * 1000).toISOString(); update the failureReason assignment
(variable name failureReason in invoiceHandlers.ts) to use
invoice.last_finalization_error?.message || invoice.status || 'unknown' (or the
formatted date approach) so the displayed reason is human-readable.
In `@packages/workers/src/routes/billing/webhookRouter.ts`:
- Around line 97-100: The router currently casts data.object to Stripe.Customer
for the 'customer.deleted' event; change the cast to Stripe.DeletedCustomer and
update the handler signature for handleCustomerDeleted to accept a
Stripe.DeletedCustomer (instead of Stripe.Customer). Locate
handleCustomerDeleted and its router invocation and change the parameter type to
Stripe.DeletedCustomer, and update any internal code in handleCustomerDeleted to
use only fields present on DeletedCustomer (id, object, deleted) to maintain
type correctness.
In `@packages/workers/src/routes/google-drive.ts`:
- Around line 360-377: The route currently only checks Google connection; add a
project access check by invoking requireProjectAccess before proceeding with the
import logic. After you parse { fileId, projectId, studyId } from
c.req.valid('json') (inside googleDriveRoutes.openapi(importRoute, async c => {
... })), call await requireProjectAccess(c, projectId) (or the equivalent
project-scoped middleware) and return its error if any, then continue with
getGoogleTokens/getValidAccessToken and the rest of the handler.
In `@README.md`:
- Line 56: The phrase "CoRATES specific" in the README line for "CoRATES MCP"
should be hyphenated as a compound adjective; update the text "- **CoRATES
MCP**: Custom MCP server for CoRATES specific AI tooling" to read "Custom MCP
server for CoRATES-specific AI tooling" so the compound modifier is
grammatically correct.
In `@scripts/loc-report.mjs`:
- Around line 38-42: The checkCommand function currently uses the Unix-only
'which' and a macOS-specific brew hint; make it cross-platform by selecting the
locator based on process.platform (use 'where' on Windows, 'which' or 'command
-v' on POSIX) or by using a Node package like 'which' to resolve executables,
and update the spawnSync call (refer to checkCommand and the existing spawnSync
invocation) to use the chosen locator; change the error text emitted via
console.error(c('yellow', ...)) to a neutral install hint (e.g., "<command> not
found. Install it via your system package manager or from
https://<command>.org") rather than referencing brew so Windows users aren't
misled.
♻️ Duplicate comments (2)
packages/workers/src/policies/lib/roles.ts (1)
8-16: Project role hierarchy is incomplete.
ProjectRole/PROJECT_ROLESomit roles used elsewhere (e.g., collaborator/viewer), which makeshasProjectRolereject valid roles.Based on learnings, project role hierarchy should include viewer and collaborator roles.🔧 Proposed fix
-export type ProjectRole = 'member' | 'owner'; +export type ProjectRole = 'viewer' | 'member' | 'collaborator' | 'owner'; // Project role hierarchy (lowest to highest) -export const PROJECT_ROLES: readonly ProjectRole[] = ['member', 'owner']; +export const PROJECT_ROLES: readonly ProjectRole[] = [ + 'viewer', + 'member', + 'collaborator', + 'owner', +];packages/workers/src/routes/billing/checkout.ts (1)
217-219: Unnecessary double type assertion on coupon property.The Stripe
PromotionCodetype directly exposescouponasStripe.Coupon. The double assertion(promo as unknown as { coupon: Stripe.Coupon }).couponis unnecessary.♻️ Simplify coupon access
const promo = promoCodes.data[0]; - // Access coupon through the expand or direct access - const coupon = (promo as unknown as { coupon: Stripe.Coupon }).coupon; + const coupon = promo.coupon;
| - **Drizzle ORM**: ALL database interactions and migrations | ||
| - **Better-Auth**: Authentication and user management | ||
| - **Ark UI**: UI components from `@corates/ui` package | ||
| - **Ark UI**: UI components (`@ark-ui/solid`) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
echo "=== Checking for `@corates/ui` references in source code ==="
rg -n '@corates/ui' packages/ --type ts --type tsx --type js --type jsx 2>/dev/null | head -20
echo ""
echo "=== Checking for `@ark-ui/solid` references in source code ==="
rg -n '@ark-ui/solid' packages/ --type ts --type tsx --type js --type jsx 2>/dev/null | head -20
echo ""
echo "=== Checking corates/ui imports ==="
rg "from ['\"]corates/ui" packages/ --type ts --type tsx --type js --type jsx 2>/dev/null | head -20
echo ""
echo "=== Checking package.json for ui package dependencies ==="
find packages -name 'package.json' -type f -exec sh -c 'echo "=== {} ===" && grep -E "@ark-ui|@corates/ui|corates/ui" "$1" || echo "No Ark UI references found"' _ {} \; 2>/dev/null
echo ""
echo "=== Checking if corates/ui package exists ==="
ls -la packages/ui 2>/dev/null || echo "No packages/ui directory found"Repository: InfinityBowman/corates
Length of output: 1301
🏁 Script executed:
#!/bin/bash
echo "=== Content of .cursor/rules/corates.mdc around line 63 ==="
cat -n .cursor/rules/corates.mdc | sed -n '55,75p'
echo ""
echo "=== Checking other rule files for Ark UI references ==="
rg -n 'Ark UI|@ark-ui|@corates/ui' .cursor/rules/ .github/
echo ""
echo "=== Checking git history or any comments about this change ==="
cat -n .cursor/rules/corates.mdc | head -75Repository: InfinityBowman/corates
Length of output: 5337
Update line 63 to reference the @corates/ui package wrapper, not @ark-ui/solid directly.
The rule file incorrectly directs developers to use @ark-ui/solid directly. However, the actual codebase structure uses @corates/ui as a centralized wrapper package that re-exports Ark UI components. All imports in the web package use @corates/ui, not @ark-ui/solid.
Line 63 should read: - **Ark UI**: UI components (from @corates/ui package)
This aligns with:
- The actual dependency structure (web imports
@corates/ui, which internally depends on@ark-ui/solid) - The established learnings that require using
@corates/uipackage - Current codebase usage patterns
🤖 Prompt for AI Agents
In @.cursor/rules/corates.mdc at line 63, Update the rule text that currently
references `@ark-ui/solid` to instead reference the centralized wrapper package
`@corates/ui`; specifically replace the entry that reads something like "**Ark
UI**: UI components (`@ark-ui/solid`)" so it reads "**Ark UI**: UI components
(from `@corates/ui` package)" to reflect that the codebase imports Ark UI via
the `@corates/ui` wrapper rather than directly from `@ark-ui/solid`.
| - **Drizzle ORM**: ALL database interactions and migrations | ||
| - **Better-Auth**: Authentication and user management | ||
| - **Ark UI**: UI components from `@corates/ui` package | ||
| - **Ark UI**: UI components (`@ark-ui/solid`) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if `@corates/ui` package exists
echo "=== Checking for corates/ui package ==="
fd -t f "package.json" packages/ui
# Check the contents of the ui package
if [ -d "packages/ui" ]; then
echo -e "\n=== corates/ui package.json ==="
cat packages/ui/package.json | head -20
fi
# Search for imports from corates/ui across the codebase
echo -e "\n=== Imports from corates/ui ==="
rg -n "from ['\"]corates/ui['\"]" --type ts --type tsx --type js --type jsx -C1 | head -50
# Search for imports from `@ark-ui/solid`
echo -e "\n=== Imports from `@ark-ui/solid` ==="
rg -n "from ['\"]@ark-ui/solid['\"]" --type ts --type tsx --type js --type jsx -C1 | head -50
# Check if there are any references to corates/ui in import paths
echo -e "\n=== Other corates/ui references ==="
rg "corates/ui" --type ts --type tsx --type js --type jsx --type json | head -30Repository: InfinityBowman/corates
Length of output: 876
🏁 Script executed:
#!/bin/bash
# Search for imports from corates/ui with broader search
echo "=== All imports from corates/ui ==="
rg "from ['\"]corates/ui['\"]" -C1
# Search for imports from `@ark-ui/solid`
echo -e "\n=== All imports from `@ark-ui/solid` ==="
rg "from ['\"]@ark-ui/solid['\"]" -C1
# Check what components the corates/ui package exports
echo -e "\n=== corates/ui index.ts ==="
cat packages/ui/src/index.ts
# Check the copilot instructions file context around line 59
echo -e "\n=== Full copilot-instructions.md context ==="
cat .github/copilot-instructions.md | head -80Repository: InfinityBowman/corates
Length of output: 4174
🏁 Script executed:
#!/bin/bash
# Check the exact content around line 59 to see if there's more context
echo "=== copilot-instructions.md lines 50-70 ==="
sed -n '50,70p' .github/copilot-instructions.md
# Verify what the corates/ui package actually exports
echo -e "\n=== corates/ui components structure ==="
find packages/ui/src/components -type f -name "*.ts" -o -name "*.tsx" | head -20
# Check if there are any example imports in docs
echo -e "\n=== Looking for import examples in documentation ==="
rg "import.*from.*corates" packages/docs/Repository: InfinityBowman/corates
Length of output: 3981
Fix misleading Ark UI library reference in critical rules section.
Line 59 lists the underlying library (@ark-ui/solid), but developers should import from the @corates/ui wrapper package instead. The project's actual documentation guides and established learnings consistently specify importing from @corates/ui.
Update the Libraries section to clarify the import source:
- Ark UI: UI components (import from
@corates/ui)
Or add a note that @ark-ui/solid is the underlying technology but the wrapper package should be used for all imports.
🤖 Prompt for AI Agents
In @.github/copilot-instructions.md at line 59, Update the Libraries entry for
"Ark UI" in the critical rules section so it no longer instructs developers to
import from `@ark-ui/solid`; change the description to indicate imports should
come from the wrapper package `@corates/ui` (e.g., replace the text
"`@ark-ui/solid`" with "`import from `@corates/ui``" or add a parenthetical note
that `@ark-ui/solid` is the underlying technology but all imports must use
`@corates/ui`). Ensure the entry referencing "**Ark UI**" is the only place
modified.
| Use `@corates/shared` error helpers: | ||
|
|
||
| ```js | ||
| import { notFound, forbidden, badRequest } from '@corates/shared'; | ||
|
|
||
| if (!project) throw notFound('Project not found'); | ||
| if (!hasAccess) throw forbidden('Access denied'); | ||
| if (!isValid) throw badRequest('Invalid input'); | ||
| ``` |
There was a problem hiding this comment.
Align error-handling guidance with createDomainError + constants.
This section currently demonstrates notFound/forbidden/badRequest with freeform strings; that can conflict with the project’s standardized error codes. Consider updating the example (or adding a note) to emphasize createDomainError + error constants to keep client handling consistent. As per coding guidelines, ...
📝 Suggested doc alignment
-Use `@corates/shared` error helpers:
+Use `@corates/shared` error helpers with standard error constants:
```js
-import { notFound, forbidden, badRequest } from '@corates/shared';
+import { createDomainError, PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS } from '@corates/shared';
-if (!project) throw notFound('Project not found');
-if (!hasAccess) throw forbidden('Access denied');
-if (!isValid) throw badRequest('Invalid input');
+if (!project) throw createDomainError(PROJECT_ERRORS.NOT_FOUND, { projectId });
+if (!hasAccess) throw createDomainError(AUTH_ERRORS.FORBIDDEN, { orgId });
+if (!isValid) throw createDomainError(VALIDATION_ERRORS.INVALID_INPUT, { field: '...' });
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @.github/instructions/api-routes.instructions.md around lines 56 - 64, Update
the example to use the project's standardized domain errors instead of freeform
strings: replace notFound/forbidden/badRequest usage with createDomainError and
the relevant error constant enums (PROJECT_ERRORS, AUTH_ERRORS,
VALIDATION_ERRORS), and pass contextual metadata (e.g., { projectId }, { orgId
}, { field }) so client handling remains consistent with the rest of the
codebase and createDomainError contract.
</details>
<!-- fingerprinting:phantom:poseidon:eagle -->
<!-- This is an auto-generated comment by CodeRabbit -->
|
|
||
| SolidType uses a **hierarchical documentation pyramid** with clear reading order: | ||
|
|
||
| ``` |
There was a problem hiding this comment.
Add language specifiers to fenced code blocks.
Multiple fenced code blocks are missing language specifiers, which affects markdown linting and may impact syntax highlighting. For directory structure representations, consider using text or plaintext as the language identifier.
📝 Proposed fixes for fenced code blocks
Line 28 - Add language specifier:
-```
+```text
AGENTS.md (Entry Point)Line 61 - Add language specifier:
-```
+```text
.github/copilot-instructions.md (GitHub Copilot - Always Applied)Line 101 - Add language specifier:
-```
+```text
plan/Line 157 - Add language specifier:
-```
+```text
packages/docs/plans/Line 328 - Add language specifier:
-```
+```text
refs/Also applies to: 61-61, 101-101, 157-157, 328-328
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
28-28: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In `@packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md` at
line 28, Several fenced code blocks in
packages/docs/audits/ai-agent-pipeline-comparison-solidtype-vs-corates.md are
missing language specifiers; update the three-tick fences that wrap the
directory/listing snippets (specifically the blocks containing "AGENTS.md (Entry
Point)", ".github/copilot-instructions.md (GitHub Copilot - Always Applied)",
"plan/", "packages/docs/plans/", and "refs/") to include a language token such
as text or plaintext (e.g., change ``` to ```text) so linting and syntax
highlighting work correctly.
| Recommended Priority Order | ||
|
|
||
| Phase 1: Foundation (First) | ||
|
|
||
| 1. Extract shared validation hook to /lib/honoValidationHook.ts | ||
| 2. Migrate index.js to TypeScript | ||
| 3. Migrate docs.js to TypeScript |
There was a problem hiding this comment.
Mark completed Phase 1 item to avoid confusion.
The shared validation hook appears implemented in this PR; consider annotating it as done in the plan so readers don’t re-scope it.
📝 Suggested doc tweak
-1. Extract shared validation hook to /lib/honoValidationHook.ts
+1. Extract shared validation hook to /lib/honoValidationHook.ts (completed in this PR)📝 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.
| Recommended Priority Order | |
| Phase 1: Foundation (First) | |
| 1. Extract shared validation hook to /lib/honoValidationHook.ts | |
| 2. Migrate index.js to TypeScript | |
| 3. Migrate docs.js to TypeScript | |
| Recommended Priority Order | |
| Phase 1: Foundation (First) | |
| 1. Extract shared validation hook to /lib/honoValidationHook.ts (completed in this PR) | |
| 2. Migrate index.js to TypeScript | |
| 3. Migrate docs.js to TypeScript |
🤖 Prompt for AI Agents
In `@packages/docs/plans/backend-typescript.md` around lines 55 - 61, Update the
Phase 1 list in the backend TypeScript plan to mark the "shared validation hook
(honoValidationHook)" item as completed: locate the Phase 1 section listing
"Extract shared validation hook to /lib/honoValidationHook.ts" and change its
status to "Done" (or add a check/strike-through and a brief note referencing the
implemented honoValidationHook), so readers won't attempt to re-implement it;
ensure the other Phase 1 items (migrate index.js and docs.js to TypeScript)
remain unchanged.
| const failureReason = | ||
| invoice.last_finalization_error?.message || | ||
| invoice.status_transitions?.finalized_at || | ||
| (invoice.status_transitions?.finalized_at ? | ||
| String(invoice.status_transitions.finalized_at) | ||
| : null) || | ||
| 'unknown'; |
There was a problem hiding this comment.
Questionable fallback for failureReason.
The fallback chain uses invoice.status_transitions?.finalized_at (a Unix timestamp) as an error reason string. This produces misleading failure reasons like "1705420800" instead of a meaningful message.
Consider using a more appropriate fallback or removing the finalized_at fallback entirely:
Suggested fix
const failureReason =
invoice.last_finalization_error?.message ||
- (invoice.status_transitions?.finalized_at ?
- String(invoice.status_transitions.finalized_at)
- : null) ||
'unknown';📝 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.
| const failureReason = | |
| invoice.last_finalization_error?.message || | |
| invoice.status_transitions?.finalized_at || | |
| (invoice.status_transitions?.finalized_at ? | |
| String(invoice.status_transitions.finalized_at) | |
| : null) || | |
| 'unknown'; | |
| const failureReason = | |
| invoice.last_finalization_error?.message || | |
| 'unknown'; |
🤖 Prompt for AI Agents
In `@packages/workers/src/routes/billing/handlers/invoiceHandlers.ts` around lines
170 - 175, The code builds failureReason using
invoice.last_finalization_error?.message or
invoice.status_transitions?.finalized_at (a Unix timestamp) which yields
misleading numeric strings; change the fallback to a meaningful value by
removing the timestamp fallback and instead use something like invoice.status
or, if a date is desired, format finalized_at into an ISO string via new
Date(finalized_at * 1000).toISOString(); update the failureReason assignment
(variable name failureReason in invoiceHandlers.ts) to use
invoice.last_finalization_error?.message || invoice.status || 'unknown' (or the
formatted date approach) so the displayed reason is human-readable.
| case 'customer.updated': | ||
| return handleCustomerUpdated(data.object, ctx); | ||
| return handleCustomerUpdated(data.object as Stripe.Customer, ctx); | ||
| case 'customer.deleted': | ||
| return handleCustomerDeleted(data.object, ctx); | ||
| return handleCustomerDeleted(data.object as unknown as Stripe.Customer, ctx); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /root/repo && find . -name "webhookRouter.ts" -o -name "*billing*" -type f | grep -E "\.(ts|tsx|js)$" | head -20Repository: InfinityBowman/corates
Length of output: 126
🏁 Script executed:
cd /root/repo && cat -n packages/workers/src/routes/billing/webhookRouter.ts | head -150Repository: InfinityBowman/corates
Length of output: 126
🏁 Script executed:
cd /root/repo && grep -n "handleCustomerDeleted" packages/workers/src/routes/billing/webhookRouter.ts -A 10Repository: InfinityBowman/corates
Length of output: 126
🏁 Script executed:
cd /root/repo && grep -n "Stripe\." packages/workers/src/routes/billing/webhookRouter.ts | head -20Repository: InfinityBowman/corates
Length of output: 126
🏁 Script executed:
cd /root/repo && find . -name "package.json" -path "*/workers/*" -exec grep -l "stripe" {} \;Repository: InfinityBowman/corates
Length of output: 126
🏁 Script executed:
git ls-files | grep -i "webhookrouter"Repository: InfinityBowman/corates
Length of output: 118
🏁 Script executed:
git ls-files | grep -E "routes/billing" | head -20Repository: InfinityBowman/corates
Length of output: 1297
🏁 Script executed:
cat packages/workers/src/routes/billing/webhookRouter.ts | head -150Repository: InfinityBowman/corates
Length of output: 4763
🏁 Script executed:
grep -n "handleCustomerDeleted" packages/workers/src/routes/billing/webhookRouter.ts -A 15 -B 5Repository: InfinityBowman/corates
Length of output: 1384
🏁 Script executed:
grep -n "Stripe\." packages/workers/src/routes/billing/webhookRouter.ts | head -30Repository: InfinityBowman/corates
Length of output: 1347
🏁 Script executed:
cat packages/workers/src/routes/billing/handlers/customerHandlers.tsRepository: InfinityBowman/corates
Length of output: 4239
🏁 Script executed:
grep -n "handleCustomerDeleted" packages/workers/src/routes/billing/handlers/customerHandlers.ts -A 20Repository: InfinityBowman/corates
Length of output: 695
🏁 Script executed:
grep -rn "DeletedCustomer" packages/workers/src --include="*.ts" --include="*.js"Repository: InfinityBowman/corates
Length of output: 180
🏁 Script executed:
grep -n "stripe" packages/workers/package.jsonRepository: InfinityBowman/corates
Length of output: 197
🏁 Script executed:
cat packages/workers/src/routes/admin/stripe-tools.ts | sed -n '425,435p'Repository: InfinityBowman/corates
Length of output: 446
🏁 Script executed:
python3 << 'EOF'
import json
# Check Stripe types for customer.deleted event
# According to Stripe docs, customer.deleted sends a DeletedCustomer object
# which has: id, object: 'customer', deleted: true
# It does NOT include fields like email, name, metadata, etc.
payload_structure = {
"customer.updated": "Stripe.Customer (full customer object)",
"customer.deleted": "Stripe.DeletedCustomer (minimal object with id, object, deleted)"
}
print("Stripe Webhook Payload Structures:")
print(json.dumps(payload_structure, indent=2))
print("\nThe handler only accesses customer.id, but the type should be accurate.")
EOFRepository: InfinityBowman/corates
Length of output: 152
🏁 Script executed:
grep -n "customer" packages/workers/src/routes/billing/handlers/types.tsRepository: InfinityBowman/corates
Length of output: 48
🏁 Script executed:
git log --oneline -5 packages/workers/src/routes/billing/webhookRouter.tsRepository: InfinityBowman/corates
Length of output: 99
🏁 Script executed:
grep -rn "Stripe.DeletedCustomer" packages/workers/src --include="*.ts"Repository: InfinityBowman/corates
Length of output: 180
🏁 Script executed:
grep -B5 -A5 "customer.id" packages/workers/src/routes/billing/handlers/customerHandlers.ts | head -20Repository: InfinityBowman/corates
Length of output: 585
Update handleCustomerDeleted to accept Stripe.DeletedCustomer for type correctness.
Stripe sends a DeletedCustomer shape for customer.deleted webhooks, which contains only id, object, and deleted fields. The current cast to Stripe.Customer misrepresents the payload type. Update both the handler parameter type and the cast in the router:
Suggested fix
// In webhookRouter.ts
- case 'customer.deleted':
- return handleCustomerDeleted(data.object as unknown as Stripe.Customer, ctx);
+ case 'customer.deleted':
+ return handleCustomerDeleted(data.object as Stripe.DeletedCustomer, ctx);
// In customerHandlers.ts
-export async function handleCustomerDeleted(
- customer: Stripe.Customer,
+export async function handleCustomerDeleted(
+ customer: Stripe.DeletedCustomer,🤖 Prompt for AI Agents
In `@packages/workers/src/routes/billing/webhookRouter.ts` around lines 97 - 100,
The router currently casts data.object to Stripe.Customer for the
'customer.deleted' event; change the cast to Stripe.DeletedCustomer and update
the handler signature for handleCustomerDeleted to accept a
Stripe.DeletedCustomer (instead of Stripe.Customer). Locate
handleCustomerDeleted and its router invocation and change the parameter type to
Stripe.DeletedCustomer, and update any internal code in handleCustomerDeleted to
use only fields present on DeletedCustomer (id, object, deleted) to maintain
type correctness.
| - **Linting**: ESLint with custom CoRATES rules | ||
| - **Code Format**: Prettier | ||
| - **Type Safety**: TypeScript with jsconfig.json path aliases | ||
| - **CoRATES MCP**: Custom MCP server for CoRATES specific AI tooling |
There was a problem hiding this comment.
Minor grammar fix: add hyphen to compound adjective.
Per static analysis, "CoRATES specific" should be hyphenated when used as a compound adjective.
📝 Suggested fix
-- **CoRATES MCP**: Custom MCP server for CoRATES specific AI tooling
+- **CoRATES MCP**: Custom MCP server for CoRATES-specific AI tooling📝 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.
| - **CoRATES MCP**: Custom MCP server for CoRATES specific AI tooling | |
| - **CoRATES MCP**: Custom MCP server for CoRATES-specific AI tooling |
🧰 Tools
🪛 LanguageTool
[grammar] ~56-~56: Use a hyphen to join words.
Context: ...TES MCP**: Custom MCP server for CoRATES specific AI tooling - AI Compatible:...
(QB_NEW_EN_HYPHEN)
🤖 Prompt for AI Agents
In `@README.md` at line 56, The phrase "CoRATES specific" in the README line for
"CoRATES MCP" should be hyphenated as a compound adjective; update the text "-
**CoRATES MCP**: Custom MCP server for CoRATES specific AI tooling" to read
"Custom MCP server for CoRATES-specific AI tooling" so the compound modifier is
grammatically correct.
| function checkCommand(command) { | ||
| const result = spawnSync('which', [command], { encoding: 'utf8' }); | ||
| if (result.status !== 0) { | ||
| if (command === 'cloc') { | ||
| console.error(`cloc not found in PATH. Install it: https://github.com/AlDanial/cloc`); | ||
| } else { | ||
| console.error(`${command} not found in PATH`); | ||
| } | ||
| console.error(c('yellow', `${command} not found. Install: brew install ${command}`)); | ||
| process.exit(2); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n scripts/loc-report.mjs | head -60Repository: InfinityBowman/corates
Length of output: 2072
🏁 Script executed:
# Check what platform-specific code patterns exist in this repo
rg 'process\.platform' scripts/ || echo "No process.platform checks found"Repository: InfinityBowman/corates
Length of output: 98
🏁 Script executed:
# Look for any cross-platform command execution patterns
rg 'spawnSync.*which|spawnSync.*where' scripts/ -A 2 -B 2Repository: InfinityBowman/corates
Length of output: 403
🏁 Script executed:
# Check Node.js version to understand available APIs
cat package.json | grep -A 5 '"engines"' || echo "No engines specified"Repository: InfinityBowman/corates
Length of output: 170
🏁 Script executed:
# Check GitHub Actions workflows for platform matrix
find .github/workflows -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -5Repository: InfinityBowman/corates
Length of output: 96
🏁 Script executed:
# Check if any workflows run on Windows
rg 'windows|win32|windows-latest' .github/workflows/ 2>/dev/null || echo "No Windows-specific workflows found"Repository: InfinityBowman/corates
Length of output: 101
🏁 Script executed:
# Check README or docs for platform requirements
cat README.md 2>/dev/null | head -100 || echo "No README found"Repository: InfinityBowman/corates
Length of output: 3640
🏁 Script executed:
cat package.json | grep -A 30 '"scripts"'Repository: InfinityBowman/corates
Length of output: 1556
Make command lookup portable across OSes.
which isn't available on Windows, and the brew-only hint is Mac-specific. This script is exposed as pnpm loc in package.json for local developer use, so Windows contributors will encounter this issue. Consider a cross‑platform locator and a neutral install hint.
🔧 Suggested fix
function checkCommand(command) {
- const result = spawnSync('which', [command], { encoding: 'utf8' });
+ const locator = process.platform === 'win32' ? 'where' : 'which';
+ const result = spawnSync(locator, [command], { encoding: 'utf8' });
if (result.status !== 0) {
- console.error(c('yellow', `${command} not found. Install: brew install ${command}`));
+ console.error(
+ c('yellow', `${command} not found. Install it and ensure it is on PATH.`),
+ );
process.exit(2);
}
}🤖 Prompt for AI Agents
In `@scripts/loc-report.mjs` around lines 38 - 42, The checkCommand function
currently uses the Unix-only 'which' and a macOS-specific brew hint; make it
cross-platform by selecting the locator based on process.platform (use 'where'
on Windows, 'which' or 'command -v' on POSIX) or by using a Node package like
'which' to resolve executables, and update the spawnSync call (refer to
checkCommand and the existing spawnSync invocation) to use the chosen locator;
change the error text emitted via console.error(c('yellow', ...)) to a neutral
install hint (e.g., "<command> not found. Install it via your system package
manager or from https://<command>.org") rather than referencing brew so Windows
users aren't misled.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/workers/src/routes/admin/users.ts (1)
896-931: Keep impersonation userId consistent between path and body.The handler blocks self‑impersonation using the path param, but the downstream request uses
body.userId. A mismatched body can bypass the self-check and create audit ambiguity. Validate they match (or use the path param exclusively).🔧 Proposed fix
const { userId } = c.req.valid('param'); const adminUser = c.get('user') as AuthUser; const body = c.req.valid('json'); try { + if (body.userId !== userId) { + const error = createValidationError( + 'userId', + VALIDATION_ERRORS.INVALID_INPUT.code, + body.userId, + 'mismatched_user_id', + ); + return c.json(error, error.statusCode as ContentfulStatusCode); + } + // Don't allow impersonating yourself if (adminUser.id === userId) { const error = createValidationError( 'userId', VALIDATION_ERRORS.INVALID_INPUT.code, userId, 'cannot_impersonate_self', ); return c.json(error, error.statusCode as ContentfulStatusCode); } @@ const authRequest = new Request(authUrl.toString(), { method: 'POST', headers, - body: JSON.stringify({ userId: body.userId }), + body: JSON.stringify({ userId }), });
🧹 Nitpick comments (5)
packages/workers/src/routes/admin/billing-observability.ts (2)
393-395: VerifyprocessedAtguard doesn't skip valid events.The guard skips events where
processedAtis null or cannot be parsed. Since this targetsLedgerStatus.PROCESSEDevents (line 386),processedAtshould always be set. However, if data integrity issues exist, legitimate stuck states could be missed silently.Consider logging a warning when a PROCESSED event has null
processedAt:Optional: Add logging for data integrity issues
: (event.processedAt as number | null); - if (!processedAtTimestamp) continue; + if (!processedAtTimestamp) { + console.warn('PROCESSED event with null processedAt:', { ledgerId: event.id, stripeEventId: event.stripeEventId }); + continue; + }
658-666: Consider extracting reduce accumulator pattern to a utility.The same
reducepattern with typed accumulator appears here and in the ledger route (lines 739-756). If this pattern is common, a small helper could reduce duplication.This is a minor observation—current code is correct and readable.
packages/workers/src/routes/admin/billing.ts (2)
26-29: Consider importingDatabasetype from@/db/client.jsinstead of redefining.The
Databasetype is already exported frompackages/workers/src/db/client.ts(see relevant code snippets). Redefining it here creates potential drift if the source definition changes.Import the existing type
-import type { DrizzleD1Database } from 'drizzle-orm/d1'; -import type * as schema from '@/db/schema.js'; +import { createDb, type Database } from '@/db/client.js'; -type Database = DrizzleD1Database<typeof schema>;Note: You'd also need to remove the duplicate
createDbimport on line 9.
796-806: Consider typingupdateDatamore strictly.Using
Record<string, unknown>loses type safety. Drizzle can infer the correct type from the schema. A partial type would be safer.Optional: Use Drizzle's inferred type
- const updateData: Record<string, unknown> = { + const updateData: Partial<typeof subscription.$inferInsert> = { updatedAt: new Date(), };This ensures only valid subscription fields can be assigned.
packages/workers/src/routes/admin/database.ts (1)
477-533: Consider moving interfaces to module level.The
ColumnInfoandDrizzleColumninterfaces are defined inside the route handler. Moving them to module level would improve readability and allow reuse.Move interfaces to module level
+interface ColumnInfo { + name: string; + type: string; + notNull: boolean; + primaryKey: boolean; + unique: boolean; + hasDefault: boolean; + foreignKey?: { + table: string; + column: string; + }; +} + +interface DrizzleColumn { + dataType?: string; + notNull?: boolean; + primary?: boolean; + isUnique?: boolean; + hasDefault?: boolean; + config?: { + references?: () => { table?: unknown; name?: string }; + }; +} + const databaseRoutes = new OpenAPIHono<{ Bindings: Env }>({Then remove the inline definitions from lines 477-499.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/workers/src/routes/admin/billing-observability.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)
**/*.{js,jsx,ts,tsx}: Use theuseProjecthook for managing Yjs connections with reference counting instead of creating Y.Doc instances directly
Never create Y.Doc instances directly; always use the connection registry managed by useProject
Access Y.Doc connection state via projectStore.getConnectionState(projectId) instead of checking connection state directly
Read Yjs-synced data from projectStore (using getStudies, getChecklist, etc.) rather than accessing Y.Doc maps/arrays directly
Use operation functions from useProject or projectActionsStore for writing data instead of directly modifying Y.Doc
Don't store Y.Doc references in component state; always retrieve the connection through useProject
Handle connection cleanup via onCleanup() in Solid.js components or equivalent cleanup patterns, calling disconnect() when the component unmounts
**/*.{js,jsx,ts,tsx}: For UI icons, usesolid-iconslibrary or SVGs only (never emojis)
Prefer modern ES6+ syntax and features
Use import aliases from jsconfig.json (see ui-components.mdc)
Group related components in subdirectories with barrel exports
Use Ark UI components from@corates/uipackage, NOT local components
Usesolid-iconsicon library (e.g.,solid-icons/bi,solid-icons/fi) for icons
Comments should explain WHY something is being done, not narrate what the code does
Use comments to explain why a particular approach or workaround was chosen
Use comments to clarify intent when code could be misread or misunderstood
Use comments to provide context from external systems, specs, or requirements
Use comments to document assumptions, edge cases, or limitations
Do NOT narrate what the code is doing in comments
Do NOT duplicate function or variable names in plain English in comments
Do NOT leave stale comments that contradict the code
Do NOT reference removed or obsolete code paths in comments
**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Comments should not repeat what the code is saying. Rese...
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
**/*.{js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
Ensure browser compatibility (Safari is usually problematic)
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
packages/workers/**/*.{js,ts}: Use Zod for schema and input validation on the backend
Use Drizzle ORM for ALL database interactions and migrations
Use Better-Auth for authentication and user management
All project routes are org-scoped - use/api/orgs/:orgId/projects/...pattern on backend
Backend uses orgId (UUID) for all API operations
UserequireOrgMembershipandrequireProjectAccessmiddleware for auth
packages/workers/**/*.{js,ts}: Always usecreateDomainErrorfrom@corates/sharedfor backend error handling instead of manually creating error objects
Use predefined error constants from@corates/shared(PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
packages/workers/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
ALWAYS use
db.batch()for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
packages/workers/src/routes/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
packages/workers/src/routes/**/*.{js,ts,jsx,tsx}: ALWAYS validate request bodies usingvalidateRequestmiddleware from the validation config rather than manual validation
UsevalidateQueryParamsmiddleware for validating query parameters in routes
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
packages/workers/src/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.{js,ts}: Use Zod for schema and input validation (backend)
Use Drizzle ORM for ALL database interactions and migrations
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
packages/{workers,web}/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
packages/{web,workers}/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Never throw string literals; always throw Error objects or return domain errors from API routes
Files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
🧠 Learnings (22)
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/{web,workers}/**/*.{js,ts,jsx,tsx} : Never throw string literals; always throw Error objects or return domain errors from API routes
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-06T18:29:08.745Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-06T18:29:08.745Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Drizzle ORM for ALL database interactions and migrations
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Drizzle ORM for ALL database interactions and migrations
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Use error constants from `corates/shared` (`PROJECT_ERRORS.*`, `AUTH_ERRORS.*`, `VALIDATION_ERRORS.*`, `SYSTEM_ERRORS.*`, `USER_ERRORS.*`) instead of creating error objects manually
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Zod for schema and input validation on the backend
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-06T18:29:08.745Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-06T18:29:08.745Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Zod for schema and input validation (backend)
Applied to files:
packages/workers/src/routes/admin/database.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/**/*.{js,ts,jsx,tsx} : ALWAYS validate request bodies using `validateRequest` middleware from the validation config rather than manual validation
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Applied to files:
packages/workers/src/routes/admin/database.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/**/*.{js,ts,jsx,tsx} : Use `validateQueryParams` middleware for validating query parameters in routes
Applied to files:
packages/workers/src/routes/admin/database.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Always create DB client from environment using `createDb` function in route handlers
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : ALWAYS use `createDomainError` from `corates/shared` for error handling in API routes
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Always use Drizzle ORM with query builders - never use raw SQL in database operations
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Apply authentication and authorization middleware in correct order: Authentication → Organization membership → Project access → Authorization → Validation → Route handler
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.js : Use organization-scoped route patterns with path structure `/api/orgs/:orgId/...` and leverage `requireOrgMembership`, `requireProjectAccess`, `getOrgContext`, and `getProjectContext` middleware
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : All project routes are org-scoped - use `/api/orgs/:orgId/projects/...` pattern on backend
Applied to files:
packages/workers/src/routes/admin/database.tspackages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `getOrgContext(c)` to retrieve the validated orgId from context instead of manual checks - orgId is guaranteed valid by middleware
Applied to files:
packages/workers/src/routes/admin/billing.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-06T18:29:08.745Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-06T18:29:08.745Z
Learning: Applies to packages/{workers,web}/src/**/*.{js,ts,jsx,tsx} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/routes/admin/users.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware instead of manual membership checks for org-scoped routes
Applied to files:
packages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/config/validation.{js,ts} : Add new Zod validation schemas to `config/validation.js` and reuse `commonFields` when possible to avoid duplication
Applied to files:
packages/workers/src/routes/admin/users.tspackages/workers/src/routes/admin/billing-observability.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use predefined error constants from `corates/shared` (PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Applied to files:
packages/workers/src/routes/admin/billing-observability.ts
🧬 Code graph analysis (3)
packages/workers/src/routes/admin/database.ts (3)
packages/workers/src/lib/honoValidationHook.ts (1)
validationHook(26-51)packages/workers/src/db/schema.ts (1)
mediaFiles(146-162)packages/workers/src/db/client.ts (1)
createDb(7-9)
packages/workers/src/routes/admin/billing.ts (5)
packages/workers/src/db/client.ts (1)
Database(5-5)packages/workers/src/lib/honoValidationHook.ts (1)
validationHook(26-51)packages/workers/src/lib/notify.ts (1)
notifyOrgMembers(67-105).claude/skills/api-route/examples/ExampleRoutes.js (1)
updateData(302-302)packages/workers/src/db/orgAccessGrants.ts (2)
revokeGrant(109-120)updateGrantExpiresAt(92-107)
packages/workers/src/routes/admin/users.ts (3)
packages/workers/src/lib/honoValidationHook.ts (1)
validationHook(26-51)packages/workers/src/db/client.ts (1)
createDb(7-9)packages/workers/src/db/schema.ts (1)
session(68-84)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: corates
- GitHub Check: Workers Builds: corates-workers-prod
🔇 Additional comments (15)
packages/workers/src/routes/admin/users.ts (1)
34-39: Schema and route setup tightening looks good.Constraining record keys to strings and centralizing the validation hook reduce ambiguous JSON shapes and improve response consistency.
Also applies to: 128-136, 163-169, 380-381
packages/workers/src/routes/admin/billing-observability.ts (5)
6-19: LGTM! Clean OpenAPIHono setup with centralized validation.The typed bindings
OpenAPIHono<{ Bindings: Env }>and centralizedvalidationHookalign well with the pattern used across other admin routes.
265-298: Well-structured TypeScript interfaces for stuck state detection.The
StuckStateandStuckOrginterfaces provide good type safety and align with the corresponding Zod schemas. The optional fields correctly mirror the schema definitions.
341-346: Good defensive guard for nullablecreatedAt.The
continueguard at line 341 prevents processing subscriptions with missingcreatedAt. This is a sensible safeguard.
516-522: Good Stripe error handling with explicit typing.Renaming the catch variable to
stripeErrand casting toErrorimproves clarity and avoids shadowing the outererrorreference pattern used elsewhere.
710-734: Clean conditional query construction.The ternary pattern for building queries with/without conditions is clear and avoids mutating query builders. This is a good approach for maintaining query immutability.
packages/workers/src/routes/admin/billing.ts (4)
35-78: Well-typed notification helper with proper async handling.The
SubscriptionNotificationDatainterface andnotifySubscriptionChangefunction provide good type safety. ThewaitUntilpattern correctly handles async notifications without blocking responses.
209-217: Schema change:metadatanow accepts any JSON object.Changing from a stricter type to
z.record(z.string(), z.unknown())provides flexibility but loses type safety on metadata contents. This is acceptable for admin endpoints where flexibility is often needed.
1169-1176: Good use ofas constfor literal action types.The
'extended' as constand'created' as constassertions ensure the response types align with theGrantSuccessResponseSchemawhich expectsz.enum(['created', 'extended']).
963-970: Verify metadata serialization handling in createGrant function.The concern about metadata type mismatch between the JSON object being passed and
GrantSchema'sz.string().nullable()definition requires code inspection. Confirm whethercreateGrantor Drizzle ORM handles JSON serialization automatically, or if explicitJSON.stringify(body.metadata)is needed.packages/workers/src/routes/admin/database.ts (5)
21-39: Good use ofas constfor type-safe table allowlist.Deriving
AllowedTableNamefrom theALLOWED_TABLESarray ensures compile-time safety when checking table access. This is a solid pattern.
590-594: Filter injection is mitigated by allowlist but consider explicit validation.The
filterBycolumn is checked againsttableRecord[filterBy]which implicitly validates it exists. However, an explicit check against known column names would be more defensive, especially sincefilterValueis used directly in the query.Since this is an admin-only endpoint with table allowlisting, the risk is low.
732-763: Query builder pattern is correct but verbose.The pattern of building a base query and conditionally applying
.where()is correct. The approach wherewhereis applied afterorderBy/limit/offsetworks because Drizzle's query builder is immutable.
856-862: Good defensive filtering for nullable userId.The
.filter(row => row.userId)ensures only rows with actual user associations are included. The cast tostringis safe after this filter.
6-18: Consistent setup with other admin routes.The OpenAPIHono initialization with typed
Envbindings and centralizedvalidationHookmatches the pattern in billing.ts and billing-observability.ts, ensuring consistency across the admin routes.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/workers/src/routes/google-drive.ts (1)
511-527: Silent DB failure may cause orphaned R2 files and misleading success response.If the
mediaFilesinsert fails, the file remains in R2 but isn't tracked in the database. The request still returnssuccess: true, misleading the user into thinking the import succeeded. Consider either failing the request or cleaning up the R2 file on DB error.🛠️ Suggested fix
try { await db.insert(mediaFiles).values({ id: mediaFileId, filename: uniqueFileName, originalName: originalFileName, fileType: 'application/pdf', fileSize: fileSize, uploadedBy: user!.id, bucketKey: r2Key, orgId: project.orgId, projectId, studyId, createdAt: new Date(), }); } catch (dbError) { console.error('Failed to insert mediaFiles record after Google Drive import:', dbError); + // Clean up orphaned R2 file + try { + await c.env.PDF_BUCKET.delete(r2Key); + } catch (cleanupError) { + console.error('Failed to clean up R2 file after DB error:', cleanupError); + } + const systemError = createDomainError(SYSTEM_ERRORS.DB_ERROR, { + operation: 'insert_media_file_record', + originalError: dbError instanceof Error ? dbError.message : String(dbError), + }); + return c.json(systemError, systemError.statusCode as ContentfulStatusCode); }packages/workers/src/index.ts (1)
241-276: Use domain errors here to avoid leaking upstream/internal details.
Returning upstreamstatusTextanderror.messagecan expose internal or third‑party details, and it bypasses standardized error handling. PrefercreateDomainErrorwithSYSTEM_ERRORS.*and map to a safe status (e.g., 502/500) while logging the underlying error server‑side. As per coding guidelines, usecreateDomainErrorand predefined error constants.
🤖 Fix all issues with AI agents
In @.claude/skills/code-review/SKILL.md:
- Around line 147-185: The markdown code block is malformed around the snippet
containing "8. No-issue outcome" and "Explicit prohibitions": end the opening
```md block before the separator (the line with the horizontal separator
characters), change "8. No-issue outcome" to a proper heading (e.g., "### 8.
No-issue outcome"), wrap the “## Code review / No issues found…” example in its
own fenced ```md block, replace the separator with a proper markdown horizontal
rule (---), and add a heading "## Explicit prohibitions" before the bulleted
list; locate these edits by searching for the exact strings "8. No-issue
outcome", "Explicit prohibitions", and the opening/closing backticks in the
problematic diff to fix the structure.
In `@packages/workers/src/commands/members/updateMemberRole.ts`:
- Around line 43-53: The update currently calls db.update(projectMembers).set({
role })... but never checks the result so it can silently succeed with 0
affected rows; change the logic in updateMemberRole to capture the update result
and assert that at least one row was affected (e.g., check result.rowsAffected
or the Drizzle/D1 equivalent) and if zero, throw a domain error (use
createDomainError with SYSTEM_ERRORS.DB_ERROR or a more specific NOT_FOUND error
and operation: 'update_member_role') so callers know the member did not exist;
keep the existing catch that maps DB errors via createDomainError for other
failures.
🧹 Nitpick comments (2)
packages/workers/src/lib/honoValidationHook.ts (1)
59-79: LGTM!Error code selection now correctly maps Zod error types to appropriate validation error codes. The fallback to
INVALID_INPUThandles unknown cases gracefully.Optional enhancement: The
targetfield (e.g.,'body','query','params') from the validation result is currently unused. Consider incorporating it into error messages for better debugging context:- error.message = message; + error.message = result.target !== 'json' + ? `${result.target} validation failed: ${message}` + : message;packages/workers/src/index.ts (1)
165-170: Centralize pdf-proxy input validation via Zod + validateRequest.
Manual JSON parsing andurlchecks bypass the shared validation hook and URL-format validation. Prefer a Zod schema andvalidateRequestto keep request validation consistent. As per coding guidelines, use Zod for backend validation and the shared validation middleware.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
.claude/skills/code-review/SKILL.mdpackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/index.tspackages/workers/src/lib/honoValidationHook.tspackages/workers/src/policies/billing.tspackages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/routes/google-drive.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/workers/src/policies/billing.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)
**/*.{js,jsx,ts,tsx}: Use theuseProjecthook for managing Yjs connections with reference counting instead of creating Y.Doc instances directly
Never create Y.Doc instances directly; always use the connection registry managed by useProject
Access Y.Doc connection state via projectStore.getConnectionState(projectId) instead of checking connection state directly
Read Yjs-synced data from projectStore (using getStudies, getChecklist, etc.) rather than accessing Y.Doc maps/arrays directly
Use operation functions from useProject or projectActionsStore for writing data instead of directly modifying Y.Doc
Don't store Y.Doc references in component state; always retrieve the connection through useProject
Handle connection cleanup via onCleanup() in Solid.js components or equivalent cleanup patterns, calling disconnect() when the component unmounts
**/*.{js,jsx,ts,tsx}: For UI icons, usesolid-iconslibrary or SVGs only (never emojis)
Prefer modern ES6+ syntax and features
Use import aliases from jsconfig.json (see ui-components.mdc)
Group related components in subdirectories with barrel exports
Use Ark UI components from@corates/uipackage, NOT local components
Usesolid-iconsicon library (e.g.,solid-icons/bi,solid-icons/fi) for icons
Comments should explain WHY something is being done, not narrate what the code does
Use comments to explain why a particular approach or workaround was chosen
Use comments to clarify intent when code could be misread or misunderstood
Use comments to provide context from external systems, specs, or requirements
Use comments to document assumptions, edge cases, or limitations
Do NOT narrate what the code is doing in comments
Do NOT duplicate function or variable names in plain English in comments
Do NOT leave stale comments that contradict the code
Do NOT reference removed or obsolete code paths in comments
**/*.{js,jsx,ts,tsx}: Prefer modern ES6+ syntax and features
Comments should not repeat what the code is saying. Rese...
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
packages/workers/src/routes/**/*.js
📄 CodeRabbit inference engine (.cursor/rules/api-routes.mdc)
packages/workers/src/routes/**/*.js: ALWAYS usevalidateRequestmiddleware for request body validation in API routes
UsevalidateQueryParamsmiddleware for query string validation in API routes
Always create DB client from environment usingcreateDbfunction in route handlers
Usedb.batch()for related database operations that must be atomic
Always use Drizzle ORM with query builders - never use raw SQL in database operations
ALWAYS usecreateDomainErrorfrom@corates/sharedfor error handling in API routes
Use error constants from@corates/shared(PROJECT_ERRORS.*,AUTH_ERRORS.*,VALIDATION_ERRORS.*,SYSTEM_ERRORS.*,USER_ERRORS.*) instead of creating error objects manually
Apply authentication and authorization middleware in correct order: Authentication → Organization membership → Project access → Authorization → Validation → Route handler
Files:
packages/workers/src/routes/__tests__/orgs-management.test.js
**/*.{js,jsx,ts,tsx,css,scss}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
Ensure browser compatibility (Safari is usually problematic)
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
packages/workers/**/*.{js,ts}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
packages/workers/**/*.{js,ts}: Use Zod for schema and input validation on the backend
Use Drizzle ORM for ALL database interactions and migrations
Use Better-Auth for authentication and user management
All project routes are org-scoped - use/api/orgs/:orgId/projects/...pattern on backend
Backend uses orgId (UUID) for all API operations
UserequireOrgMembershipandrequireProjectAccessmiddleware for auth
packages/workers/**/*.{js,ts}: Always usecreateDomainErrorfrom@corates/sharedfor backend error handling instead of manually creating error objects
Use predefined error constants from@corates/shared(PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS) instead of arbitrary error strings
Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
packages/workers/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
ALWAYS use
db.batch()for multiple related database operations in Drizzle to ensure atomicity - all operations must succeed or all fail together
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
packages/workers/src/routes/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
packages/workers/src/routes/**/*.{js,ts,jsx,tsx}: ALWAYS validate request bodies usingvalidateRequestmiddleware from the validation config rather than manual validation
UsevalidateQueryParamsmiddleware for validating query parameters in routes
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/routes/google-drive.ts
{packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js}
📄 CodeRabbit inference engine (.cursor/rules/workers-testing.mdc)
{packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js}: Tests in packages/workers must run under@cloudflare/vitest-pool-workerswith global setup from packages/workers/src/tests/setup.js
Use resetTestDatabase() helper to reset D1 from migration SQL before each test or describe block
Seed test data using provided seed* helpers (seedUser, seedOrganization, seedOrgMember, seedProject, etc.) from packages/workers/src/tests/helpers.js with Zod schema validation
Clear ProjectDoc Durable Objects storage between tests using clearProjectDOs() helper to prevent invalidation/reset edge cases
For integration tests, fetch against real worker env using app.fetch(req, env, ctx) with env from cloudflare:test, or use fetchApp() helper
Use shared json() helper from packages/workers/src/tests/helpers.js to safely parse response bodies in tests
Mock src/middleware/auth.js requireAuth function in route tests by setting user/session via x-test-user-id and x-test-user-email headers
Reuse provided helpers from packages/workers/src/tests/helpers.js (resetTestDatabase, clearProjectDOs, seedUser, seedOrganization, createTestEnv, fetchApp, json, createAuthHeaders) instead of implementing ad-hoc solutions
Only re-mock Postmark and Stripe modules inside a test file if you need behavior different from the global mock setup in packages/workers/src/tests/setup.js
Files:
packages/workers/src/routes/__tests__/orgs-management.test.js
packages/workers/src/**/*.{js,ts}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/workers/src/**/*.{js,ts}: Use Zod for schema and input validation (backend)
Use Drizzle ORM for ALL database interactions and migrations
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
packages/{workers,web}/src/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use Better-Auth for authentication and user management
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
packages/{web,workers}/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
Never throw string literals; always throw Error objects or return domain errors from API routes
Files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.tspackages/workers/src/lib/honoValidationHook.ts
🧠 Learnings (50)
📓 Common learnings
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-06T18:29:08.745Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Zod for schema and input validation (backend)
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Zod for schema and input validation on the backend
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.js : Use organization-scoped route patterns with path structure `/api/orgs/:orgId/...` and leverage `requireOrgMembership`, `requireProjectAccess`, `getOrgContext`, and `getProjectContext` middleware
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `getOrgContext(c)` to retrieve the validated orgId from context instead of manual checks - orgId is guaranteed valid by middleware
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx} : Use `requireOrgMembership` and `requireProjectAccess` middleware instead of manual membership checks for org-scoped routes
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Reuse provided helpers from packages/workers/src/__tests__/helpers.js (resetTestDatabase, clearProjectDOs, seedUser, seedOrganization, createTestEnv, fetchApp, json, createAuthHeaders) instead of implementing ad-hoc solutions
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.js
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Mock src/middleware/auth.js requireAuth function in route tests by setting user/session via x-test-user-id and x-test-user-email headers
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.js
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Seed test data using provided seed* helpers (seedUser, seedOrganization, seedOrgMember, seedProject, etc.) from packages/workers/src/__tests__/helpers.js with Zod schema validation
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.js
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : All project routes are org-scoped - use `/api/orgs/:orgId/projects/...` pattern on backend
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Use UUID for backend API endpoints: /api/orgs/:orgId/...
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.js
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Use error constants from `corates/shared` (`PROJECT_ERRORS.*`, `AUTH_ERRORS.*`, `VALIDATION_ERRORS.*`, `SYSTEM_ERRORS.*`, `USER_ERRORS.*`) instead of creating error objects manually
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/index.tspackages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Import and use requireOrgMembership middleware to verify org membership and optionally enforce minimum role requirements
Applied to files:
packages/workers/src/routes/__tests__/orgs-management.test.jspackages/workers/src/routes/google-drive.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Handle WebSocket upgrade requests by checking for 'Upgrade: websocket' header in ProjectDoc fetch method
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Use y-websocket protocol with message type 1 for awareness (presence) messages in ProjectDoc
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-06T18:29:08.745Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-06T18:29:08.745Z
Learning: Applies to packages/{workers,web}/src/**/*.{js,ts,jsx,tsx} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/index.tspackages/workers/src/routes/google-drive.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Use y-websocket protocol with message type 0 for Yjs sync messages in ProjectDoc
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Verify authentication before WebSocket upgrade in ProjectDoc
Applied to files:
packages/workers/src/index.tspackages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Better-Auth for authentication and user management
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Do not expose internal sync endpoints (/sync, /sync-member, /sync-pdf, /disconnect-all) publicly without X-Internal-Request verification
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use comments to document assumptions, edge cases, or limitations
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use comments to explain why a particular approach or workaround was chosen
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Do NOT reference removed or obsolete code paths in comments
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : Apply authentication and authorization middleware in correct order: Authentication → Organization membership → Project access → Authorization → Validation → Route handler
Applied to files:
packages/workers/src/index.tspackages/workers/src/routes/google-drive.ts
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use `downloadPdf` from `api/pdf-api.js` to download PDFs from server, then cache the result using `cachePdf`
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Structure ProjectDoc Y.Doc with meta (Map), members (Map), and reviews (Map) containing checklists (Map), pdfs (Array), and reconciliation (Map)
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Close WebSocket connection with code 1008 when user access to project is denied
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use PDF operations from `useProject` hook instead of bypassing through direct API calls
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:02:50.087Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/yjs-sync.mdc:0-0
Timestamp: 2025-12-27T03:02:50.087Z
Learning: Applies to packages/workers/src/durable-objects/ProjectDoc.js, packages/web/src/primitives/useProject/** : Y.Doc structure in ProjectDoc follows a specific hierarchy: meta (Map), members (Map), reviews (Map containing studies with checklists, pdfs, and reconciliation)
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:02:50.087Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/yjs-sync.mdc:0-0
Timestamp: 2025-12-27T03:02:50.087Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Never create Y.Doc instances directly; always use the connection registry managed by useProject
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-05T01:00:58.113Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers-testing.mdc:0-0
Timestamp: 2026-01-05T01:00:58.113Z
Learning: Applies to {packages/workers/src/**/*.test.js,packages/workers/src/**/__tests__/**/*.js} : Clear ProjectDoc Durable Objects storage between tests using clearProjectDOs() helper to prevent invalidation/reset edge cases
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/ProjectDoc.{js,ts} : Check user membership in project before allowing WebSocket connection in ProjectDoc
Applied to files:
packages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Track active WebSocket sessions in a Map with session structure { user, awarenessClientId }
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2025-12-27T03:01:35.601Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/durable-objects.mdc:0-0
Timestamp: 2025-12-27T03:01:35.601Z
Learning: Applies to packages/workers/src/durable-objects/**/*.{js,ts} : Remove WebSocket sessions from tracking on disconnect and perform cleanup
Applied to files:
packages/workers/src/index.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : ALWAYS use `createDomainError` from `corates/shared` for error handling in API routes
Applied to files:
packages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/{web,workers}/**/*.{js,ts,jsx,tsx} : Never throw string literals; always throw Error objects or return domain errors from API routes
Applied to files:
packages/workers/src/index.tspackages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Always use `createDomainError` from `corates/shared` for backend error handling instead of manually creating error objects
Applied to files:
packages/workers/src/index.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Use `importFromGoogleDrive` from `api/google-drive.js` for importing PDFs from Google Drive. Pass fileId, projectId, studyId, and tag
Applied to files:
packages/workers/src/routes/google-drive.ts
📚 Learning: 2025-12-27T03:02:05.951Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/pdf-handling.mdc:0-0
Timestamp: 2025-12-27T03:02:05.951Z
Learning: Applies to packages/web/src/**/*.{js,jsx,ts,tsx} : Save form state using `saveFormState` from `@/lib/formStatePersistence.js` before triggering OAuth redirects for Google Drive
Applied to files:
packages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use `requireOrgMembership` and `requireProjectAccess` middleware for auth
Applied to files:
packages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Use requireProjectAccess middleware after requireOrgMembership to verify project membership and optionally enforce minimum role requirements
Applied to files:
packages/workers/src/routes/google-drive.tspackages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Enforce middleware chain order: requireAuth → requireOrgMembership → requireProjectAccess → requireEntitlement → validateRequest
Applied to files:
packages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/routes/**/*.js : ALWAYS use `validateRequest` middleware for request body validation in API routes
Applied to files:
packages/workers/src/routes/google-drive.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/routes/**/*.{js,ts,jsx,tsx} : ALWAYS validate request bodies using `validateRequest` middleware from the validation config rather than manual validation
Applied to files:
packages/workers/src/routes/google-drive.tspackages/workers/src/lib/honoValidationHook.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Ensure project invitation flow creates org membership before project membership, including orgRole and projectRole in invitation data
Applied to files:
packages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-01T23:32:17.698Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/organizations.mdc:0-0
Timestamp: 2026-01-01T23:32:17.698Z
Learning: Applies to packages/workers/src/routes/orgs/** : Use project role hierarchy: owner > collaborator > member > viewer
Applied to files:
packages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Wrap database operations in try-catch blocks and return domain errors using createDomainError with SYSTEM_ERRORS.DB_ERROR
Applied to files:
packages/workers/src/commands/members/updateMemberRole.ts
📚 Learning: 2026-01-01T23:32:23.488Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/workers.mdc:0-0
Timestamp: 2026-01-01T23:32:23.488Z
Learning: Applies to packages/workers/src/config/validation.{js,ts} : Add new Zod validation schemas to `config/validation.js` and reuse `commonFields` when possible to avoid duplication
Applied to files:
packages/workers/src/lib/honoValidationHook.ts
📚 Learning: 2026-01-06T18:29:08.745Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-01-06T18:29:08.745Z
Learning: Applies to packages/workers/src/**/*.{js,ts} : Use Zod for schema and input validation (backend)
Applied to files:
packages/workers/src/lib/honoValidationHook.ts
📚 Learning: 2026-01-01T23:32:06.095Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/corates.mdc:0-0
Timestamp: 2026-01-01T23:32:06.095Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use Zod for schema and input validation on the backend
Applied to files:
packages/workers/src/lib/honoValidationHook.ts
📚 Learning: 2026-01-01T23:31:43.756Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/api-routes.mdc:0-0
Timestamp: 2026-01-01T23:31:43.756Z
Learning: Applies to packages/workers/src/config/validation.js : Add new validation schemas to `src/config/validation.js` and reuse `commonFields` when possible
Applied to files:
packages/workers/src/lib/honoValidationHook.ts
📚 Learning: 2026-01-06T23:56:57.354Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/error-handling.mdc:0-0
Timestamp: 2026-01-06T23:56:57.354Z
Learning: Applies to packages/workers/**/*.{js,ts} : Use validation middleware (validateRequest) for request validation instead of manually validating request bodies in routes
Applied to files:
packages/workers/src/lib/honoValidationHook.ts
🧬 Code graph analysis (3)
packages/workers/src/index.ts (1)
packages/workers/src/docs.ts (1)
getDocsHtml(3-160)
packages/workers/src/commands/members/updateMemberRole.ts (4)
packages/workers/src/db/client.ts (1)
createDb(7-9)packages/workers/src/db/schema.ts (1)
projectMembers(134-144)packages/workers/src/commands/lib/doSync.ts (1)
syncMemberToDO(12-12)packages/workers/src/commands/lib/notifications.ts (2)
notifyUser(29-47)NotificationTypes(12-17)
packages/workers/src/lib/honoValidationHook.ts (1)
packages/workers/src/config/validation.ts (1)
ValidationResult(230-234)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Workers Builds: corates
- GitHub Check: Workers Builds: corates-workers-prod
🔇 Additional comments (19)
packages/workers/src/routes/__tests__/orgs-management.test.js (1)
490-491: Reasonable workaround for enum validation variability.The regex approach handles Zod's behavior where missing enum fields can be reported as either missing or invalid. However, this introduces inconsistency with other validation tests in the same file (lines 188 and 410) that use exact
toBe('VALIDATION_FIELD_REQUIRED')assertions.Consider whether the underlying Zod schema could be configured to report missing enum fields consistently, or alternatively, apply the same regex pattern to other similar tests for uniformity.
packages/workers/src/lib/honoValidationHook.ts (3)
1-12: LGTM!Module documentation is clear, and imports are well-organized using type-only imports where appropriate. Aligns with coding guidelines for using Zod and
@corates/sharederror utilities.
14-18: LGTM!Interface correctly models the Hono/OpenAPIHono validation hook result contract with
success,error, andtargetfields.
31-57: LGTM!Robust handling of Zod validation errors:
- Optional chaining safely handles edge cases (empty issues array)
- Cross-version compatibility for detecting
undefinedvalues (actual vs string representation)- Comprehensive missing-field detection covering
invalid_type,invalid_enum_value, and message patternspackages/workers/src/routes/google-drive.ts (10)
6-28: LGTM!The imports are well-organized, and the centralized
validationHookreplaces the inlined defaultHook appropriately. UsingEnvtype bindings and applyingrequireAuthglobally is consistent with the codebase patterns.
69-76: LGTM!The
detailsfield typing update toz.record(z.string(), z.unknown())properly constrains the error payload structure while maintaining flexibility for various error contexts.
78-102: LGTM!The
GoogleTokensinterface andgetGoogleTokensfunction are well-typed. The function properly handles the case where no Google account exists by returningundefined.
107-138: LGTM!The function is well-typed with proper error handling using
createDomainError. The type assertion on line 133 for the Google OAuth response is acceptable for external API responses.
143-182: LGTM!The token refresh logic is sound. The non-null assertion on line 154 is safe since callers verify
tokens?.accessTokenis truthy before invoking this function. The edge case handling for invalidexpiresAtvalues (lines 157-159) is appropriate defensive coding.
200-210: LGTM!The status route correctly returns the connection status. The
user!assertion is safe due to therequireAuthmiddleware applied globally.
232-277: LGTM!The picker token route properly handles token retrieval with comprehensive error handling. The
@ts-expect-errorannotation for OpenAPIHono strict return types is an acceptable workaround, andContentfulStatusCodecasting maintains type consistency.
295-315: LGTM!The disconnect route correctly removes the Google account association with proper error handling using
SYSTEM_ERRORS.DB_ERRORper guidelines.
367-375: LGTM!The project access check using
requireProjectEditproperly validates user permissions before proceeding with the import. Domain errors are correctly surfaced to the client.
529-551: LGTM!The success response is well-structured with proper
as constannotations for literal types, and the outer catch block correctly handles and surfaces domain errors.packages/workers/src/index.ts (1)
39-39: Typed OpenAPIHono bindings look solid.
Improves compile-time safety for env bindings without changing runtime behavior.packages/workers/src/commands/members/updateMemberRole.ts (4)
1-15: LGTM!The imports are well-organized and include the required
createDomainErrorandSYSTEM_ERRORSfrom@corates/sharedper coding guidelines. The JSDoc clearly documents the potential error case.
17-31: LGTM!The interfaces establish a clear typed contract for the command, consistent with the other member command typings mentioned in the AI summary.
55-78: LGTM!The non-fatal error handling for DO sync and user notifications is appropriate—the core operation (DB update) succeeds, and failures in secondary operations are logged without blocking the response. This aligns with the pattern used across other member commands.
33-37: Ensure route-level validation exists for allupdateMemberRoleinputs.This command accepts parameters directly without validation. Per the backend guidelines, all input validation must happen at the route layer using
validateRequestmiddleware with a Zod schema that validatesorgId,projectId,userId, androlebefore invoking this command function.Verify that the route calling this command includes proper Zod schema validation and
validateRequestmiddleware.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| ```md | ||
| ### Issue: <short title> | ||
|
|
||
| **Type:** Bug | Logic Error | CLAUDE.md Violation | ||
| **Severity:** High | ||
| **Reason flagged:** <agent-provided reason> | ||
|
|
||
| **Description:** | ||
| Clear explanation of the problem. | ||
|
|
||
| **Evidence:** | ||
|
|
||
| - File path and line range (for example: `src/foo/bar.ts:42-58`) | ||
| - Include at least one line of context before and after the relevant lines | ||
|
|
||
| **Relevant Rule (if applicable):** | ||
| Quoted CLAUDE.md rule with its file path. | ||
|
|
||
| **Suggested Fix:** | ||
| Description of the fix. Include a committable suggestion only if it fully resolves the issue. | ||
|
|
||
| ⸻ | ||
|
|
||
| 8. No-issue outcome | ||
|
|
||
| If no validated issues remain, the Markdown file must contain: | ||
|
|
||
| ## Code review | ||
|
|
||
| No issues found. Checked for bugs and CLAUDE.md compliance. | ||
|
|
||
| ⸻ | ||
|
|
||
| Explicit prohibitions | ||
| • Do not post GitHub comments | ||
| • Do not post inline comments | ||
| • Do not modify the repository | ||
| • Output only the Markdown report | ||
| ``` |
There was a problem hiding this comment.
Fix markdown code block structure and heading formats.
The code block starting at line 147 has structural issues:
-
Step 8 missing proper heading format (line 170): The text "8. No-issue outcome" should use
### 8. No-issue outcometo match the format of steps 0-7. -
"Explicit prohibitions" missing heading (line 180): This section lacks a markdown heading (should be
##or###). -
Code block boundaries unclear (lines 147-185): The current structure suggests lines 147-185 are all within one code block, but lines 168-184 appear to be documentation rather than example output. This creates ambiguity about whether:
- The code block should end at line 167 (before the separator), OR
- Lines 168-184 are meant as part of the example format
The separator at line 168 and the formatting of lines 169-184 suggest these should be outside the code block, but the closing backticks are at line 185.
📝 Proposed fix for markdown structure
Option 1: If lines 168-184 are meant to be documentation (recommended):
**Suggested Fix:**
Description of the fix. Include a committable suggestion only if it fully resolves the issue.
+```
-⸻
+---
-8. No-issue outcome
+### 8. No-issue outcome
If no validated issues remain, the Markdown file must contain:
+```md
## Code review
No issues found. Checked for bugs and CLAUDE.md compliance.
+```
-⸻
+---
-Explicit prohibitions
+## Explicit prohibitions
+
• Do not post GitHub comments
• Do not post inline comments
• Do not modify the repository
• Output only the Markdown report
-```Option 2: If lines 168-184 are meant to be part of the example (inside the code block):
**Suggested Fix:**
Description of the fix. Include a committable suggestion only if it fully resolves the issue.
⸻
-8. No-issue outcome
+### 8. No-issue outcome
If no validated issues remain, the Markdown file must contain:
## Code review
No issues found. Checked for bugs and CLAUDE.md compliance.
⸻
-Explicit prohibitions
+## Explicit prohibitions
+
• Do not post GitHub comments
• Do not post inline comments
• Do not modify the repository
• Output only the Markdown report
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In @.claude/skills/code-review/SKILL.md around lines 147 - 185, The markdown
code block is malformed around the snippet containing "8. No-issue outcome" and
"Explicit prohibitions": end the opening md block before the separator (the line with the horizontal separator characters), change "8. No-issue outcome" to a proper heading (e.g., "### 8. No-issue outcome"), wrap the “## Code review / No issues found…” example in its own fenced md block, replace the separator
with a proper markdown horizontal rule (---), and add a heading "## Explicit
prohibitions" before the bulleted list; locate these edits by searching for the
exact strings "8. No-issue outcome", "Explicit prohibitions", and the
opening/closing backticks in the problematic diff to fix the structure.
</details>
<!-- fingerprinting:phantom:triton:puma -->
<!-- This is an auto-generated comment by CodeRabbit -->
| try { | ||
| await db | ||
| .update(projectMembers) | ||
| .set({ role }) | ||
| .where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId))); | ||
| } catch (err) { | ||
| throw createDomainError(SYSTEM_ERRORS.DB_ERROR, { | ||
| operation: 'update_member_role', | ||
| cause: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| } |
There was a problem hiding this comment.
Update may silently succeed if user is not a project member.
The update query will succeed with 0 affected rows if the user is not a member of the project, returning a successful result despite no actual change occurring. Consider checking the result to ensure the member exists.
🔧 Suggested fix
try {
- await db
+ const result = await db
.update(projectMembers)
.set({ role })
.where(and(eq(projectMembers.projectId, projectId), eq(projectMembers.userId, userId)));
+
+ if (result.rowsAffected === 0) {
+ throw createDomainError(VALIDATION_ERRORS.NOT_FOUND, {
+ resource: 'project_member',
+ userId,
+ projectId,
+ });
+ }
} catch (err) {
+ if (err && typeof err === 'object' && 'code' in err) {
+ throw err; // Re-throw domain errors
+ }
throw createDomainError(SYSTEM_ERRORS.DB_ERROR, {
operation: 'update_member_role',
cause: err instanceof Error ? err.message : String(err),
});
}Verify if rowsAffected is available on Drizzle's update result for D1:
#!/bin/bash
# Check how other commands handle update results in this codebase
rg -n -A5 'rowsAffected|\.update\(' packages/workers/src/commands/ --type ts🤖 Prompt for AI Agents
In `@packages/workers/src/commands/members/updateMemberRole.ts` around lines 43 -
53, The update currently calls db.update(projectMembers).set({ role })... but
never checks the result so it can silently succeed with 0 affected rows; change
the logic in updateMemberRole to capture the update result and assert that at
least one row was affected (e.g., check result.rowsAffected or the Drizzle/D1
equivalent) and if zero, throw a domain error (use createDomainError with
SYSTEM_ERRORS.DB_ERROR or a more specific NOT_FOUND error and operation:
'update_member_role') so callers know the member did not exist; keep the
existing catch that maps DB errors via createDomainError for other failures.
Summary by CodeRabbit
New Features
Refactoring
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.