Phase 03: Images - 4 plan(s) in 3 wave(s) - Wave 1: 03-01 (foundation) - Wave 2: 03-02, 03-03 (parallel - upload + camera) - Wave 3: 03-04 (integration + verification) - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7.9 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 03-images | 01 | execute | 1 |
|
true |
|
Purpose: Establishes the data model and infrastructure that upload components will use. Without this layer, images cannot be stored, processed, or retrieved.
Output:
- Database table for images with entry relationship
- Repository layer for image CRUD
- Filesystem utilities for storing originals and thumbnails
- API endpoints that serve images to the browser
<execution_context> @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/03-images/03-RESEARCH.md @src/lib/server/db/schema.ts @src/lib/server/db/repository.ts @src/lib/server/db/index.ts Task 1: Database schema and repository for images src/lib/server/db/schema.ts src/lib/server/db/repository.ts 1. Install sharp: `npm install sharp`-
Add images table to schema.ts:
- id: text primary key (nanoid)
- entryId: text foreign key to entries.id (not null)
- filename: text (original filename for display)
- ext: text (file extension without dot: jpg, png, etc.)
- createdAt: text ISO timestamp
Export Image and NewImage types.
-
Add ImageRepository interface and implementation to repository.ts:
- create(image: Omit<NewImage, 'id' | 'createdAt'>): Image
- getById(id: string): Image | undefined
- getByEntryId(entryId: string): Image[]
- delete(id: string): boolean
- deleteByEntryId(entryId: string): number (returns count deleted)
-
Export imageRepository singleton alongside entryRepository.
Use same patterns as existing entryRepository (nanoid for IDs, synchronous Drizzle operations).
Run npm run check - TypeScript compiles without errors.
Verify exports: grep -n "imageRepository" src/lib/server/db/repository.ts
images table defined with entryId relationship.
imageRepository exported with create, getById, getByEntryId, delete, deleteByEntryId methods.
Functions:
- ensureDirectories(): Creates directories if they don't exist (use mkdir recursive)
- saveOriginal(id: string, ext: string, buffer: Buffer): Promise
Writes to
${ORIGINALS_DIR}/${id}.${ext} - saveThumbnail(id: string, buffer: Buffer): Promise
Writes to
${THUMBNAILS_DIR}/${id}.jpg(thumbnails always jpg) - getOriginalPath(id: string, ext: string): string
- getThumbnailPath(id: string): string
- deleteImage(id: string, ext: string): Promise Removes both original and thumbnail files (use try/catch, don't throw if missing)
-
Create src/lib/server/images/thumbnails.ts:
- import sharp from 'sharp'
Function:
- generateThumbnail(buffer: Buffer, size?: number): Promise
Default size: 150
CRITICAL: Call sharp.rotate() FIRST to handle EXIF orientation from mobile photos
Then resize with fit: 'cover', position: 'center', withoutEnlargement: true
Output as jpeg with quality 80
Return the thumbnail buffer (caller saves it)
Run
npm run check- TypeScript compiles. Run quick test:node -e "import('sharp').then(s => console.log('sharp version:', s.default.versions?.sharp || 'loaded'))"or just verify sharp installed in package.json. storage.ts exports directory management and file I/O functions. thumbnails.ts exports generateThumbnail that handles EXIF rotation.
- Create src/routes/api/images/[id]/thumbnail/+server.ts:
- Same pattern but reads from getThumbnailPath(id)
- Content-Type always image/jpeg (thumbnails are always jpg)
Import imageRepository and storage functions. Use readFile from 'node:fs/promises'.
Run npm run check - TypeScript compiles.
Verify endpoints exist: ls -la src/routes/api/images/
GET /api/images/[id] serves original images with proper Content-Type.
GET /api/images/[id]/thumbnail serves thumbnails.
Both return 404 for missing images.
<success_criteria>
- images table can store image metadata with entry relationship
- imageRepository provides typed CRUD for images
- Thumbnail generation uses Sharp with EXIF auto-rotation
- API endpoints can serve images from filesystem
- All code compiles without TypeScript errors </success_criteria>