# Research: CDN Image Serving ## Decision 1: Where does URL construction logic live? **Decision**: In the image router's `_image_to_dict` helper, not in the `StorageBackend`. **Rationale**: The `StorageBackend` interface is responsible for put/get/delete of object bytes. Adding URL construction there conflates two concerns — storage operations and HTTP URL generation — and would require the storage abstraction to know about CDN configuration. The router already has access to application settings via `get_settings()` and knows the image ID and storage key, making it the natural place to construct URLs. **Alternatives considered**: Adding a `get_url(key)` method to `StorageBackend` — rejected because it leaks HTTP/CDN concerns into the storage abstraction, violating §2.3. --- ## Decision 2: Fallback URL format in local development **Decision**: Relative paths (`/api/v1/images/{id}/file`, `/api/v1/images/{id}/thumbnail`) when `S3_PUBLIC_BASE_URL` is not set. **Rationale**: Relative paths work regardless of the host the app is running on, require no additional configuration, and match how the UI currently constructs these URLs via `getFileUrl(id)` and `getThumbnailUrl(id)`. An absolute fallback would require `API_BASE_URL` to be set in local dev, adding unnecessary setup friction. **Alternatives considered**: Absolute URL fallback using `API_BASE_URL` — rejected because it adds a mandatory config dependency where none exists today. --- ## Decision 3: Trailing slash normalisation **Decision**: Strip trailing slash from `S3_PUBLIC_BASE_URL` at construction time using `rstrip('/')` in the config validator or at point of use. **Rationale**: Prevents double-slash URLs (`https://cdn.example.com//key`) if the operator includes a trailing slash in the configured value. Simple, defensive, zero-cost. --- ## Decision 4: Proxy endpoints retained or removed? **Decision**: Retained, fully functional, unchanged. **Rationale**: Spec FR-005 explicitly requires them. They serve as the local dev fallback and a safety net if the CDN is temporarily unavailable or misconfigured. Removing them would break local development immediately. --- ## Decision 5: `storage_key` and `thumbnail_key` in API response **Decision**: Keep both fields in the response alongside the new `file_url` and `thumbnail_url`. **Rationale**: Removing them is a breaking API change. The UI currently reads `thumbnail_key` to decide whether a thumbnail exists. After this change the UI will use `thumbnail_url` (null when no thumbnail), but the keys remain in the response for backward compatibility with any tooling. --- ## Decision 6: Settings access in `_image_to_dict` **Decision**: Pre-compute the CDN base URL string once per request at the endpoint level and pass it into `_image_to_dict` as a parameter, rather than calling `get_settings()` inside the helper. **Rationale**: Keeps `_image_to_dict` a pure function (easier to test), avoids calling `get_settings()` inside a helper that is called in a loop (image list endpoint), and makes the dependency explicit.