Implements ADR 0013 — image-cache part.
Why
The browse-mode grid (other issue) shows item images as tiles. The integration APIs serve images behind their own auth tokens — we don't want to expose tokens to the browser.
What
- New endpoint:
GET /api/lookup/{integration}/{id}/image
- Backend fetches the source image using the integration's API token (Snipe-IT, Grocy)
- For Spoolman: synthesise a colour swatch from
filament.color_hex (no real image to fetch)
- Disk cache under
data/image-cache/<integration>/<id>.jpg with ETag and last-modified handling
- Cache eviction: TTL-based (e.g. 7 days untouched) + manual purge endpoint
- Returns
image/jpeg or image/png with appropriate Cache-Control headers
- Placeholder for items without an image (generic icon per integration)
Acceptance
- First request fetches from upstream + caches; subsequent requests serve from cache
- ETag revalidation against upstream on cache stale (304 keeps cached copy)
- Spoolman colour-swatch generation produces valid PNG matching
color_hex
- Tokens never appear in HTTP responses or logs
- Cache directory respects the non-root container UID (1000)
Storage estimate
~50 KB per item × 500 items × 3 integrations ~= 75 MB worst case — acceptable for the data volume
Implements ADR 0013 — image-cache part.
Why
The browse-mode grid (other issue) shows item images as tiles. The integration APIs serve images behind their own auth tokens — we don't want to expose tokens to the browser.
What
GET /api/lookup/{integration}/{id}/imagefilament.color_hex(no real image to fetch)data/image-cache/<integration>/<id>.jpgwith ETag and last-modified handlingimage/jpegorimage/pngwith appropriate Cache-Control headersAcceptance
color_hexStorage estimate
~50 KB per item × 500 items × 3 integrations ~= 75 MB worst case — acceptable for the data volume