update
This commit is contained in:
164
AzA march 2026 - Kopie (18)/project_status_routes.py
Normal file
164
AzA march 2026 - Kopie (18)/project_status_routes.py
Normal file
@@ -0,0 +1,164 @@
|
||||
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
|
||||
Reference in New Issue
Block a user