Add Strava sync status report and manual trigger to admin panel

Each sync run now writes _strava_sync_status.json per user (status,
imported count, error message). New admin endpoints expose this data
and allow triggering an on-demand sync. The admin page gains a Strava
Sync section showing per-user token/credentials state, total imported,
last sync time, and last-run status with inline error messages.
This commit is contained in:
Davide Scaini
2026-05-08 13:44:23 +02:00
parent 12693dbd60
commit 2287d6e2ee
3 changed files with 240 additions and 3 deletions
+87
View File
@@ -164,6 +164,8 @@ public_url: str = "" # e.g. "https://yourdomain.com" — used for OAuth redire
dem_url: str = "https://api.open-elevation.com" # Open-Elevation-compatible API base URL
sync_secret: str = "" # shared secret for /api/internal/rebuild (set via --sync-secret)
_db = None # sqlite3.Connection, opened lazily
_strava_sync_running = False
_strava_sync_lock = threading.Lock()
def _get_db():
@@ -1476,6 +1478,91 @@ async def admin_delete_user_directory(
return JSONResponse({"ok": True})
@app.get("/api/admin/strava-sync")
async def admin_strava_sync_status(
bincio_session: Optional[str] = Cookie(default=None),
) -> JSONResponse:
"""Return per-user Strava sync status for the admin panel."""
_require_admin(bincio_session)
root = _get_data_dir()
users = []
for tf in sorted(root.glob("*/strava_token.json")):
user_dir = tf.parent
handle = user_dir.name
has_creds = (user_dir / "strava_credentials.json").exists()
last_sync: str | None = None
total_imported = 0
sync_path = user_dir / "_strava_sync.json"
if sync_path.exists():
try:
sc = json.loads(sync_path.read_text(encoding="utf-8"))
last_sync = sc.get("last_sync")
total_imported = len(sc.get("imported_ids", []))
except Exception:
pass
run_status: str | None = None
run_imported = 0
run_errors = 0
run_error_message: str | None = None
last_run: str | None = None
status_path = user_dir / "_strava_sync_status.json"
if status_path.exists():
try:
ss = json.loads(status_path.read_text(encoding="utf-8"))
run_status = ss.get("status")
run_imported = ss.get("imported", 0)
run_errors = ss.get("errors", 0)
run_error_message = ss.get("error_message")
last_run = ss.get("last_run")
except Exception:
pass
users.append({
"handle": handle,
"has_credentials": has_creds,
"last_sync": last_sync,
"total_imported": total_imported,
"run_status": run_status,
"run_imported": run_imported,
"run_errors": run_errors,
"run_error_message": run_error_message,
"last_run": last_run,
})
return JSONResponse({"running": _strava_sync_running, "users": users})
@app.post("/api/admin/strava-sync/run")
async def admin_strava_sync_run(
bincio_session: Optional[str] = Cookie(default=None),
) -> JSONResponse:
"""Trigger an immediate Strava sync for all users (admin only)."""
global _strava_sync_running
_require_admin(bincio_session)
with _strava_sync_lock:
if _strava_sync_running:
raise HTTPException(409, "Sync already running")
_strava_sync_running = True
def _run() -> None:
global _strava_sync_running
try:
from bincio.sync_strava import sync_all
results = sync_all(_get_data_dir())
total_new = sum(n for n, _ in results.values())
if total_new > 0:
_site_rebuild_event.set()
except Exception:
log.exception("admin_strava_sync_run: unexpected error")
finally:
_strava_sync_running = False
threading.Thread(target=_run, daemon=True, name="admin-strava-sync").start()
return JSONResponse({"ok": True}, status_code=202)
# ── Self-service user settings ────────────────────────────────────────────────