use mediafile table for file uploads#232
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
corates | cc7df8d | Commit Preview URL | Jan 05 2026, 10:13 PM |
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 📝 WalkthroughWalkthroughThis PR extends the mediaFiles table with orgId, projectId, and studyId columns and foreign keys, then refactors PDF and Google Drive file uploads to insert mediaFiles records and generate unique filenames. The database becomes the source of truth for PDF state instead of bucket enumeration. Changes
Sequence DiagramssequenceDiagram
participant Client
participant Server
participant DB as Database
participant R2 as R2 Storage
rect rgb(200, 220, 255)
note over Client,R2: Old PDF Upload Flow
Client->>Server: POST upload (document.pdf)
Server->>R2: Check for existing key
alt Duplicate found
Server-->>Client: 409 Conflict
else New file
Server->>R2: Upload file
Server-->>Client: 200 Success
end
end
rect rgb(220, 240, 220)
note over Client,R2: New PDF Upload Flow
Client->>Server: POST upload (document.pdf)
Server->>DB: Check mediaFiles for filename collision
alt Duplicate found
note over Server: Generate unique name (document (1).pdf)
end
Server->>R2: Upload file with unique name
Server->>DB: Insert mediaFiles record (id, orgId, projectId, studyId, etc.)
Server-->>Client: 200 Success (id, filename, orgId, projectId, studyId)
end
sequenceDiagram
participant Client
participant Server
participant GoogleDrive as Google Drive
participant DB as Database
participant R2 as R2 Storage
Client->>Server: POST import from Google Drive
Server->>GoogleDrive: Fetch file
GoogleDrive-->>Server: File content + metadata
Server->>DB: Fetch project (to get orgId)
DB-->>Server: Project record (orgId)
Server->>Server: Generate unique filename
Server->>R2: Upload file
R2-->>Server: Success
Server->>DB: Insert mediaFiles record (orgId, projectId, studyId, etc.)
alt DB insert fails
note over Server: Log error, continue
end
Server-->>Client: 200 Success (id, filename, metadata)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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 |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
packages/workers/src/routes/google-drive.js (1)
378-397: Consider: R2 orphan risk when DB insert fails.If the
mediaFilesinsert fails, the R2 object persists without a corresponding database record. While logging and continuing is pragmatic (user gets their file), this creates potential for orphaned storage objects.Consider adding a cleanup mechanism (background job or manual reconciliation) to detect and remove R2 objects without matching
mediaFilesrecords. Alternatively, reverse the order: insert DB record first (with a placeholder or pending status), then upload to R2, then update the record.packages/workers/src/routes/orgs/pdfs.js (1)
61-117: Performance:generateUniqueFileNamemakes O(n) DB queries in a loop.The current implementation makes up to 1000 sequential database queries to find a unique filename. While this works, it could be slow for studies with many similarly-named files.
Consider optimizing with a single query to fetch all matching filenames:
Suggested optimization
export async function generateUniqueFileName(fileName, projectId, studyId, db) { - // Check if original filename is available - const existing = await db - .select({ id: mediaFiles.id }) + // Extract name and extension + const lastDot = fileName.lastIndexOf('.'); + const nameWithoutExt = lastDot > 0 ? fileName.slice(0, lastDot) : fileName; + const ext = lastDot > 0 ? fileName.slice(lastDot) : ''; + + // Fetch all filenames that start with the base name in one query + const existingFiles = await db + .select({ filename: mediaFiles.filename }) .from(mediaFiles) .where( and( eq(mediaFiles.projectId, projectId), eq(mediaFiles.studyId, studyId), - eq(mediaFiles.filename, fileName), ), ) - .get(); + .all(); - if (!existing) { + const existingNames = new Set(existingFiles.map(f => f.filename)); + + if (!existingNames.has(fileName)) { return fileName; } - // Extract name and extension - const lastDot = fileName.lastIndexOf('.'); - const nameWithoutExt = lastDot > 0 ? fileName.slice(0, lastDot) : fileName; - const ext = lastDot > 0 ? fileName.slice(lastDot) : ''; - - // Try numbered versions: "file (1).pdf", "file (2).pdf", etc. let counter = 1; - let uniqueFileName; - let found = true; - - while (found && counter < 1000) { - uniqueFileName = `${nameWithoutExt} (${counter})${ext}`; - const duplicate = await db - .select({ id: mediaFiles.id }) - .from(mediaFiles) - .where( - and( - eq(mediaFiles.projectId, projectId), - eq(mediaFiles.studyId, studyId), - eq(mediaFiles.filename, uniqueFileName), - ), - ) - .get(); - - if (!duplicate) { - found = false; - } else { + while (counter < 1000) { + const candidate = `${nameWithoutExt} (${counter})${ext}`; + if (!existingNames.has(candidate)) { + return candidate; + } counter++; - } } - if (found) { - // Fallback: use timestamp if we hit the limit - const timestamp = Date.now(); - uniqueFileName = `${nameWithoutExt}_${timestamp}${ext}`; - } - - return uniqueFileName; + // Fallback: use timestamp if we hit the limit + return `${nameWithoutExt}_${Date.now()}${ext}`; }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
packages/web/src/components/billing/BillingPage.jsxpackages/workers/migrations/0000_amusing_black_panther.sqlpackages/workers/migrations/meta/0000_snapshot.jsonpackages/workers/migrations/meta/_journal.jsonpackages/workers/src/__tests__/helpers.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/orgs/pdfs.js
🧰 Additional context used
📓 Path-based instructions (27)
packages/workers/**/!(*.test|*.spec).{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
packages/workers/**/!(*.test|*.spec).{js,ts,jsx,tsx}: Always usecreateDomainErrorfrom@corates/sharedfor backend error handling in workers, with predefined error constants (PROJECT_ERRORS, AUTH_ERRORS, VALIDATION_ERRORS, SYSTEM_ERRORS, USER_ERRORS)
Wrap database operations in try-catch blocks and return domain errors usingcreateDomainError(SYSTEM_ERRORS.DB_ERROR, ...)with operation metadata
Use validation middleware withvalidateRequest(schema)for request validation; do not manually validate in routes
Files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
packages/{web,workers}/**/!(*.test|*.spec).{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
packages/{web,workers}/**/!(*.test|*.spec).{js,ts,jsx,tsx}: Never throw string literals; always throw Error objects or return domain errors from API routes
UseisErrorCodeutility from@corates/sharedor@/lib/error-utils.jsto check for specific error codes instead of manual error comparisons
Files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/web/src/components/billing/BillingPage.jsxpackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
**/*.{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
Files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/web/src/components/billing/BillingPage.jsxpackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
**/*
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*: NEVER use emojis anywhere - not in code, comments, documentation, plan files, commit messages, or examples
Do NOT use unicode symbols - unicode symbols are forbidden anywhere in the codebase
Files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/migrations/meta/_journal.jsonpackages/web/src/components/billing/BillingPage.jsxpackages/workers/src/routes/orgs/pdfs.jspackages/workers/migrations/meta/0000_snapshot.jsonpackages/workers/migrations/0000_amusing_black_panther.sqlpackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
**/*.{ts,tsx,jsx,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx,jsx,js}: For UI icons, usesolid-iconslibrary or SVGs only (never emojis)
Prefer modern ES6+ syntax and features
Use import aliases from jsconfig.json as defined in the ui-components.mdc documentation
Prefer config files over hardcoding values
Ensure browser compatibility (Safari is usually problematic)
Group related components in subdirectories with barrel exports
Use Ark UI components from@corates/uipackage, not local component implementations
Usesolid-iconsicon library (e.g.,solid-icons/bi,solid-icons/fi) for icons
Comments should explain WHY something is being done, not WHAT the code is doing
Comment to explain why a particular approach or workaround was chosen
Comment to clarify intent when code could be misread or misunderstood
Comment to provide context from external systems, specs, or requirements
Comment to document assumptions, edge cases, or limitations
Do NOT comment by narrating what the code is doing
Do NOT duplicate function or variable names in plain English comments
Do NOT leave stale comments that contradict the code
Do NOT reference removed or obsolete code paths in comments
Files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/web/src/components/billing/BillingPage.jsxpackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.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/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/web/src/components/billing/BillingPage.jsxpackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
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
Files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
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/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.jspackages/workers/src/__tests__/seed-schemas.jspackages/workers/src/db/schema.js
{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-workers with 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/__tests__/helpers.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/__tests__/seed-schemas.js
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/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.js
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/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.js
packages/web/**/!(*.test|*.spec).{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/error-handling.mdc)
packages/web/**/!(*.test|*.spec).{js,ts,jsx,tsx}: Always usehandleFetchErrorfrom@/lib/error-utils.jsfor frontend fetch calls with optionalshowToastparameter
UsecreateFormErrorSignalsfrom@/lib/form-errors.jsfor handling form validation errors, field-level errors, and global errors in frontend forms
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/src/**/*.{js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/form-state.mdc)
packages/web/src/**/*.{js,jsx}: Save form state to IndexedDB before initiating OAuth redirects (Google Drive, ORCID) using saveFormState() with form type ('createProject' or 'addStudies') and serializable state only
Only save serializable data to IndexedDB—exclude File objects, ArrayBuffers, functions, and other non-serializable objects from form state persistence
Restore form state after OAuth redirects by checking URL restore params with getRestoreParamsFromUrl(), retrieving saved state with getFormState(), restoring to form, clearing saved state with clearFormState(), and clearing URL params with clearRestoreParamsFromUrl()
For temporary File object storage during OAuth flows (e.g., pending PDFs), use projectStore.setPendingProjectData() instead of IndexedDB, as File objects cannot be serialized
Add restore parameters to the URL after OAuth redirects in the format '?restore=&projectId=' to signal form state restoration on mount
Clear URL restore parameters after form state restoration by calling clearRestoreParamsFromUrl() to prevent stale restoration attempts on subsequent navigation
Form state persistence library functions (saveFormState, getFormState, clearFormState, getRestoreParamsFromUrl, clearRestoreParamsFromUrl) are implemented in packages/web/src/lib/formStatePersistence.js and should be imported from @/lib/formStatePersistence.js
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/src/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/pdf-handling.mdc)
packages/web/src/**/*.{js,jsx,ts,tsx}: Always useuploadPdffrom@api/pdf-api.jsfor uploading PDF files. Pass object with projectId, studyId, tag ('primary' or 'supplementary'), and fileName
Validate PDF files on frontend by checking file.type includes 'pdf' and file.size is within MAX_PDF_SIZE limit (full validation is done on backend)
UsecachePdfandgetCachedPdffrom@primitives/pdfCache.jsfor caching PDF data in IndexedDB
Implement PDF caching strategy: check cache first, download from server if not cached, then cache the downloaded PDF
Store only PDF metadata in Yjs (id, name, size, tag, uploadedAt, uploadedBy), never store PDF binary data in Yjs as it is too large
UseimportFromGoogleDrivefrom@api/google-drive.jsfor importing PDFs from Google Drive. Pass fileId, projectId, studyId, and tag
Save form state usingsaveFormStatefrom@/lib/formStatePersistence.jsbefore triggering OAuth redirects for Google Drive
UseaddPdfToStudyoperation fromuseProjecthook to add PDFs to a study, passing id, name, size, tag, uploadedAt, and uploadedBy
UseremovePdfFromStudyoperation fromuseProjecthook to remove PDFs from a study
UsepdfPreviewStorefrom@/stores/pdfPreviewStore.jsfor managing PDF preview state (openPreview, closePreview, getPreview methods)
UsedownloadPdffrom@api/pdf-api.jsto download PDFs from server, then cache the result usingcachePdf
Always cache PDFs after download and check cache before downloading to avoid redundant downloads
Use PDF operations fromuseProjecthook instead of bypassing through direct API calls
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/src/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/pdf-handling.mdc)
Use
PdfViewercomponent from@/components/checklist-ui/pdf/PdfViewer.jsxfor displaying PDFs, passing pdfData as ArrayBuffer, fileName, readOnly, and onPageChange
Files:
packages/web/src/components/billing/BillingPage.jsx
{packages/web/**,packages/landing/**}/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/solidjs.mdc)
{packages/web/**,packages/landing/**}/**/*.{jsx,tsx}: NEVER destructure props in SolidJS components - access props directly or wrap in functions to maintain reactivity
Use external stores in packages/web/src/stores/ for shared/cross-feature state instead of prop-drilling
Keep SolidJS components lean and focused on rendering - move business logic to stores, primitives, or utilities
Create reusable logic in primitives (hooks) in packages/web/src/primitives/ and import them into components
Use Solid's Show component for conditional rendering instead of ternary operators
Use Solid's For component for rendering lists instead of Array.map()
Use the children helper when manipulating props.children in SolidJS components
Files:
packages/web/src/components/billing/BillingPage.jsx
{packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/solidjs.mdc)
{packages/web/**,packages/landing/**}/**/*.{js,jsx,ts,tsx}: Use createSignal for simple reactive values in SolidJS components
Use createStore for complex objects and arrays that need granular reactivity in SolidJS
Use createMemo for computed/derived values that depend on reactive state in SolidJS
Always clean up SolidJS effects that create subscriptions or timers using onCleanup
Import stores directly in components and use store read/write action pattern - read from store, write via actions store
Prefer derived state with createMemo or signals over effects whenever possible
Use local createSignal or createStore for local component state; use external stores for shared/cross-feature state
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/{web,ui}/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
packages/{web,ui}/**/*.{js,jsx,ts,tsx}: Import UI components from '@corates/ui' package instead of local components directories
Use solid-icons library for icons instead of emojis or other icon sources
Use Tailwind CSS classes for styling UI components
Apply responsive design using mobile-first approach with Tailwind CSS
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Use import aliases from jsconfig.json (e.g., @/, @components/, @auth-ui/, @checklist-ui/, @project-ui/, @routes/, @primitives/, @api/, @config/, @lib/) instead of relative paths
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/**/*.{ts,tsx,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
packages/web/**/*.{ts,tsx,jsx}: Do NOT prop-drill application state in SolidJS - import stores directly where needed
Do NOT destructure props in SolidJS - accessprops.fielddirectly or wrap in function:() => props.field
SolidJS components should receive at most 1-5 props (local config only, not shared state)
Move business logic to stores, utilities, or primitives (not in components) in SolidJS
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/corates.mdc)
packages/web/**/*.{jsx,tsx}: Do NOT prop-drill application state in SolidJS - import stores directly where needed
Do NOT destructure props in SolidJS - accessprops.fielddirectly or wrap in function:() => props.field
Shared state lives in external stores underpackages/web/src/stores/
SolidJS components should receive at most 1-5 props (local config only, not shared state)
UsecreateStorefor complex state objects in SolidJS
UsecreateMemofor derived values in SolidJS
Move business logic to stores, utilities, or primitives - not components
Frontend uses orgSlug in URLs (/orgs/:orgSlug/...) for readability
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/web/src/**
📄 CodeRabbit inference engine (.cursor/rules/organizations.mdc)
packages/web/src/**: Use human-readable slug in frontend URLs: /orgs/:orgSlug/...
Use orgId (UUID) for API calls, not orgSlug, when making fetch requests to backend endpoints
Files:
packages/web/src/components/billing/BillingPage.jsx
packages/workers/src/routes/orgs/**/*.js
📄 CodeRabbit inference engine (.cursor/rules/api-routes.mdc)
Use organization-scoped route patterns with path structure
/api/orgs/:orgId/...and leveragerequireOrgMembership,requireProjectAccess,getOrgContext, andgetProjectContextmiddleware
Files:
packages/workers/src/routes/orgs/pdfs.js
packages/workers/src/routes/orgs/**
📄 CodeRabbit inference engine (.cursor/rules/organizations.mdc)
packages/workers/src/routes/orgs/**: Use org role hierarchy: owner > admin > member
Use project role hierarchy: owner > collaborator > member > viewer
Use UUID for backend API endpoints: /api/orgs/:orgId/...
Import and use requireOrgMembership middleware to verify org membership and optionally enforce minimum role requirements
Use requireProjectAccess middleware after requireOrgMembership to verify project membership and optionally enforce minimum role requirements
Enforce middleware chain order: requireAuth → requireOrgMembership → requireProjectAccess → requireEntitlement → validateRequest
When creating projects, set orgId foreign key and add creator as project owner in single atomic batch operation
Ensure project invitation flow creates org membership before project membership, including orgRole and projectRole in invitation data
Files:
packages/workers/src/routes/orgs/pdfs.js
packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/workers.mdc)
packages/workers/src/routes/orgs/**/*.{js,ts,jsx,tsx}: UserequireOrgMembershipandrequireProjectAccessmiddleware instead of manual membership checks for org-scoped routes
UsegetOrgContext(c)to retrieve the validated orgId from context instead of manual checks - orgId is guaranteed valid by middleware
Files:
packages/workers/src/routes/orgs/pdfs.js
packages/workers/**/*.{ts,sql}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use DrizzleKit to generate new migrations when necessary
Files:
packages/workers/migrations/0000_amusing_black_panther.sql
packages/workers/**/*.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Do NOT create separate migration files manually - use DrizzleKit instead
packages/workers/**/*.sql: Use DrizzleKit to generate new migrations when necessary
Do NOT create separate migration files manually (0002_xxx.sql, etc.) - use DrizzleKit instead
Files:
packages/workers/migrations/0000_amusing_black_panther.sql
🧠 Learnings (36)
📚 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/__tests__/helpers.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/__tests__/seed-schemas.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} : 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/__tests__/helpers.jspackages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/__tests__/seed-schemas.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} : Clear ProjectDoc Durable Objects storage between tests using clearProjectDOs() helper to prevent invalidation/reset edge cases
Applied to files:
packages/workers/src/__tests__/helpers.jspackages/workers/src/routes/__tests__/pdfs.test.js
📚 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.jspackages/workers/src/routes/orgs/pdfs.js
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/**/*.{js,jsx} : Save form state to IndexedDB before initiating OAuth redirects (Google Drive, ORCID) using saveFormState() with form type ('createProject' or 'addStudies') and serializable state only
Applied to files:
packages/workers/src/routes/google-drive.js
📚 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} : Always use `uploadPdf` from `api/pdf-api.js` for uploading PDF files. Pass object with projectId, studyId, tag ('primary' or 'supplementary'), and fileName
Applied to files:
packages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.js
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/**/*.{js,jsx} : For temporary File object storage during OAuth flows (e.g., pending PDFs), use projectStore.setPendingProjectData() instead of IndexedDB, as File objects cannot be serialized
Applied to files:
packages/workers/src/routes/google-drive.jspackages/workers/src/routes/orgs/pdfs.js
📚 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.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/google-drive.js
📚 Learning: 2025-12-27T03:01:54.727Z
Learnt from: CR
Repo: InfinityBowman/corates PR: 0
File: .cursor/rules/form-state.mdc:0-0
Timestamp: 2025-12-27T03:01:54.727Z
Learning: Applies to packages/web/src/components/project-ui/**/*.{js,jsx} : Form state should include serializable metadata when handling files (name, size, type) and use the store's pendingPdfs pattern for actual File objects that persist across redirects
Applied to files:
packages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.js
📚 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 `addPdfToStudy` operation from `useProject` hook to add PDFs to a study, passing id, name, size, tag, uploadedAt, and uploadedBy
Applied to files:
packages/workers/src/routes/google-drive.jspackages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.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} : Use `requireOrgMembership` and `requireProjectAccess` middleware for auth
Applied to files:
packages/workers/src/routes/google-drive.jspackages/workers/src/routes/orgs/pdfs.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} : Use Drizzle ORM for ALL database interactions and migrations
Applied to files:
packages/workers/src/routes/google-drive.jspackages/workers/src/routes/orgs/pdfs.js
📚 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/google-drive.jspackages/workers/src/routes/orgs/pdfs.js
📚 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} : Validate PDF files on frontend by checking file.type includes 'pdf' and file.size is within MAX_PDF_SIZE limit (full validation is done on backend)
Applied to files:
packages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.js
📚 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 `removePdfFromStudy` operation from `useProject` hook to remove PDFs from a study
Applied to files:
packages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.js
📚 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} : Store only PDF metadata in Yjs (id, name, size, tag, uploadedAt, uploadedBy), never store PDF binary data in Yjs as it is too large
Applied to files:
packages/workers/src/routes/__tests__/pdfs.test.jspackages/workers/src/routes/orgs/pdfs.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__/pdfs.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} : 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
Applied to files:
packages/workers/src/routes/__tests__/pdfs.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} : Use resetTestDatabase() helper to reset D1 from migration SQL before each test or describe block
Applied to files:
packages/workers/src/routes/__tests__/pdfs.test.js
📚 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/routes/__tests__/pdfs.test.js
📚 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/routes/__tests__/pdfs.test.js
📚 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 `cachePdf` and `getCachedPdf` from `primitives/pdfCache.js` for caching PDF data in IndexedDB
Applied to files:
packages/workers/src/routes/orgs/pdfs.js
📚 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/routes/orgs/pdfs.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/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/orgs/pdfs.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 : ALWAYS use `createDomainError` from `corates/shared` for error handling in API routes
Applied to files:
packages/workers/src/routes/orgs/pdfs.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 : Always create DB client from environment using `createDb` function in route handlers
Applied to files:
packages/workers/src/routes/orgs/pdfs.js
📚 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/orgs/pdfs.js
📚 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/orgs/pdfs.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 : Always use Drizzle ORM with query builders - never use raw SQL in database operations
Applied to files:
packages/workers/src/routes/orgs/pdfs.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/orgs/pdfs.js
📚 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: Projects belong to organizations via orgId foreign key; org membership is separate from project membership
Applied to files:
packages/workers/migrations/meta/0000_snapshot.jsonpackages/workers/migrations/0000_amusing_black_panther.sql
📚 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/**/*.sql : Do NOT create separate migration files manually (0002_xxx.sql, etc.) - use DrizzleKit instead
Applied to files:
packages/workers/migrations/0000_amusing_black_panther.sql
📚 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/__tests__/seed-schemas.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/config/validation.js : Add new validation schemas to `src/config/validation.js` and reuse `commonFields` when possible
Applied to files:
packages/workers/src/__tests__/seed-schemas.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} : Use Zod for schema and input validation on the backend
Applied to files:
packages/workers/src/__tests__/seed-schemas.js
🧬 Code graph analysis (2)
packages/workers/src/routes/google-drive.js (2)
packages/workers/src/db/schema.js (6)
projects(118-131)projects(118-131)user(5-26)user(5-26)mediaFiles(146-162)mediaFiles(146-162)packages/workers/src/routes/orgs/pdfs.js (8)
uniqueFileName(86-86)uniqueFileName(288-288)generateUniqueFileName(61-117)studyId(31-31)studyId(125-125)studyId(192-192)studyId(358-358)studyId(422-422)
packages/workers/src/routes/orgs/pdfs.js (3)
packages/workers/src/db/schema.js (4)
mediaFiles(146-162)mediaFiles(146-162)user(5-26)user(5-26)packages/workers/src/db/client.js (1)
createDb(9-11)packages/shared/src/errors/domains/domain.ts (1)
SYSTEM_ERRORS(163-199)
⏰ 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/web/src/components/billing/BillingPage.jsx (1)
164-164: LGTM - Success message simplified.The success message text has been appropriately condensed to a single sentence while maintaining clarity.
packages/workers/migrations/meta/_journal.json (1)
8-9: Note: Migration 0000 was regenerated.The migration tag changed from "0000_good_violations" to "0000_amusing_black_panther", indicating the initial migration was regenerated rather than adding a new migration file (0001, 0002, etc.). This is acceptable for development environments or if the database is being reset, but ensure this approach aligns with your deployment strategy.
Based on learnings, DrizzleKit should generate migrations rather than manual creation.
packages/workers/migrations/0000_amusing_black_panther.sql (1)
39-45: LGTM - mediaFiles table properly extended with organizational scope.The new columns and foreign keys correctly establish the organizational hierarchy for media files:
orgIdandprojectIdare required (NOT NULL), ensuring every file belongs to an organization and projectstudyIdis optional, allowing files at project level or study level- Foreign key constraints with CASCADE delete ensure proper cleanup when parent entities are removed
The migration structure follows DrizzleKit conventions.
Based on learnings, projects belong to organizations via orgId foreign key.
packages/workers/src/__tests__/helpers.js (1)
351-353: LGTM - Test helper updated for new mediaFiles schema.The
seedMediaFilehelper correctly inserts the neworgId,projectId, andstudyIdfields, which are validated byseedMediaFileSchema. The function signature remains unchanged, maintaining compatibility while relying on schema validation to ensure required fields are provided.Based on learnings, test helpers should use Zod schema validation for seed data.
packages/workers/migrations/meta/0000_snapshot.json (1)
260-318: LGTM - Snapshot accurately reflects mediaFiles schema changes.The snapshot correctly documents the addition of
orgId,projectId, andstudyIdcolumns along with their foreign key constraints. The schema is consistent with the migration SQL:
- Column nullability matches (orgId and projectId NOT NULL, studyId nullable)
- Foreign keys properly reference organization and projects tables with cascade delete
- Structure follows DrizzleKit snapshot format
packages/workers/src/__tests__/seed-schemas.js (1)
210-212: LGTM!The new
orgId,projectId, andstudyIdfields are correctly added to match the updatedmediaFilesdatabase schema. The validation constraints align properly:orgIdandprojectIdare required (matching NOT NULL in schema), whilestudyIdis optional with a null default (matching the nullable column).packages/workers/src/db/schema.js (1)
154-160: LGTM!The new columns are correctly defined with appropriate constraints:
orgIdandprojectIdare non-null with cascade delete FKs, ensuring referential integrity and automatic cleanup when parent entities are deleted.studyIdis nullable without a FK, which aligns with the architectural pattern where studies are managed in Yjs rather than D1.packages/workers/src/routes/__tests__/pdfs.test.js (3)
364-384: LGTM!Good test coverage for verifying the
mediaFilesrecord creation after upload. The assertions correctly validate all the new fields (id,orgId,projectId,studyId,uploadedBy) are persisted as expected.
546-559: LGTM!The auto-rename test correctly seeds an existing
mediaFilesrecord before attempting the duplicate upload, validating the new database-driven duplicate detection logic.
795-808: LGTM!The delete test properly seeds a
mediaFilesrecord and verifies both the database record deletion and R2 storage cleanup. This validates the new "database as source of truth" behavior.Also applies to: 823-836
packages/workers/src/routes/google-drive.js (2)
341-355: LGTM!Good addition to fetch the project and validate it exists before proceeding. Using
SYSTEM_ERRORS.DB_ERRORwith descriptive context is appropriate for this lookup failure case.
357-360: LGTM!Correctly reuses
generateUniqueFileNamefrompdfs.jsfor consistent duplicate handling across both upload paths.packages/workers/src/routes/orgs/pdfs.js (3)
123-176: LGTM!The refactored list endpoint correctly queries
mediaFileswith aleftJointouserfor uploader details. The response mapping is clean and provides all necessary fields including the newidand enricheduploadedByobject.
306-325: Same orphan risk as noted in google-drive.js.The DB insert failure is caught and logged but doesn't fail the request, which could leave orphaned R2 objects. This is consistent with
google-drive.js, so at least the behavior is uniform across upload paths.Confirm this is the intended behavior and consider documenting the need for a cleanup mechanism for orphaned R2 objects.
446-490: LGTM!Good implementation treating the database as the source of truth for deletions:
- Idempotent: returns success even if DB record doesn't exist
- Attempts R2 cleanup regardless of DB state
- R2 failures don't fail the request since DB is authoritative
This is a resilient pattern that handles partial state gracefully.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.