Skip to content

use mediafile table for file uploads#232

Merged
InfinityBowman merged 5 commits into
mainfrom
231-pdf-relations-in-d1
Jan 5, 2026
Merged

use mediafile table for file uploads#232
InfinityBowman merged 5 commits into
mainfrom
231-pdf-relations-in-d1

Conversation

@InfinityBowman
Copy link
Copy Markdown
Owner

@InfinityBowman InfinityBowman commented Jan 5, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added organization and project-level tracking for file uploads, improving file management capabilities
    • Files with duplicate names are now auto-renamed with numbered suffixes instead of being rejected
  • Bug Fixes

    • Refined subscription activation success message for improved clarity

✏️ Tip: You can customize this high-level summary in your review settings.

@InfinityBowman InfinityBowman linked an issue Jan 5, 2026 that may be closed by this pull request
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Jan 5, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
corates cc7df8d Commit Preview URL Jan 05 2026, 10:13 PM

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 5, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Billing UI
packages/web/src/components/billing/BillingPage.jsx
Condensed success alert text from two-line to single-line message ("Your subscription has been activated!")
Database Schema
packages/workers/src/db/schema.js
Added orgId (non-null, FK to organization.id), projectId (non-null, FK to projects.id), and studyId (optional text) columns to mediaFiles table with cascade delete policies
Database Migrations
packages/workers/migrations/0000_amusing_black_panther.sql, packages/workers/migrations/meta/0000_snapshot.json, packages/workers/migrations/meta/_journal.json
Created migration defining new mediaFiles columns and foreign keys; updated snapshot ID and journal metadata
Test Infrastructure
packages/workers/src/__tests__/helpers.js, packages/workers/src/__tests__/seed-schemas.js
Extended seedMediaFile to include orgId, projectId, studyId; added schema validation for new fields (orgId and projectId required, studyId optional)
PDF Tests
packages/workers/src/routes/__tests__/pdfs.test.js
Updated tests to seed mediaFiles records, verify database insertions, assert orgId/projectId/studyId in responses, and expect auto-renaming behavior (200 with numbered suffix) instead of duplicate rejection (409)
Google Drive Integration
packages/workers/src/routes/google-drive.js
Added project fetching for orgId, unique filename generation, R2 upload with metadata, and mediaFiles database insert after successful upload; logs DB errors without aborting import
PDF Routes
packages/workers/src/routes/orgs/pdfs.js
Introduced new generateUniqueFileName() helper; replaced bucket enumeration with database queries for listing PDFs; refactored upload to generate unique names and insert mediaFiles records; enhanced deletion with DB-first checks and storage fallback

Sequence Diagrams

sequenceDiagram
    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
Loading
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)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • 81 todo tab multiple pdf support #83: Modifies PDF handling in StudyCard/Todo UI components and backend mediaFiles behavior to work with the new database-driven file management system.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: introducing the mediaFile table for file upload management across multiple components.

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/workers/src/routes/google-drive.js (1)

378-397: Consider: R2 orphan risk when DB insert fails.

If the mediaFiles insert 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 mediaFiles records. 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: generateUniqueFileName makes 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

📥 Commits

Reviewing files that changed from the base of the PR and between 42ceebc and 74aa45f.

📒 Files selected for processing (10)
  • packages/web/src/components/billing/BillingPage.jsx
  • packages/workers/migrations/0000_amusing_black_panther.sql
  • packages/workers/migrations/meta/0000_snapshot.json
  • packages/workers/migrations/meta/_journal.json
  • packages/workers/src/__tests__/helpers.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/workers/src/db/schema.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/workers/src/routes/google-drive.js
  • packages/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 use createDomainError from @corates/shared for 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 using createDomainError(SYSTEM_ERRORS.DB_ERROR, ...) with operation metadata
Use validation middleware with validateRequest(schema) for request validation; do not manually validate in routes

Files:

  • packages/workers/src/__tests__/helpers.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/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
Use isErrorCode utility from @corates/shared or @/lib/error-utils.js to check for specific error codes instead of manual error comparisons

Files:

  • packages/workers/src/__tests__/helpers.js
  • packages/workers/src/routes/google-drive.js
  • packages/web/src/components/billing/BillingPage.jsx
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/workers/src/db/schema.js
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/yjs-sync.mdc)

**/*.{js,jsx,ts,tsx}: Use the useProject hook 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, use solid-icons library 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/ui package, NOT local components
Use solid-icons icon 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.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/web/src/components/billing/BillingPage.jsx
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/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.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/workers/migrations/meta/_journal.json
  • packages/web/src/components/billing/BillingPage.jsx
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/migrations/meta/0000_snapshot.json
  • packages/workers/migrations/0000_amusing_black_panther.sql
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/workers/src/db/schema.js
**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,jsx,js}: For UI icons, use solid-icons library 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/ui package, not local component implementations
Use solid-icons icon 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.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/web/src/components/billing/BillingPage.jsx
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/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.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/web/src/components/billing/BillingPage.jsx
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/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
Use requireOrgMembership and requireProjectAccess middleware for auth

Files:

  • packages/workers/src/__tests__/helpers.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/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.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/workers/src/routes/orgs/pdfs.js
  • packages/workers/src/__tests__/seed-schemas.js
  • packages/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.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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 use validateRequest middleware for request body validation in API routes
Use validateQueryParams middleware for query string validation in API routes
Always create DB client from environment using createDb function in route handlers
Use db.batch() for related database operations that must be atomic
Always use Drizzle ORM with query builders - never use raw SQL in database operations
ALWAYS use createDomainError from @corates/shared for 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.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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 using validateRequest middleware from the validation config rather than manual validation
Use validateQueryParams middleware for validating query parameters in routes

Files:

  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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 use handleFetchError from @/lib/error-utils.js for frontend fetch calls with optional showToast parameter
Use createFormErrorSignals from @/lib/form-errors.js for 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 use uploadPdf from @api/pdf-api.js for 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)
Use cachePdf and getCachedPdf from @primitives/pdfCache.js for 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
Use importFromGoogleDrive from @api/google-drive.js for importing PDFs from Google Drive. Pass fileId, projectId, studyId, and tag
Save form state using saveFormState from @/lib/formStatePersistence.js before triggering OAuth redirects for Google Drive
Use addPdfToStudy operation from useProject hook to add PDFs to a study, passing id, name, size, tag, uploadedAt, and uploadedBy
Use removePdfFromStudy operation from useProject hook to remove PDFs from a study
Use pdfPreviewStore from @/stores/pdfPreviewStore.js for managing PDF preview state (openPreview, closePreview, getPreview methods)
Use downloadPdf from @api/pdf-api.js to download PDFs from server, then cache the result using cachePdf
Always cache PDFs after download and check cache before downloading to avoid redundant downloads
Use PDF operations from useProject hook 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 PdfViewer component from @/components/checklist-ui/pdf/PdfViewer.jsx for 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 - access props.field directly 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 - access props.field directly or wrap in function: () => props.field
Shared state lives in external stores under packages/web/src/stores/
SolidJS components should receive at most 1-5 props (local config only, not shared state)
Use createStore for complex state objects in SolidJS
Use createMemo for 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 leverage requireOrgMembership, requireProjectAccess, getOrgContext, and getProjectContext middleware

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}: Use requireOrgMembership and requireProjectAccess middleware instead of manual membership checks for org-scoped routes
Use getOrgContext(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.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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.js
  • packages/workers/src/routes/google-drive.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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.js
  • 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 `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.js
  • packages/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.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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.js
  • 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} : 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.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • 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 `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.js
  • packages/workers/src/routes/__tests__/pdfs.test.js
  • packages/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.js
  • packages/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.js
  • 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 `requireOrgMembership` and `requireProjectAccess` middleware instead of manual membership checks for org-scoped routes

Applied to files:

  • packages/workers/src/routes/google-drive.js
  • 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} : 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.js
  • 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 `removePdfFromStudy` operation from `useProject` hook to remove PDFs from a study

Applied to files:

  • packages/workers/src/routes/__tests__/pdfs.test.js
  • 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} : 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.js
  • packages/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.json
  • packages/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:

  • orgId and projectId are required (NOT NULL), ensuring every file belongs to an organization and project
  • studyId is 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 seedMediaFile helper correctly inserts the new orgId, projectId, and studyId fields, which are validated by seedMediaFileSchema. 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, and studyId columns 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, and studyId fields are correctly added to match the updated mediaFiles database schema. The validation constraints align properly: orgId and projectId are required (matching NOT NULL in schema), while studyId is 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:

  • orgId and projectId are non-null with cascade delete FKs, ensuring referential integrity and automatic cleanup when parent entities are deleted.
  • studyId is 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 mediaFiles record 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 mediaFiles record before attempting the duplicate upload, validating the new database-driven duplicate detection logic.


795-808: LGTM!

The delete test properly seeds a mediaFiles record 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_ERROR with descriptive context is appropriate for this lookup failure case.


357-360: LGTM!

Correctly reuses generateUniqueFileName from pdfs.js for consistent duplicate handling across both upload paths.

packages/workers/src/routes/orgs/pdfs.js (3)

123-176: LGTM!

The refactored list endpoint correctly queries mediaFiles with a leftJoin to user for uploader details. The response mapping is clean and provides all necessary fields including the new id and enriched uploadedBy object.


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.

@InfinityBowman InfinityBowman merged commit a10bca6 into main Jan 5, 2026
3 checks passed
@InfinityBowman InfinityBowman deleted the 231-pdf-relations-in-d1 branch January 5, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PDF relations in D1

2 participants