API responses now include file_url and thumbnail_url fields. When S3_PUBLIC_BASE_URL is configured, these point to the CDN domain; when unset, they fall back to the existing API proxy paths so local dev requires no additional setup. UI updated to use response URL fields directly instead of constructing proxy URLs client-side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.0 KiB
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.