"""The Android OTA feed. The APK never comes from the phone's own Gitea credentials — the repository is private. The backend reads the newest successful Actions artifact and re-serves it here, behind the same auth as the rest of `/api`. """ from __future__ import annotations from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse from app.services import android_updates router = APIRouter(prefix="/api/updates", tags=["updates"]) def _raise_http(exc: android_updates.UpdateUnavailable) -> None: if isinstance(exc, android_updates.UpdateNotConfigured): raise HTTPException(status_code=503, detail=str(exc)) from exc if isinstance(exc, android_updates.UpdateNotFound): raise HTTPException(status_code=404, detail=str(exc)) from exc raise HTTPException(status_code=502, detail=str(exc)) from exc @router.get("/android") def android_update() -> dict: """Metadata for the newest successful master build. The client compares `version_code` with its own; the backend deliberately does not try to know which build the phone is running. """ try: return android_updates.latest_android() except android_updates.UpdateUnavailable as exc: _raise_http(exc) @router.get("/android/apk") def android_apk() -> FileResponse: """The APK for the newest successful master build.""" try: path, metadata = android_updates.android_apk() except android_updates.UpdateUnavailable as exc: _raise_http(exc) version = metadata.get("version_name") or metadata.get("version_code") or "latest" return FileResponse( path, media_type="application/vnd.android.package-archive", filename=f"kadence-{version}.apk", headers={"Cache-Control": "no-store"}, )