# Feature Specification: API Image Proxy **Feature Branch**: `002-api-image-proxy` **Created**: 2026-05-03 **Status**: Draft **Input**: User description: "Instead of directly exposing the Minio storage, proxy fetching images through the API. The API server should talk directly to the S3 backend." ## User Scenarios & Testing *(mandatory)* ### User Story 1 — View Images Without Storage Configuration (Priority: P1) A user opens the application on any network without any special DNS or host-file configuration. All images — both thumbnails in the library grid and the full-size image on the detail page — load correctly. **Why this priority**: This is the core motivator for the feature. Direct storage exposure required `/etc/hosts` hacks to resolve the internal storage hostname; the proxy removes this friction entirely and makes the application work out-of-the-box. **Independent Test**: Start the application on a machine with no custom `/etc/hosts` entries for the storage backend. Open the library and verify all thumbnails load. Open an image detail page and verify the full-size image loads. Confirm no network errors related to image fetching appear in the browser console. **Acceptance Scenarios**: 1. **Given** the application is running, **When** the user opens the library, **Then** all image thumbnails display correctly with no host-file configuration required. 2. **Given** the user opens an image detail page, **When** the page loads, **Then** the full-size image displays correctly. 3. **Given** a request for an image that does not exist, **When** the client requests its content, **Then** a not-found response is returned with no storage-specific details exposed. --- ### User Story 2 — Storage Backend Remains Private (Priority: P1) The image storage system is never directly reachable or identifiable from a client browser. Image URLs in the application reference only the API, never the storage backend's hostname, credentials, or bucket names. **Why this priority**: Security and portability. A private storage backend means credentials cannot be extracted from the browser and the storage layer can be reconfigured without affecting clients. **Independent Test**: Open browser developer tools while browsing the application. Inspect all image `src` attributes, network requests, and API responses. Confirm that no request goes directly to the storage backend and no storage-specific URL, hostname, or credential appears in the browser's network tab or page source. **Acceptance Scenarios**: 1. **Given** the user is browsing the application, **When** they inspect network traffic, **Then** all image content is fetched through the application's API domain — no request goes directly to storage infrastructure. 2. **Given** any API response containing image metadata, **When** it is inspected, **Then** no storage-specific URLs, hostnames, bucket names, or credentials appear in the response body. --- ### Edge Cases - What happens when the storage backend is temporarily unavailable? → The proxy returns a generic server-error response; no storage internals are disclosed. - What happens when the image exists in the database but the file is missing from storage? → The proxy returns a not-found response with a generic message. - What happens when image content is large (up to 50 MB)? → Content streams to the client without exhausting server memory. ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: The API MUST expose an endpoint that returns image binary content when given a valid image identifier. - **FR-002**: The API MUST serve image content with the correct file-type indicator so that browsers render it natively. - **FR-003**: The image content endpoint MUST be the sole mechanism by which clients retrieve image binary data; no other image content URL format MUST be presented to clients. - **FR-004**: The storage backend MUST NOT be directly reachable by client browsers; all image content MUST flow through the API. - **FR-005**: The API MUST return a not-found response when content is requested for a non-existent image. - **FR-006**: The API MUST handle image content requests for files up to 50 MB without exhausting server memory. - **FR-007**: The image content endpoint MUST include caching metadata in its response so that browsers can avoid redundant fetches for the same image. - **FR-008**: The UI MUST construct all image display URLs using the API's image content endpoint, for both library thumbnails and the detail view. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: A user can view all images in the library and on detail pages on a freshly configured machine with no custom DNS or host-file entries for the storage backend. - **SC-002**: No image-related network request in the browser targets the storage backend directly — verifiable by inspecting browser developer tools. - **SC-003**: Images up to 50 MB load completely without a server-side memory or timeout error. - **SC-004**: A browser that has already loaded an image does not re-download it on a second visit to the same page (browser caching headers are respected). - **SC-005**: Image load time via the proxy is within 20% of the time taken when served directly from storage on a local network connection. ## Assumptions - The storage backend is accessible from the API server via an internal network hostname that is not reachable from client browsers. - Image files are immutable after upload; once a client has the content for a given image identifier it need not be re-fetched. - No authentication is required to access the image content endpoint in Phase 1, consistent with the application's no-auth Phase 1 stance. - This feature changes how image content is delivered but does not alter any image metadata endpoints, the upload flow, or tag management behaviour. - No new database entities or schema changes are required; only how the existing stored file is retrieved and served changes.