# 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.
