Files
taskplaner/.planning/phases/03-images/03-01-PLAN.md
Thomas Richter 04c2742e73 docs(03): create phase plan
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>
2026-01-29 15:15:30 +01:00

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
src/lib/server/db/schema.ts
src/lib/server/db/repository.ts
src/lib/server/images/storage.ts
src/lib/server/images/thumbnails.ts
src/routes/api/images/[id]/+server.ts
src/routes/api/images/[id]/thumbnail/+server.ts
true
truths artifacts key_links
Images table exists in database with entryId foreign key
Image files can be written to filesystem
Thumbnails can be generated from uploaded images
Images can be served via API endpoints
path provides contains
src/lib/server/db/schema.ts images table definition images = sqliteTable
path provides contains
src/lib/server/db/repository.ts imageRepository with CRUD operations imageRepository
path provides exports
src/lib/server/images/storage.ts File write/read operations
saveOriginal
saveThumbnail
deleteImage
path provides exports
src/lib/server/images/thumbnails.ts Sharp thumbnail generation
generateThumbnail
path provides exports
src/routes/api/images/[id]/+server.ts GET endpoint for original images
GET
path provides exports
src/routes/api/images/[id]/thumbnail/+server.ts GET endpoint for thumbnails
GET
from to via pattern
src/lib/server/images/thumbnails.ts sharp import sharp import sharp from 'sharp'
from to via pattern
src/routes/api/images/[id]/+server.ts src/lib/server/db/repository.ts imageRepository.getById imageRepository.getById
Create the foundation layer for image attachments: database schema, file storage utilities, thumbnail generation, and API endpoints for serving images.

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`
  1. 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.

  2. 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)
  3. 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.

Task 2: File storage and thumbnail generation utilities src/lib/server/images/storage.ts src/lib/server/images/thumbnails.ts 1. Create src/lib/server/images/storage.ts: - UPLOAD_DIR constant: 'data/uploads' - ORIGINALS_DIR: 'data/uploads/originals' - THUMBNAILS_DIR: 'data/uploads/thumbnails'

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)
  1. 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.
Task 3: API endpoints for serving images src/routes/api/images/[id]/+server.ts src/routes/api/images/[id]/thumbnail/+server.ts 1. Create src/routes/api/images/[id]/+server.ts: - GET handler that: a. Gets id from params b. Looks up image in imageRepository.getById(id) c. Returns 404 error if not found d. Reads file from getOriginalPath(id, image.ext) e. Returns Response with: - Content-Type based on ext (jpg -> image/jpeg, png -> image/png, etc.) - Content-Length header - Cache-Control: public, max-age=31536000, immutable (images are immutable by ID) f. Catches file read errors, returns 404
  1. 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.

1. `npm run check` passes with no TypeScript errors 2. `npm run dev` starts without errors 3. Sharp is in dependencies: `grep sharp package.json` 4. All new files exist: - src/lib/server/db/schema.ts contains `images = sqliteTable` - src/lib/server/db/repository.ts exports `imageRepository` - src/lib/server/images/storage.ts exists - src/lib/server/images/thumbnails.ts exists - src/routes/api/images/[id]/+server.ts exists - src/routes/api/images/[id]/thumbnail/+server.ts exists

<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>
After completion, create `.planning/phases/03-images/03-01-SUMMARY.md`