Files
GearBox/.planning/phases/17-object-storage/17-02-PLAN.md

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
17-object-storage 02 execute 2
17-01
src/server/services/image.service.ts
src/server/routes/images.ts
src/server/routes/items.ts
src/server/routes/threads.ts
src/server/routes/setups.ts
src/server/index.ts
src/server/mcp/tools/items.ts
src/server/mcp/tools/threads.ts
src/server/mcp/tools/images.ts
tests/services/image.service.test.ts
tests/routes/images.test.ts
true
IMG-01
IMG-03
truths artifacts key_links
Image upload via POST /api/images stores file in MinIO, not local filesystem
Image upload via POST /api/images/from-url stores fetched image in MinIO
Deleting an item or candidate with an image deletes the image from MinIO
API responses include imageUrl field with presigned URLs for items and candidates
Static file serving for /uploads/* is removed from the server
MCP tools use storage service for image operations
path provides
src/server/services/image.service.ts URL-based image fetch using storage service
path provides
src/server/routes/images.ts Image upload routes using storage service
path provides
src/server/index.ts Server entry without /uploads/* static serving
from to via pattern
src/server/routes/images.ts src/server/services/storage.service.ts uploadImage() call import.*uploadImage.*storage
from to via pattern
src/server/routes/items.ts src/server/services/storage.service.ts deleteImage() for cleanup, withImageUrl/withImageUrls for responses import.*deleteImage.*storage
from to via pattern
src/server/routes/threads.ts src/server/services/storage.service.ts deleteImage() and withImageUrl for candidate images import.*storage
Refactor all server-side image handling to use the S3 storage service instead of local filesystem.

Purpose: Replace every Bun.write, unlink, and /uploads/ reference on the server with storage service calls. Enrich API responses with presigned URLs so clients can fetch images directly from MinIO. Output: All server image operations go through storage.service.ts. API responses include imageUrl field. Static /uploads/* serving removed.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/17-object-storage/17-CONTEXT.md @.planning/phases/17-object-storage/17-RESEARCH.md @.planning/phases/17-object-storage/17-01-SUMMARY.md

@src/server/services/image.service.ts @src/server/routes/images.ts @src/server/routes/items.ts @src/server/routes/threads.ts @src/server/index.ts @src/server/mcp/tools/images.ts @src/server/mcp/tools/items.ts @src/server/mcp/tools/threads.ts

From src/server/services/storage.service.ts: ```typescript export async function uploadImage(buffer: Buffer | ArrayBuffer, filename: string, contentType: string): Promise; export async function deleteImage(filename: string): Promise; export async function getImageUrl(filename: string): Promise; export async function withImageUrl(record: T): Promise; export async function withImageUrls(records: T[]): Promise<(T & { imageUrl: string | null })[]>; ``` Task 1: Refactor image service and image routes to use storage service src/server/services/image.service.ts, src/server/routes/images.ts, tests/services/image.service.test.ts, tests/routes/images.test.ts - src/server/services/storage.service.ts (created in Plan 01 — the storage API) - src/server/services/image.service.ts (current local fs logic to replace) - src/server/routes/images.ts (current upload routes) - tests/services/image.service.test.ts (existing tests to update) - tests/routes/images.test.ts (existing tests to update) 1. Refactor `src/server/services/image.service.ts` per D-07: - Remove `mkdir` and `Bun.write` imports - Remove `uploadsDir` parameter from `fetchImageFromUrl` - Import `uploadImage` from `./storage.service` - After fetching and validating the image buffer, call `await uploadImage(Buffer.from(buffer), filename, contentType)` instead of `Bun.write` - Keep ALL validation logic unchanged (URL parsing, protocol check, content type, size limits, timeout) - Keep UUID filename generation unchanged per D-12
  1. Refactor src/server/routes/images.ts per D-06:

    • Remove mkdir, join, Bun.write usage
    • Import uploadImage from ../services/storage.service
    • In POST / handler: after validation, call await uploadImage(Buffer.from(buffer), filename, file.type) instead of mkdir + Bun.write
    • In POST /from-url handler: no changes needed (delegates to image.service.ts which is already refactored)
    • Keep content type validation and size validation unchanged
    • Keep filename generation pattern unchanged
  2. Update tests/services/image.service.test.ts:

    • Mock the storage.service module using mock.module so uploadImage is a mock function
    • Update assertions: verify uploadImage was called with correct buffer, filename, contentType instead of checking Bun.write
    • Remove any assertions about local filesystem writes
  3. Update tests/routes/images.test.ts:

    • Mock storage.service module
    • Update assertions to verify uploadImage calls instead of filesystem writes
    • Test that POST /api/images returns { filename } with 201 status
    • Test that POST /api/images/from-url returns { filename, sourceUrl } with 201 status bun test tests/services/image.service.test.ts tests/routes/images.test.ts <acceptance_criteria>
    • grep -q "import.*uploadImage.*storage" src/server/services/image.service.ts
    • grep -qv "Bun.write" src/server/services/image.service.ts
    • grep -qv "mkdir" src/server/services/image.service.ts
    • grep -q "import.*uploadImage.*storage" src/server/routes/images.ts
    • grep -qv "Bun.write" src/server/routes/images.ts
    • grep -qv "mkdir" src/server/routes/images.ts
    • bun test tests/services/image.service.test.ts passes
    • bun test tests/routes/images.test.ts passes </acceptance_criteria> Image service and routes use storage service for uploads. No local filesystem writes remain. Tests pass with mocked storage.
Task 2: Refactor item/thread/setup routes, remove static serving, update MCP tools src/server/routes/items.ts, src/server/routes/threads.ts, src/server/routes/setups.ts, src/server/index.ts, src/server/mcp/tools/items.ts, src/server/mcp/tools/threads.ts, src/server/mcp/tools/images.ts - src/server/services/storage.service.ts (withImageUrl, withImageUrls, deleteImage APIs) - src/server/routes/items.ts (unlink usage on delete, response patterns) - src/server/routes/threads.ts (unlink usage on delete, response patterns) - src/server/routes/setups.ts (response patterns for setup items with images) - src/server/index.ts (serveStatic for /uploads/*) - src/server/mcp/tools/items.ts (MCP tool response patterns) - src/server/mcp/tools/threads.ts (MCP tool response patterns) - src/server/mcp/tools/images.ts (fetchImageFromUrl usage) 1. Refactor `src/server/routes/items.ts` per D-08, D-09: - Remove `unlink` and `join` imports related to uploads - Import `deleteImage`, `withImageUrl`, `withImageUrls` from `../services/storage.service` - On item delete: replace `unlink(join("uploads", deleted.imageFilename))` with `await deleteImage(deleted.imageFilename)` - On GET single item: wrap response with `withImageUrl()` before returning - On GET list items: wrap response array with `withImageUrls()` before returning - Keep try/catch around deleteImage (missing object is not an error, same as current pattern)
  1. Refactor src/server/routes/threads.ts per D-08, D-09:

    • Remove unlink and join imports related to uploads
    • Import deleteImage, withImageUrl, withImageUrls from ../services/storage.service
    • On thread delete (where candidate images are cleaned up): replace unlink(join("uploads", filename)) with await deleteImage(filename) in the loop
    • On candidate delete: replace unlink(join("uploads", deleted.imageFilename)) with await deleteImage(deleted.imageFilename)
    • On GET thread with candidates: enrich candidate records with withImageUrls() before returning
    • On GET thread list: if threads include image data, enrich accordingly
  2. Refactor src/server/routes/setups.ts per D-09:

    • Import withImageUrls from ../services/storage.service
    • On GET setup detail (which includes items with imageFilename): enrich the items array with withImageUrls() before returning
    • On GET setup list: if list includes items with images, enrich accordingly
  3. Update src/server/index.ts per D-08:

    • Remove the line app.use("/uploads/*", serveStatic({ root: "./" })) entirely
    • Remove serveStatic import if no longer used elsewhere (check — it IS still used for production SPA serving)
    • Actually: serveStatic is still used for SPA serving in production. Only remove the /uploads/* line.
  4. Update MCP tools per D-09:

    • src/server/mcp/tools/items.ts: After getting items from service, enrich with withImageUrl/withImageUrls before returning in tool response
    • src/server/mcp/tools/threads.ts: After getting thread with candidates, enrich candidate images with withImageUrls
    • src/server/mcp/tools/images.ts: No changes needed if it calls fetchImageFromUrl (already refactored in Task 1). Verify it does not reference local filesystem directly. grep -rn "unlink.*uploads|Bun.write.*uploads|/uploads/" src/server/ | grep -v node_modules | grep -v ".test." && echo "FAIL: still has uploads references" || echo "PASS: no uploads references in server" <acceptance_criteria>
    • grep -qv "unlink.*uploads" src/server/routes/items.ts
    • grep -q "deleteImage" src/server/routes/items.ts
    • grep -q "withImageUrl" src/server/routes/items.ts
    • grep -qv "unlink.*uploads" src/server/routes/threads.ts
    • grep -q "deleteImage" src/server/routes/threads.ts
    • grep -qv 'uploads/*.*serveStatic' src/server/index.ts
    • grep -q "withImageUrl" src/server/mcp/tools/items.ts
    • No remaining references to "unlink.uploads" or "/uploads/" in src/server/ (excluding test files) </acceptance_criteria> All server routes use storage service for image deletion and URL generation. Static /uploads/ serving removed. MCP tools return presigned URLs. Zero local filesystem image references remain in server code.
- `grep -rn "/uploads/" src/server/ | grep -v node_modules` returns NO matches (all server references removed) - `grep -rn "Bun.write" src/server/ | grep -v node_modules` returns NO image-related matches - `grep -rn "unlink.*uploads" src/server/` returns NO matches - `bun test tests/services/image.service.test.ts tests/routes/images.test.ts` passes - `bun run lint` passes (no unused imports from removed code)

<success_criteria>

  • All image uploads go through storage.service.ts (no Bun.write to uploads/)
  • All image deletions go through storage.service.ts (no unlink of uploads/)
  • All API responses with images include imageUrl presigned URL field
  • Static /uploads/* serving removed from server
  • MCP tools return presigned URLs
  • All existing image-related tests pass with mocked storage </success_criteria>
After completion, create `.planning/phases/17-object-storage/17-02-SUMMARY.md`