# Phase 17: Object Storage - Context **Gathered:** 2026-04-05 **Status:** Ready for planning ## Phase Boundary Move image storage from local filesystem (`uploads/` directory) to MinIO (S3-compatible object storage). All image uploads go to MinIO. Existing images are migrated. Image URLs are served via presigned URLs or a proxy. Docker Compose includes MinIO for local development. ## Implementation Decisions ### S3 Client - **D-01:** Use `@aws-sdk/client-s3` (AWS SDK v3) for MinIO communication. Works with any S3-compatible service. Tree-shakeable, well-maintained. - **D-02:** Use `@aws-sdk/s3-request-presigner` for generating presigned URLs. ### Storage Service - **D-03:** Create `src/server/services/storage.service.ts` — thin wrapper around S3 SDK with functions: `uploadImage(buffer, filename, contentType)`, `deleteImage(filename)`, `getImageUrl(filename)`. - **D-04:** `getImageUrl()` returns a presigned URL with configurable expiry (default 1 hour). No proxy — client fetches directly from MinIO. - **D-05:** Environment variables: `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_BUCKET` (default: `gearbox-images`), `S3_REGION` (default: `us-east-1`). ### Image Upload Changes - **D-06:** `POST /api/images` and `POST /api/images/from-url` upload to MinIO instead of local filesystem. Same filename pattern (UUID-based). - **D-07:** `fetchImageFromUrl()` in image.service.ts uploads the fetched buffer to MinIO instead of writing to disk. - **D-08:** Remove the static file serving for `/uploads/*` from the server — images are served via presigned URLs from MinIO. ### Image URL Serving - **D-09:** When returning item/candidate data with `imageFilename`, the API resolves it to a presigned MinIO URL. Add a `imageUrl` field to API responses (or replace `imageFilename` with the URL). - **D-10:** Client components use the presigned URL directly. No changes to image display components beyond using the URL field. ### Migration - **D-11:** One-time migration script (`scripts/migrate-images-to-minio.ts`) reads all files from `uploads/`, uploads each to MinIO bucket, verifies upload, logs progress. - **D-12:** No filename changes during migration — existing `imageFilename` values in the database remain valid as MinIO object keys. ### Docker Compose - **D-13:** MinIO service in docker-compose.yml (both dev and prod) with automatic bucket creation on startup. - **D-14:** Dev compose uses fixed credentials for simplicity. Prod compose uses env vars. ### Claude's Discretion - Presigned URL expiry duration (1h default, configurable) - Whether to add a GET /api/images/:filename proxy endpoint as fallback - MinIO Docker image version - Bucket policy (private with presigned URLs vs public-read) - Whether to delete local files after successful migration - Error handling strategy for upload failures ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Image Handling (to be refactored) - `src/server/services/image.service.ts` — Current local file storage logic - `src/server/routes/images.ts` — Upload endpoints (file + URL) - `src/server/index.ts` — Static file serving for uploads/ ### Schema (imageFilename columns) - `src/db/schema.ts` — `imageFilename` on items and threadCandidates tables ### Docker - `docker-compose.yml` — Production compose (add MinIO) - `docker-compose.dev.yml` — Dev compose (add MinIO) - `.env.example` — Add S3 env vars ### Client (image display) - `src/client/lib/api.ts` — API wrapper - `src/client/components/` — Components that display images ### Requirements - `.planning/REQUIREMENTS.md` — IMG-01 through IMG-04 ## Existing Code Insights ### Reusable Assets - `image.service.ts` — refactor to use storage service instead of local fs - UUID filename generation pattern — keep as-is for MinIO object keys - Content type validation — keep in image routes ### Established Patterns - Service DI pattern (db, userId params) — storage service is stateless, no db needed - Async operations — all storage ops will be async (already async pattern) - Environment config via `process.env` — same for S3 config ### Integration Points - `src/server/routes/images.ts` — Replace Bun.write with storage.upload - `src/server/services/image.service.ts` — Replace Bun.write with storage.upload - `src/server/index.ts` — Remove static file serving for uploads/ - API response serialization — add imageUrl field from presigned URL ## Specific Ideas No specific requirements — open to standard approaches for S3-compatible object storage with MinIO. ## Deferred Ideas None — discussion stayed within phase scope --- *Phase: 17-object-storage* *Context gathered: 2026-04-05*