76 lines
5.3 KiB
Markdown
76 lines
5.3 KiB
Markdown
# Phase 25: File System — Codebase Research
|
|
|
|
**Gathered:** 2026-04-01
|
|
**Status:** Complete
|
|
|
|
## Existing Patterns
|
|
|
|
### Storage System
|
|
- **StorageService** in `server/src/storage/service.ts` handles file persistence via a provider abstraction (local disk or S3)
|
|
- **LocalDiskProvider** in `server/src/storage/local-disk-provider.ts` writes files to `{instanceRoot}/data/storage/`
|
|
- Object keys follow `{companyId}/{namespace}/{year}/{month}/{day}/{uuid}-{stem}{ext}` pattern
|
|
- Storage service computes SHA-256 hash, validates inputs, sanitizes filenames
|
|
- `PutFileInput` requires: companyId, namespace, originalFilename, contentType, body (Buffer)
|
|
- `PutFileResult` returns: provider, objectKey, contentType, byteSize, sha256, originalFilename
|
|
|
|
### Asset Upload (existing reference)
|
|
- `server/src/routes/assets.ts` uses **multer v2** with memory storage for single file upload
|
|
- Pattern: `multer.memoryStorage()` + `limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 }`
|
|
- Manual `runSingleFileUpload` wrapper around multer's middleware
|
|
- After upload: calls `storage.putFile()`, then `assetService.create()` to write DB row
|
|
- Content served via `GET /api/assets/:assetId/content` which pipes `storage.getObject()` stream
|
|
- `MAX_ATTACHMENT_BYTES` defaults to 10MB (from `server/src/attachment-types.ts`)
|
|
- Allowed content types configurable via `PAPERCLIP_ALLOWED_ATTACHMENT_TYPES` env var
|
|
|
|
### Database Schema
|
|
- **Drizzle ORM** with PostgreSQL, migrations in `packages/db/src/migrations/` (currently at 0052)
|
|
- Schema files in `packages/db/src/schema/`, exported from `index.ts`
|
|
- Convention: `pgTable()` with `uuid("id").primaryKey().defaultRandom()`, timestamps with timezone
|
|
- Index callbacks use object syntax: `(table) => ({ indexName: index("idx_name").on(table.col) })`
|
|
- Chat tables: `chat_conversations`, `chat_messages`, `chat_message_bookmarks`
|
|
- `chat_messages` columns: id, conversationId, role, content, agentId, messageType, createdAt, updatedAt
|
|
|
|
### Chat Routes
|
|
- `server/src/routes/chat.ts` — `chatRoutes(db: Db)` returns Express Router
|
|
- Mounted at `/api` in `server/src/app.ts` line 160: `api.use(chatRoutes(db))`
|
|
- Currently does NOT receive `storageService` — only `db`
|
|
- Uses `assertBoard(req)` + `assertCompanyAccess(req, companyId)` for auth
|
|
- Uses validators from `@paperclipai/shared` (zod schemas)
|
|
- Message creation via `svc.addMessage(conversationId, { role, content, agentId })`
|
|
|
|
### Chat Service
|
|
- `server/src/services/chat.ts` — `chatService(db)` returns object with methods
|
|
- Methods: listConversations, createConversation, getConversation, updateConversation, addMessage, editMessage, truncateMessagesAfter, addSystemMessage, streamEcho, searchMessages, toggleBookmark, getBookmarks, branchConversation, listBranches, exportConversation
|
|
|
|
### Shared Types
|
|
- `packages/shared/src/types/chat.ts` — ChatConversation, ChatMessage, ChatConversationListResponse, etc.
|
|
- ChatMessage has: id, conversationId, role, content, agentId, messageType, createdAt, updatedAt
|
|
- NO file/attachment fields exist on ChatMessage yet
|
|
|
|
### UI Components
|
|
- `ChatInput.tsx` — textarea with slash commands and @mentions; `onSend(content: string)` callback (text only)
|
|
- `ChatMessage.tsx` — dispatches to specialized components by messageType (spec_card, handoff, etc.)
|
|
- `ChatPanel.tsx` — orchestrates conversation selection, message list, input, streaming, bookmarks
|
|
- `chatApi` in `ui/src/api/chat.ts` — API client using fetch-based `api` helper
|
|
|
|
### Migration Pattern
|
|
- SQL files named `NNNN_descriptive_name.sql`
|
|
- Journal in `packages/db/src/migrations/meta/_journal.json`
|
|
- Snapshots in `packages/db/src/migrations/meta/NNNN_snapshot.json`
|
|
- Next migration index: 0053
|
|
|
|
## Key Design Decisions (Claude's Discretion)
|
|
|
|
1. **Reuse existing StorageService** — Files will be stored via the same `StorageService` (local disk or S3) used by assets. This means files live in `{instanceRoot}/data/storage/` under company-namespaced object keys, NOT in a separate `files/` git repo. The git versioning requirement (FILE-09, FILE-10) is out of scope for this batch of requirements (FILE-01 through FILE-06 only).
|
|
|
|
2. **New `chat_files` table** — A new Drizzle schema table tracking file metadata: id, companyId, conversationId, messageId, filename, originalFilename, mimeType, sizeBytes, objectKey (StorageService key), sha256, source (user_upload | agent_generated), createdAt.
|
|
|
|
3. **New `chat_file_references` table** — Enables a file to be referenced from multiple messages/conversations without re-uploading the binary.
|
|
|
|
4. **Extend chat routes** — Add upload endpoint to chat routes (`POST /api/conversations/:id/files`) rather than creating a separate file routes module. This keeps the chat domain cohesive.
|
|
|
|
5. **Extend ChatMessage type** — Add optional `files` array to ChatMessage response (populated via join or separate query). Messages with files get `messageType: "file_attachment"` or files are included as metadata alongside normal messages.
|
|
|
|
6. **ChatInput gets file drop zone** — Add drag-and-drop, paste, and button-based file selection to ChatInput. Files are uploaded immediately on drop/paste, and the pending file IDs are sent with the message.
|
|
|
|
7. **ChatFilePreview component** — Renders inline image previews, syntax-highlighted code previews (reusing existing rehype-highlight from Phase 21), and download buttons for other file types.
|