Plans cover FILE-01 through FILE-06: DB schema + shared types (wave 1), server file service + routes and UI file upload (wave 2, parallel), file preview components + full wiring (wave 3). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
311 lines
13 KiB
Markdown
311 lines
13 KiB
Markdown
---
|
|
phase: 25-file-system
|
|
plan: 00
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- packages/db/src/schema/chat_files.ts
|
|
- packages/db/src/schema/chat_file_references.ts
|
|
- packages/db/src/schema/index.ts
|
|
- packages/db/src/migrations/0053_create_chat_files.sql
|
|
- packages/db/src/migrations/0054_create_chat_file_references.sql
|
|
- packages/shared/src/types/chat.ts
|
|
- packages/shared/src/validators/chat.ts
|
|
- packages/shared/src/index.ts
|
|
- server/src/__tests__/chat-file-service.test.ts
|
|
- server/src/__tests__/chat-file-routes.test.ts
|
|
autonomous: true
|
|
requirements:
|
|
- FILE-01
|
|
- FILE-02
|
|
- FILE-03
|
|
must_haves:
|
|
truths:
|
|
- "chat_files table exists in Postgres with all required metadata columns"
|
|
- "chat_file_references table exists enabling cross-conversation file references"
|
|
- "Shared types and validators for file operations are exported from @paperclipai/shared"
|
|
artifacts:
|
|
- path: "packages/db/src/schema/chat_files.ts"
|
|
provides: "chat_files Drizzle schema table"
|
|
contains: "pgTable"
|
|
- path: "packages/db/src/schema/chat_file_references.ts"
|
|
provides: "chat_file_references Drizzle schema table"
|
|
contains: "pgTable"
|
|
- path: "packages/shared/src/types/chat.ts"
|
|
provides: "ChatFile and ChatFileReference types"
|
|
contains: "ChatFile"
|
|
key_links:
|
|
- from: "packages/db/src/schema/chat_files.ts"
|
|
to: "packages/db/src/schema/chat_conversations.ts"
|
|
via: "FK reference conversationId"
|
|
pattern: "chatConversations"
|
|
- from: "packages/db/src/schema/chat_file_references.ts"
|
|
to: "packages/db/src/schema/chat_files.ts"
|
|
via: "FK reference fileId"
|
|
pattern: "chatFiles"
|
|
---
|
|
|
|
<objective>
|
|
Create the database schema, shared types, validators, and test stubs for the chat file system.
|
|
|
|
Purpose: Establish the data layer contracts that all subsequent plans (upload routes, UI previews) build against.
|
|
Output: Two new DB tables (chat_files, chat_file_references), shared TypeScript types, Zod validators, and test scaffolds.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/25-file-system/25-RESEARCH.md
|
|
|
|
@packages/db/src/schema/chat_messages.ts
|
|
@packages/db/src/schema/chat_conversations.ts
|
|
@packages/db/src/schema/assets.ts
|
|
@packages/db/src/schema/index.ts
|
|
@packages/shared/src/types/chat.ts
|
|
@packages/shared/src/validators/chat.ts
|
|
@server/src/__tests__/chat-routes.test.ts
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Create chat_files and chat_file_references DB schema + migrations</name>
|
|
<files>
|
|
packages/db/src/schema/chat_files.ts,
|
|
packages/db/src/schema/chat_file_references.ts,
|
|
packages/db/src/schema/index.ts,
|
|
packages/db/src/migrations/0053_create_chat_files.sql,
|
|
packages/db/src/migrations/0054_create_chat_file_references.sql
|
|
</files>
|
|
<read_first>
|
|
packages/db/src/schema/chat_messages.ts,
|
|
packages/db/src/schema/chat_conversations.ts,
|
|
packages/db/src/schema/assets.ts,
|
|
packages/db/src/schema/index.ts,
|
|
packages/db/src/migrations/meta/_journal.json
|
|
</read_first>
|
|
<action>
|
|
Create `packages/db/src/schema/chat_files.ts` with a `chatFiles` pgTable containing:
|
|
- id: uuid PK defaultRandom
|
|
- companyId: uuid NOT NULL FK to companies.id
|
|
- conversationId: uuid FK to chatConversations.id (nullable — file may exist without conversation)
|
|
- messageId: uuid FK to chatMessages.id ON DELETE SET NULL (nullable — file may be uploaded before message is sent)
|
|
- filename: text NOT NULL (display filename, may differ from original)
|
|
- originalFilename: text NOT NULL (as uploaded)
|
|
- mimeType: text NOT NULL
|
|
- sizeBytes: integer NOT NULL
|
|
- objectKey: text NOT NULL (StorageService object key)
|
|
- sha256: text NOT NULL
|
|
- source: text NOT NULL (values: 'user_upload', 'agent_generated')
|
|
- category: text (values: 'image', 'document', 'code', 'other' — nullable, derived from mimeType)
|
|
- projectId: uuid FK to projects.id ON DELETE SET NULL (nullable — for dual scoping FILE-04)
|
|
- createdAt: timestamp with timezone NOT NULL defaultNow
|
|
- updatedAt: timestamp with timezone NOT NULL defaultNow
|
|
|
|
Indexes (use object-syntax callback like existing chat schemas):
|
|
- chat_files_conversation_idx on (conversationId)
|
|
- chat_files_message_idx on (messageId)
|
|
- chat_files_company_created_idx on (companyId, createdAt)
|
|
- chat_files_project_idx on (projectId)
|
|
|
|
Create `packages/db/src/schema/chat_file_references.ts` with a `chatFileReferences` pgTable:
|
|
- id: uuid PK defaultRandom
|
|
- fileId: uuid NOT NULL FK to chatFiles.id ON DELETE CASCADE
|
|
- conversationId: uuid NOT NULL FK to chatConversations.id ON DELETE CASCADE
|
|
- messageId: uuid FK to chatMessages.id ON DELETE SET NULL (nullable)
|
|
- createdAt: timestamp with timezone NOT NULL defaultNow
|
|
|
|
Indexes:
|
|
- chat_file_refs_file_idx on (fileId)
|
|
- chat_file_refs_conversation_idx on (conversationId)
|
|
- chat_file_refs_message_idx on (messageId)
|
|
|
|
Add exports to `packages/db/src/schema/index.ts`:
|
|
- export { chatFiles } from "./chat_files.js";
|
|
- export { chatFileReferences } from "./chat_file_references.js";
|
|
|
|
Create migration SQL files:
|
|
- 0053_create_chat_files.sql: CREATE TABLE chat_files with all columns and indexes
|
|
- 0054_create_chat_file_references.sql: CREATE TABLE chat_file_references with all columns and indexes
|
|
|
|
Update `packages/db/src/migrations/meta/_journal.json` to add entries for idx 53 and 54.
|
|
Copy the latest snapshot JSON file as a template for the new snapshots (0053_snapshot.json, 0054_snapshot.json) — but note that the migration system only strictly requires the SQL files and journal entries; skip snapshot creation if too complex.
|
|
|
|
IMPORTANT: Follow existing index callback pattern from chat_messages.ts — use `(table) => ({})` object syntax, not array syntax.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && grep -q "chatFiles" packages/db/src/schema/index.ts && grep -q "chatFileReferences" packages/db/src/schema/index.ts && grep -q "chat_files" packages/db/src/migrations/0053_create_chat_files.sql && grep -q "chat_file_references" packages/db/src/migrations/0054_create_chat_file_references.sql && echo "PASS"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep "chatFiles" packages/db/src/schema/index.ts returns a match
|
|
- grep "chatFileReferences" packages/db/src/schema/index.ts returns a match
|
|
- grep "pgTable" packages/db/src/schema/chat_files.ts returns a match
|
|
- grep "pgTable" packages/db/src/schema/chat_file_references.ts returns a match
|
|
- grep "CREATE TABLE" packages/db/src/migrations/0053_create_chat_files.sql returns a match
|
|
- grep "CREATE TABLE" packages/db/src/migrations/0054_create_chat_file_references.sql returns a match
|
|
- grep "company_id" packages/db/src/schema/chat_files.ts returns a match (dual scoping support)
|
|
- grep "project_id" packages/db/src/schema/chat_files.ts returns a match (FILE-04 support)
|
|
- grep "source" packages/db/src/schema/chat_files.ts returns a match
|
|
</acceptance_criteria>
|
|
<done>
|
|
chat_files and chat_file_references Drizzle schemas exist with all columns, indexes, FKs.
|
|
Migration SQL files exist. Schema index re-exports both tables.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Add shared types, validators, and test stubs</name>
|
|
<files>
|
|
packages/shared/src/types/chat.ts,
|
|
packages/shared/src/validators/chat.ts,
|
|
packages/shared/src/index.ts,
|
|
server/src/__tests__/chat-file-service.test.ts,
|
|
server/src/__tests__/chat-file-routes.test.ts
|
|
</files>
|
|
<read_first>
|
|
packages/shared/src/types/chat.ts,
|
|
packages/shared/src/validators/chat.ts,
|
|
packages/shared/src/index.ts,
|
|
server/src/__tests__/chat-routes.test.ts
|
|
</read_first>
|
|
<action>
|
|
Extend `packages/shared/src/types/chat.ts` — add these interfaces (append, do NOT modify existing interfaces):
|
|
|
|
```typescript
|
|
export interface ChatFile {
|
|
id: string;
|
|
companyId: string;
|
|
conversationId: string | null;
|
|
messageId: string | null;
|
|
filename: string;
|
|
originalFilename: string;
|
|
mimeType: string;
|
|
sizeBytes: number;
|
|
objectKey: string;
|
|
sha256: string;
|
|
source: "user_upload" | "agent_generated";
|
|
category: "image" | "document" | "code" | "other" | null;
|
|
projectId: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface ChatFileReference {
|
|
id: string;
|
|
fileId: string;
|
|
conversationId: string;
|
|
messageId: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface ChatFileUploadResponse {
|
|
file: ChatFile;
|
|
contentPath: string;
|
|
}
|
|
|
|
export interface ChatFileListResponse {
|
|
items: ChatFile[];
|
|
}
|
|
```
|
|
|
|
Also extend `ChatMessage` by adding an optional `files` field:
|
|
```typescript
|
|
// In ChatMessage interface, add:
|
|
files?: ChatFile[];
|
|
```
|
|
|
|
Extend `packages/shared/src/validators/chat.ts` — add Zod schemas:
|
|
```typescript
|
|
export const uploadChatFileSchema = z.object({
|
|
conversationId: z.string().uuid().optional(),
|
|
messageId: z.string().uuid().optional(),
|
|
source: z.enum(["user_upload", "agent_generated"]).default("user_upload"),
|
|
projectId: z.string().uuid().optional(),
|
|
});
|
|
|
|
export const createFileReferenceSchema = z.object({
|
|
fileId: z.string().uuid(),
|
|
messageId: z.string().uuid().optional(),
|
|
});
|
|
```
|
|
|
|
Ensure new schemas and types are re-exported from `packages/shared/src/index.ts` — check existing export pattern in that file and add:
|
|
- The new type exports (ChatFile, ChatFileReference, ChatFileUploadResponse, ChatFileListResponse)
|
|
- The new validator exports (uploadChatFileSchema, createFileReferenceSchema)
|
|
|
|
Create test stubs:
|
|
|
|
`server/src/__tests__/chat-file-service.test.ts`:
|
|
```typescript
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("chatFileService", () => {
|
|
it.todo("creates a file record after upload");
|
|
it.todo("lists files for a conversation");
|
|
it.todo("lists files for a message");
|
|
it.todo("creates a file reference in another conversation");
|
|
it.todo("returns file with contentPath");
|
|
});
|
|
```
|
|
|
|
`server/src/__tests__/chat-file-routes.test.ts`:
|
|
```typescript
|
|
import { describe, it } from "vitest";
|
|
|
|
describe("chatFileRoutes", () => {
|
|
it.todo("POST /conversations/:id/files uploads a file and returns 201");
|
|
it.todo("GET /conversations/:id/files lists files for conversation");
|
|
it.todo("GET /files/:fileId/content serves file content");
|
|
it.todo("POST /files/:fileId/references creates a cross-conversation reference");
|
|
it.todo("rejects upload when file exceeds size limit");
|
|
it.todo("rejects upload when content type is not allowed");
|
|
});
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && npx tsc --noEmit -p packages/shared/tsconfig.json 2>&1 | tail -5 && grep -q "ChatFile" packages/shared/src/types/chat.ts && grep -q "uploadChatFileSchema" packages/shared/src/validators/chat.ts && echo "PASS"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep "ChatFile" packages/shared/src/types/chat.ts returns a match
|
|
- grep "ChatFileReference" packages/shared/src/types/chat.ts returns a match
|
|
- grep "ChatFileUploadResponse" packages/shared/src/types/chat.ts returns a match
|
|
- grep "files?" packages/shared/src/types/chat.ts returns a match (optional files on ChatMessage)
|
|
- grep "uploadChatFileSchema" packages/shared/src/validators/chat.ts returns a match
|
|
- grep "createFileReferenceSchema" packages/shared/src/validators/chat.ts returns a match
|
|
- grep "it.todo" server/src/__tests__/chat-file-service.test.ts returns matches
|
|
- grep "it.todo" server/src/__tests__/chat-file-routes.test.ts returns matches
|
|
- TypeScript compilation of shared package succeeds
|
|
</acceptance_criteria>
|
|
<done>
|
|
ChatFile and related types exported from shared. Zod validators for file upload and reference creation exported.
|
|
ChatMessage type has optional files array. Test stubs exist for service and routes.
|
|
</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `packages/db/src/schema/chat_files.ts` exists with pgTable definition
|
|
- `packages/db/src/schema/chat_file_references.ts` exists with pgTable definition
|
|
- Both tables exported from schema index
|
|
- SQL migration files exist for both tables
|
|
- `ChatFile` type exported from shared
|
|
- `uploadChatFileSchema` validator exported from shared
|
|
- Test stubs have `.todo()` tests for service and routes
|
|
- TypeScript compiles without errors in shared package
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Database schema for file tracking is defined with all columns needed for FILE-01 (directory/storage), FILE-02 (metadata), FILE-03 (cross-references), and FILE-04 (dual scoping via projectId). Shared types provide the contract for Plans 01-03.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/25-file-system/25-00-SUMMARY.md`
|
|
</output>
|