Skip to main content

File upload

src/upload/ provides image upload for agent icons, screenshots, and avatars.

Endpoint

POST /uploadmultipart/form-data, single file field. Returns:

{
"data": { "url": "https://cdn.example.com/agents/icon-abc.png" }
}

Storage providers

The module is provider-agnostic. The current production deployment uses S3 / R2 / Cloudflare Images depending on environment. Provider selection lives in the upload service's constructor, driven by env vars like STORAGE_PROVIDER, S3_BUCKET, etc.

To swap providers, edit the upload service and add the new SDK + env vars. Don't sprinkle provider-specific code in callers.

Validation

CheckConstraint
MIME typeimage/png, image/jpeg, image/webp, image/svg+xml
SizeDefault 5 MB (configurable via env)
FilenameRandom UUID-based, never user-controlled

Reject everything else with 422 UNPROCESSABLE_ENTITY.

Usage from frontend

fleapo-marketplace/src/api/upload/ is generated by Orval. Wrap it with a hook in the admin form so the URL flows back into the agent's iconUrl / photoUrls[] automatically.

Public URLs

All uploads are publicly readable. The marketplace UI loads them via <img src>, so the storage bucket is fronted by a CDN with public-read.

If you need authenticated assets (e.g. signed contracts), build a separate module that returns short-lived signed URLs.

Garbage collection

No automatic cleanup. Replacing an agent's icon leaves the old object stranded. Build scripts/gc-uploads.ts when this becomes a cost issue: walk agents.iconUrl + photoUrls + capabilities.icon, collect referenced object keys, delete unreferenced keys.

Why a separate module

Keeping uploads in one place makes future improvements (image resizing, format conversion, CDN URL rewriting) a single-file change.