feat: add space weather image prefetch and stable cache-busting

Backend: Add /prefetch-images endpoint that warms the image cache in
parallel using a thread pool, skipping already-cached images.

Frontend: Trigger prefetch on mode init so images load instantly.
Replace per-request Date.now() cache-bust with a 5-minute rotating
key to allow browser caching aligned with backend max-age.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-03-02 22:21:55 +00:00
parent 4b64862eb4
commit f07ec23da9
2 changed files with 42 additions and 2 deletions
+33
View File
@@ -304,3 +304,36 @@ def get_image(key: str):
_cache_set(cache_key, img_data, TTL_IMAGE)
return Response(img_data, content_type=entry['content_type'],
headers={'Cache-Control': 'public, max-age=300'})
@space_weather_bp.route('/prefetch-images')
def prefetch_images():
"""Warm the image cache by fetching all whitelisted images in parallel."""
# Only fetch images not already cached
to_fetch = {}
for key, entry in IMAGE_WHITELIST.items():
cache_key = f'img_{key}'
if _cache_get(cache_key) is None:
to_fetch[key] = entry
if not to_fetch:
return jsonify({'status': 'all cached', 'count': 0})
def _fetch_and_cache(key: str, entry: dict) -> bool:
img_data = _fetch_bytes(entry['url'])
if img_data:
_cache_set(f'img_{key}', img_data, TTL_IMAGE)
return True
return False
fetched = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=6) as executor:
futures = {
executor.submit(_fetch_and_cache, k, e): k
for k, e in to_fetch.items()
}
for future in concurrent.futures.as_completed(futures):
if future.result():
fetched += 1
return jsonify({'status': 'ok', 'fetched': fetched, 'cached': len(IMAGE_WHITELIST) - len(to_fetch)})