Files
kadence/backend/app/api/routes_updates.py
T
timka123 1bdd65cc76
lint / ruff (push) Successful in 14s
tests / desktop (push) Successful in 22s
docker / image (push) Successful in 43s
tests / web (push) Successful in 39s
tests / android-core (push) Successful in 1m1s
tests / backend (push) Successful in 2m12s
android / apk (push) Successful in 6m50s
implement ota updates
2026-10-22 21:11:04 +03:00

54 lines
1.8 KiB
Python

"""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"},
)