Files
aza/AzA march 2026 - Kopie (4)/project_status_routes.py
2026-03-30 07:59:11 +02:00

165 lines
4.9 KiB
Python

from __future__ import annotations
import json
import os
from datetime import datetime, timezone
from pathlib import Path
from fastapi import APIRouter, HTTPException, Request
_BASE_DIR = Path(__file__).resolve().parent
_STATUS_FILE = _BASE_DIR / "project_status.json"
_HISTORY_FILE = _BASE_DIR / "project_status_history.jsonl"
_ALLOWED_KEYS = frozenset(("phase", "current_step", "last_completed_step", "next_step", "last_update", "notes"))
try:
_HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
except Exception:
pass
router = APIRouter(tags=["project"])
def _read_expected_token() -> str:
expected = os.environ.get("MEDWORK_API_TOKEN", "").strip()
if expected:
return expected
try:
token_path = _BASE_DIR / "backend_token.txt"
with open(token_path, "r", encoding="utf-8") as f:
return (f.readline() or "").strip()
except Exception:
return ""
def _check_token(request: Request) -> bool:
expected = _read_expected_token()
if not expected:
return False
token = (request.headers.get("X-API-Token", "") or "").strip()
if not token:
auth = (request.headers.get("Authorization", "") or "").strip()
if auth.startswith("Bearer "):
token = auth[len("Bearer "):].strip()
return token == expected
def _get_device_id(request: Request) -> str:
return (request.headers.get("X-Device-Id", "") or "").strip() or ""
def _append_history_entry(ts: str, device_id: str, action: str, status: dict) -> None:
try:
_HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
entry = {"ts": ts, "device_id": device_id, "action": action, "status": status}
with open(_HISTORY_FILE, "a", encoding="utf-8") as hf:
hf.write(json.dumps(entry, ensure_ascii=False) + "\n")
hf.flush()
os.fsync(hf.fileno())
except Exception:
pass
def _atomic_write_status(data: dict) -> bool:
tmp = _STATUS_FILE.with_suffix(".json.tmp")
try:
text = json.dumps(data, indent=2, ensure_ascii=False) + "\n"
with open(tmp, "w", encoding="utf-8") as f:
f.write(text)
f.flush()
os.fsync(f.fileno())
os.replace(tmp, _STATUS_FILE)
return True
except Exception:
try:
tmp.unlink(missing_ok=True)
except Exception:
pass
return False
@router.get("/api/project/status")
def get_project_status(request: Request):
if not _check_token(request):
raise HTTPException(status_code=401, detail="Unauthorized")
if not _STATUS_FILE.is_file():
raise HTTPException(status_code=500, detail="project_status.json missing")
try:
with open(_STATUS_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception:
raise HTTPException(status_code=500, detail="project_status.json missing")
data["updated_at"] = datetime.now(timezone.utc).isoformat()
try:
_append_history_entry(
data["updated_at"],
_get_device_id(request),
"read",
{k: data.get(k) for k in ("phase", "current_step", "last_completed_step", "next_step", "last_update", "notes")},
)
except Exception:
pass
return data
@router.post("/api/project/status")
async def post_project_status(request: Request):
if not _check_token(request):
raise HTTPException(status_code=401, detail="Unauthorized")
try:
body = await request.json()
except Exception:
body = {}
if not isinstance(body, dict):
body = {}
data = {}
if _STATUS_FILE.is_file():
try:
with open(_STATUS_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception:
pass
if not data:
data = {
"project": "AZA Medical AI Assistant",
"phase": "",
"current_step": 0,
"last_completed_step": 0,
"next_step": 0,
"last_update": "",
"notes": "",
"todos": [],
"roadmap": [],
}
for key in _ALLOWED_KEYS:
if key in body:
val = body[key]
if key in ("current_step", "last_completed_step", "next_step"):
try:
data[key] = int(val) if val is not None and str(val).strip() != "" else 0
except (ValueError, TypeError):
data[key] = data.get(key, 0)
else:
data[key] = str(val) if val is not None else ""
data["updated_at"] = datetime.now(timezone.utc).isoformat()
if not _atomic_write_status(data):
raise HTTPException(status_code=500, detail="Failed to write project_status.json")
try:
_append_history_entry(
data["updated_at"],
_get_device_id(request),
"update",
dict(data),
)
except Exception:
pass
return data