fix: move wallpaper routes before /{key} catch-all to fix 404

Bing wallpaper endpoint (/bing-wallpapers) was returning 404 from HTTP
because FastAPI matched it against the /{key} catch-all route at position
2 before the specific route at position 13 ever got reached.

- Moved wallpaper routes (bing-wallpapers, bing-wallpaper) and helper code
  before the /{key} wildcard route
- Removed dead guard code in get_setting() that was never effective —
  FastAPI route matching happens before the handler body executes
- Removed duplicate wallpaper code block at end of file

Root cause: FastAPI matches routes in registration order. /{key} at
position 2 matched 'bing-wallpapers' as the key parameter, returning
'Setting not found' instead of routing to the wallpaper endpoint.
This commit is contained in:
Your Name
2026-05-31 22:00:33 +08:00
parent c6485168b6
commit 1ecd8e4542
+127 -130
View File
@@ -148,12 +148,136 @@ async def get_ip_allowlist(
}
# ── Bing Daily Wallpaper — download 12+ images, rotate hourly ──
_WALLPAPER_DIR = Path(__file__).resolve().parent.parent.parent / "web" / "app" / "wallpapers"
_MAX_WALLPAPERS = 12 # keep 12 images (12 hours rotation before repeat)
_MAX_AGE_DAYS = 7 # auto-clean older than 7 days
def _cleanup_old_wallpapers() -> int:
"""Remove wallpapers older than 7 days. Returns count removed."""
import time
from datetime import datetime, timedelta
cutoff = datetime.utcnow() - timedelta(days=_MAX_AGE_DAYS)
removed = 0
for f in sorted(_WALLPAPER_DIR.glob("*.jpg")):
try:
mtime = datetime.utcfromtimestamp(f.stat().st_mtime)
if mtime < cutoff:
f.unlink()
removed += 1
except Exception:
pass
return removed
@router.get("/bing-wallpapers", response_model=dict)
async def bing_wallpapers():
"""Return all cached wallpaper URLs for frontend slideshow rotation.
Syncs with Bing daily (pulls up to 8 days of images), caches to disk,
cleans up old files, returns sorted list of local URLs.
"""
import httpx
from datetime import datetime, timedelta
_WALLPAPER_DIR.mkdir(parents=True, exist_ok=True)
# Download today + last 7 days from Bing (n=8 gives ~8 images)
downloaded = 0
try:
async with httpx.AsyncClient(timeout=12.0) as client:
# Fetch up to 8 recent images from Bing
resp = await client.get(
"https://cn.bing.com/HPImageArchive.aspx",
params={"format": "js", "idx": 0, "n": 8, "mkt": "zh-CN"},
)
data = resp.json()
for img in (data.get("images") or []):
img_path = img.get("url")
if not img_path:
continue
# Use date from Bing metadata as filename
date_str = img.get("startdate", "") or img.get("fullstartdate", "")[:8] or ""
if not date_str:
# fallback: extract from URL
import re
m = re.search(r'OHR\.(\w+)_', img_path)
date_str = m.group(1) if m else f"unknown_{downloaded}"
cached = _WALLPAPER_DIR / f"{date_str}.jpg"
if cached.exists() and cached.stat().st_size > 1024:
continue # already cached
try:
img_resp = await client.get(f"https://cn.bing.com{img_path}")
if img_resp.status_code == 200:
cached.write_bytes(img_resp.content)
downloaded += 1
except Exception:
continue
except Exception:
pass
# Cleanup old files
_cleanup_old_wallpapers()
# Also keep only newest N files if we somehow have too many
all_files = sorted(_WALLPAPER_DIR.glob("*.jpg"), key=lambda f: f.stat().st_mtime, reverse=True)
for f in all_files[_MAX_WALLPAPERS:]:
try:
f.unlink()
except Exception:
pass
# Return sorted URLs (newest first)
urls = [f"/app/wallpapers/{f.name}" for f in sorted(all_files[:_MAX_WALLPAPERS], key=lambda f: f.name, reverse=True)]
return {"urls": urls, "count": len(urls)}
# Keep the old single-image endpoint for backward compatibility
@router.get("/bing-wallpaper", response_model=dict)
async def bing_wallpaper():
"""Return today's wallpaper URL (backward compat)."""
try:
import httpx
from datetime import datetime
_WALLPAPER_DIR.mkdir(parents=True, exist_ok=True)
today = datetime.utcnow().strftime("%Y-%m-%d")
cached = _WALLPAPER_DIR / f"{today}.jpg"
if cached.exists() and cached.stat().st_size > 1024:
return {"url": f"/app/wallpapers/{today}.jpg"}
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(
"https://cn.bing.com/HPImageArchive.aspx",
params={"format": "js", "idx": 0, "n": 1, "mkt": "zh-CN"},
)
data = resp.json()
img = (data.get("images") or [{}])[0]
img_url = img.get("url")
if not img_url:
return {"url": None}
img_resp = await client.get(f"https://cn.bing.com{img_url}")
if img_resp.status_code == 200:
cached.write_bytes(img_resp.content)
_cleanup_old_wallpapers()
return {"url": f"/app/wallpapers/{today}.jpg"}
except Exception:
# Try any cached file
cached_list = sorted(_WALLPAPER_DIR.glob("*.jpg"))
if cached_list:
return {"url": f"/app/wallpapers/{cached_list[-1].name}"}
return {"url": None}
@router.get("/{key}", response_model=dict)
async def get_setting(key: str, admin: Admin = Depends(get_current_admin), db: AsyncSession = Depends(get_db)):
"""Get a single setting by key (sensitive values masked)"""
# Skip catch-all for known fixed paths
if key in ("bing-wallpaper", "bing-wallpapers"):
raise HTTPException(status_code=404, detail="use dedicated wallpaper endpoints")
repo = SettingRepositoryImpl(db)
value = await repo.get(key)
if value is None:
@@ -1265,130 +1389,3 @@ def _retry_to_dict(job: PushRetryJob) -> dict:
"last_error": job.last_error,
"created_at": str(job.created_at) if job.created_at else None,
}
# ── Bing Daily Wallpaper — download 12+ images, rotate hourly ──
_WALLPAPER_DIR = Path(__file__).resolve().parent.parent.parent / "web" / "app" / "wallpapers"
_MAX_WALLPAPERS = 12 # keep 12 images (12 hours rotation before repeat)
_MAX_AGE_DAYS = 7 # auto-clean older than 7 days
def _cleanup_old_wallpapers() -> int:
"""Remove wallpapers older than 7 days. Returns count removed."""
import time
from datetime import datetime, timedelta
cutoff = datetime.utcnow() - timedelta(days=_MAX_AGE_DAYS)
removed = 0
for f in sorted(_WALLPAPER_DIR.glob("*.jpg")):
try:
mtime = datetime.utcfromtimestamp(f.stat().st_mtime)
if mtime < cutoff:
f.unlink()
removed += 1
except Exception:
pass
return removed
@router.get("/bing-wallpapers", response_model=dict)
async def bing_wallpapers():
"""Return all cached wallpaper URLs for frontend slideshow rotation.
Syncs with Bing daily (pulls up to 8 days of images), caches to disk,
cleans up old files, returns sorted list of local URLs.
"""
import httpx
from datetime import datetime, timedelta
_WALLPAPER_DIR.mkdir(parents=True, exist_ok=True)
# Download today + last 7 days from Bing (n=8 gives ~8 images)
downloaded = 0
try:
async with httpx.AsyncClient(timeout=12.0) as client:
# Fetch up to 8 recent images from Bing
resp = await client.get(
"https://cn.bing.com/HPImageArchive.aspx",
params={"format": "js", "idx": 0, "n": 8, "mkt": "zh-CN"},
)
data = resp.json()
for img in (data.get("images") or []):
img_path = img.get("url")
if not img_path:
continue
# Use date from Bing metadata as filename
date_str = img.get("startdate", "") or img.get("fullstartdate", "")[:8] or ""
if not date_str:
# fallback: extract from URL
import re
m = re.search(r'OHR\.(\w+)_', img_path)
date_str = m.group(1) if m else f"unknown_{downloaded}"
cached = _WALLPAPER_DIR / f"{date_str}.jpg"
if cached.exists() and cached.stat().st_size > 1024:
continue # already cached
try:
img_resp = await client.get(f"https://cn.bing.com{img_path}")
if img_resp.status_code == 200:
cached.write_bytes(img_resp.content)
downloaded += 1
except Exception:
continue
except Exception:
pass
# Cleanup old files
_cleanup_old_wallpapers()
# Also keep only newest N files if we somehow have too many
all_files = sorted(_WALLPAPER_DIR.glob("*.jpg"), key=lambda f: f.stat().st_mtime, reverse=True)
for f in all_files[_MAX_WALLPAPERS:]:
try:
f.unlink()
except Exception:
pass
# Return sorted URLs (newest first)
urls = [f"/app/wallpapers/{f.name}" for f in sorted(all_files[:_MAX_WALLPAPERS], key=lambda f: f.name, reverse=True)]
return {"urls": urls, "count": len(urls)}
# Keep the old single-image endpoint for backward compatibility
@router.get("/bing-wallpaper", response_model=dict)
async def bing_wallpaper():
"""Return today's wallpaper URL (backward compat)."""
try:
import httpx
from datetime import datetime
_WALLPAPER_DIR.mkdir(parents=True, exist_ok=True)
today = datetime.utcnow().strftime("%Y-%m-%d")
cached = _WALLPAPER_DIR / f"{today}.jpg"
if cached.exists() and cached.stat().st_size > 1024:
return {"url": f"/app/wallpapers/{today}.jpg"}
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(
"https://cn.bing.com/HPImageArchive.aspx",
params={"format": "js", "idx": 0, "n": 1, "mkt": "zh-CN"},
)
data = resp.json()
img = (data.get("images") or [{}])[0]
img_url = img.get("url")
if not img_url:
return {"url": None}
img_resp = await client.get(f"https://cn.bing.com{img_url}")
if img_resp.status_code == 200:
cached.write_bytes(img_resp.content)
_cleanup_old_wallpapers()
return {"url": f"/app/wallpapers/{today}.jpg"}
except Exception:
# Try any cached file
cached_list = sorted(_WALLPAPER_DIR.glob("*.jpg"))
if cached_list:
return {"url": f"/app/wallpapers/{cached_list[-1].name}"}
return {"url": None}