Files
aza/AzA march 2026/empfang_routes.py

767 lines
26 KiB
Python
Raw Normal View History

2026-04-16 15:23:14 +02:00
# -*- coding: utf-8 -*-
"""
AZA Empfang - Backend-Routen V4.
Serverseitige Auth, Benutzer, Sessions, Nachrichten, Aufgaben.
Alle Daten practice-scoped. Backend ist die einzige Wahrheit.
2026-04-16 15:23:14 +02:00
"""
import hashlib
import hmac
2026-04-16 15:23:14 +02:00
import json
import os
import secrets
2026-04-16 15:23:14 +02:00
import time
import uuid
from pathlib import Path
from typing import Optional
2026-04-16 15:23:14 +02:00
from fastapi import APIRouter, Cookie, HTTPException, Query, Request, Response
2026-04-16 15:23:14 +02:00
from fastapi.responses import HTMLResponse, JSONResponse
from pydantic import BaseModel, Field
router = APIRouter()
_DATA_DIR = Path(__file__).resolve().parent / "data"
_EMPFANG_FILE = _DATA_DIR / "empfang_nachrichten.json"
_PRACTICES_FILE = _DATA_DIR / "empfang_practices.json"
_ACCOUNTS_FILE = _DATA_DIR / "empfang_accounts.json"
_SESSIONS_FILE = _DATA_DIR / "empfang_sessions.json"
_TASKS_FILE = _DATA_DIR / "empfang_tasks.json"
DEFAULT_PRACTICE_ID = "default"
SESSION_MAX_AGE = 30 * 24 * 3600 # 30 Tage
2026-04-16 15:23:14 +02:00
def _ensure_data_dir():
_DATA_DIR.mkdir(parents=True, exist_ok=True)
# =====================================================================
# JSON helpers (atomic write)
# =====================================================================
def _load_json(path: Path, default=None):
if not path.is_file():
return default if default is not None else []
2026-04-16 15:23:14 +02:00
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
2026-04-16 15:23:14 +02:00
except Exception:
return default if default is not None else []
2026-04-16 15:23:14 +02:00
def _save_json(path: Path, data):
2026-04-16 15:23:14 +02:00
_ensure_data_dir()
tmp = str(path) + ".tmp"
2026-04-16 15:23:14 +02:00
with open(tmp, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
os.replace(tmp, str(path))
# =====================================================================
# Password hashing (PBKDF2 no external dependency)
# =====================================================================
def _hash_password(password: str, salt: str = None) -> tuple[str, str]:
salt = salt or secrets.token_hex(16)
dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 100_000)
return dk.hex(), salt
def _verify_password(password: str, stored_hash: str, salt: str) -> bool:
dk = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 100_000)
return hmac.compare_digest(dk.hex(), stored_hash)
# =====================================================================
# Practices
# =====================================================================
def _load_practices() -> dict:
return _load_json(_PRACTICES_FILE, {})
def _save_practices(data: dict):
_save_json(_PRACTICES_FILE, data)
def _ensure_default_practice():
practices = _load_practices()
if DEFAULT_PRACTICE_ID not in practices:
practices[DEFAULT_PRACTICE_ID] = {
"practice_id": DEFAULT_PRACTICE_ID,
"name": "Meine Praxis",
"invite_code": secrets.token_urlsafe(8),
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
}
_save_practices(practices)
_migrate_old_users(DEFAULT_PRACTICE_ID)
return practices[DEFAULT_PRACTICE_ID]
def _migrate_old_users(practice_id: str):
"""Migriert alte empfang_users.json Strings zu echten Accounts."""
old_file = _DATA_DIR / "empfang_users.json"
if not old_file.is_file():
return
try:
names = json.loads(old_file.read_text(encoding="utf-8"))
if not isinstance(names, list):
return
accounts = _load_accounts()
for name in names:
name = name.strip()
if not name:
continue
exists = any(
a["display_name"] == name and a["practice_id"] == practice_id
for a in accounts.values()
)
if not exists:
uid = uuid.uuid4().hex[:12]
pw_hash, pw_salt = _hash_password(name.lower())
accounts[uid] = {
"user_id": uid,
"practice_id": practice_id,
"display_name": name,
"role": "mpa",
"pw_hash": pw_hash,
"pw_salt": pw_salt,
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
}
_save_accounts(accounts)
except Exception:
pass
# =====================================================================
# Accounts (practice-scoped users with auth)
# =====================================================================
def _load_accounts() -> dict:
return _load_json(_ACCOUNTS_FILE, {})
def _save_accounts(data: dict):
_save_json(_ACCOUNTS_FILE, data)
def _practice_users(practice_id: str) -> list[dict]:
accounts = _load_accounts()
return [
{"user_id": a["user_id"], "display_name": a["display_name"], "role": a["role"]}
for a in accounts.values()
if a.get("practice_id") == practice_id
]
# =====================================================================
# Sessions
# =====================================================================
2026-04-16 15:23:14 +02:00
def _load_sessions() -> dict:
return _load_json(_SESSIONS_FILE, {})
def _save_sessions(data: dict):
_save_json(_SESSIONS_FILE, data)
def _create_session(user_id: str, practice_id: str, display_name: str, role: str) -> str:
token = secrets.token_urlsafe(32)
sessions = _load_sessions()
sessions[token] = {
"user_id": user_id,
"practice_id": practice_id,
"display_name": display_name,
"role": role,
"created": time.time(),
"last_active": time.time(),
}
_save_sessions(sessions)
return token
def _get_session(token: str) -> Optional[dict]:
if not token:
return None
sessions = _load_sessions()
s = sessions.get(token)
if not s:
return None
if time.time() - s.get("created", 0) > SESSION_MAX_AGE:
del sessions[token]
_save_sessions(sessions)
return None
s["last_active"] = time.time()
sessions[token] = s
_save_sessions(sessions)
return s
def _delete_session(token: str):
sessions = _load_sessions()
if token in sessions:
del sessions[token]
_save_sessions(sessions)
def _session_from_request(request: Request) -> Optional[dict]:
token = request.cookies.get("aza_session") or ""
if not token:
auth = request.headers.get("Authorization", "")
if auth.startswith("Bearer "):
token = auth[7:]
if not token:
token = request.query_params.get("session_token", "")
return _get_session(token)
def _require_session(request: Request) -> dict:
s = _session_from_request(request)
if not s:
raise HTTPException(status_code=401, detail="Nicht angemeldet")
return s
# =====================================================================
# Messages (practice-scoped)
# =====================================================================
def _load_messages() -> list[dict]:
return _load_json(_EMPFANG_FILE, [])
def _save_messages(messages: list[dict]):
_save_json(_EMPFANG_FILE, messages)
def _msg_practice(m: dict) -> str:
return m.get("practice_id") or DEFAULT_PRACTICE_ID
def _filter_by_practice(messages: list[dict], pid: str) -> list[dict]:
return [m for m in messages if _msg_practice(m) == pid]
# =====================================================================
# Tasks (practice-scoped, server-side)
# =====================================================================
def _load_tasks() -> list[dict]:
return _load_json(_TASKS_FILE, [])
def _save_tasks(tasks: list[dict]):
_save_json(_TASKS_FILE, tasks)
# =====================================================================
# AUTH ENDPOINTS
# =====================================================================
@router.post("/auth/setup")
async def auth_setup(request: Request):
"""Erstellt den ersten Admin-Benutzer fuer die Default-Praxis.
Nur aufrufbar wenn noch keine Accounts existieren."""
practice = _ensure_default_practice()
accounts = _load_accounts()
practice_accounts = [a for a in accounts.values()
if a.get("practice_id") == DEFAULT_PRACTICE_ID]
if practice_accounts:
raise HTTPException(status_code=409,
detail="Setup bereits abgeschlossen. Bitte Login verwenden.")
try:
body = await request.json()
except Exception:
body = {}
name = (body.get("name") or "").strip()
password = (body.get("password") or "").strip()
if not name or not password or len(password) < 4:
raise HTTPException(status_code=400,
detail="Name und Passwort (min. 4 Zeichen) erforderlich")
uid = uuid.uuid4().hex[:12]
pw_hash, pw_salt = _hash_password(password)
accounts[uid] = {
"user_id": uid,
"practice_id": DEFAULT_PRACTICE_ID,
"display_name": name,
"role": "admin",
"pw_hash": pw_hash,
"pw_salt": pw_salt,
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
}
_save_accounts(accounts)
token = _create_session(uid, DEFAULT_PRACTICE_ID, name, "admin")
resp = JSONResponse(content={
"success": True, "user_id": uid, "role": "admin",
"display_name": name, "practice_id": DEFAULT_PRACTICE_ID,
"invite_code": practice.get("invite_code", ""),
})
resp.set_cookie("aza_session", token, httponly=True, samesite="lax",
max_age=SESSION_MAX_AGE)
return resp
@router.post("/auth/login")
async def auth_login(request: Request):
"""Login mit Name + Passwort."""
try:
body = await request.json()
except Exception:
body = {}
name = (body.get("name") or "").strip()
password = (body.get("password") or "").strip()
pid = (body.get("practice_id") or "").strip() or DEFAULT_PRACTICE_ID
if not name or not password:
raise HTTPException(status_code=400, detail="Name und Passwort erforderlich")
accounts = _load_accounts()
target = None
for a in accounts.values():
if a["display_name"] == name and a.get("practice_id") == pid:
target = a
break
if not target:
raise HTTPException(status_code=401, detail="Benutzer nicht gefunden")
if not _verify_password(password, target["pw_hash"], target["pw_salt"]):
raise HTTPException(status_code=401, detail="Falsches Passwort")
token = _create_session(target["user_id"], pid, name, target["role"])
resp = JSONResponse(content={
"success": True, "user_id": target["user_id"], "role": target["role"],
"display_name": name, "practice_id": pid,
})
resp.set_cookie("aza_session", token, httponly=True, samesite="lax",
max_age=SESSION_MAX_AGE)
return resp
@router.post("/auth/register")
async def auth_register(request: Request):
"""Neuen Benutzer registrieren mit Einladungscode."""
try:
body = await request.json()
except Exception:
body = {}
invite_code = (body.get("invite_code") or "").strip()
name = (body.get("name") or "").strip()
password = (body.get("password") or "").strip()
role = (body.get("role") or "mpa").strip()
if not invite_code or not name or not password or len(password) < 4:
raise HTTPException(status_code=400,
detail="Einladungscode, Name und Passwort (min. 4 Zeichen) erforderlich")
if role not in ("admin", "arzt", "mpa", "empfang"):
role = "mpa"
practices = _load_practices()
target_pid = None
for pid, p in practices.items():
if p.get("invite_code") == invite_code:
target_pid = pid
break
if not target_pid:
raise HTTPException(status_code=403, detail="Ungueltiger Einladungscode")
accounts = _load_accounts()
exists = any(a["display_name"] == name and a.get("practice_id") == target_pid
for a in accounts.values())
if exists:
raise HTTPException(status_code=409, detail="Benutzername bereits vergeben")
uid = uuid.uuid4().hex[:12]
pw_hash, pw_salt = _hash_password(password)
accounts[uid] = {
"user_id": uid,
"practice_id": target_pid,
"display_name": name,
"role": role,
"pw_hash": pw_hash,
"pw_salt": pw_salt,
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
}
_save_accounts(accounts)
token = _create_session(uid, target_pid, name, role)
resp = JSONResponse(content={
"success": True, "user_id": uid, "role": role,
"display_name": name, "practice_id": target_pid,
})
resp.set_cookie("aza_session", token, httponly=True, samesite="lax",
max_age=SESSION_MAX_AGE)
return resp
@router.get("/auth/me")
async def auth_me(request: Request):
"""Aktuelle Session pruefen. Liefert User-Daten oder 401."""
s = _session_from_request(request)
if not s:
return JSONResponse(status_code=401, content={"authenticated": False})
return JSONResponse(content={
"authenticated": True,
"user_id": s["user_id"],
"display_name": s["display_name"],
"role": s["role"],
"practice_id": s["practice_id"],
})
@router.post("/auth/logout")
async def auth_logout(request: Request):
token = request.cookies.get("aza_session", "")
_delete_session(token)
resp = JSONResponse(content={"success": True})
resp.delete_cookie("aza_session")
return resp
@router.get("/auth/needs_setup")
async def auth_needs_setup():
"""Pruefen ob Setup noetig ist (keine Accounts vorhanden)."""
_ensure_default_practice()
accounts = _load_accounts()
has_accounts = any(a.get("practice_id") == DEFAULT_PRACTICE_ID
for a in accounts.values())
practices = _load_practices()
invite_code = practices.get(DEFAULT_PRACTICE_ID, {}).get("invite_code", "")
return JSONResponse(content={
"needs_setup": not has_accounts,
"invite_code": invite_code if not has_accounts else "",
})
# =====================================================================
# USER MANAGEMENT (admin only for invite/role changes)
# =====================================================================
@router.get("/users")
async def empfang_users(request: Request):
"""Liefert Benutzer der Praxis. Offen fuer alle authentifizierten + Legacy."""
s = _session_from_request(request)
if s:
users = _practice_users(s["practice_id"])
return JSONResponse(content={
"users": [u["display_name"] for u in users],
"users_full": users,
"practice_id": s["practice_id"],
})
pid = request.query_params.get("practice_id", "") or DEFAULT_PRACTICE_ID
users = _practice_users(pid)
if users:
return JSONResponse(content={
"users": [u["display_name"] for u in users],
"practice_id": pid,
})
old_file = _DATA_DIR / "empfang_users.json"
if old_file.is_file():
try:
names = json.loads(old_file.read_text(encoding="utf-8"))
if isinstance(names, list):
return JSONResponse(content={"users": names, "practice_id": pid})
except Exception:
pass
return JSONResponse(content={"users": [], "practice_id": pid})
@router.post("/users")
async def empfang_register_user(request: Request):
"""Legacy-kompatibel: Benutzer anlegen/umbenennen/loeschen."""
try:
body = await request.json()
except Exception:
body = {}
name = (body.get("name") or "").strip()
action = (body.get("action") or "add").strip()
pid = DEFAULT_PRACTICE_ID
s = _session_from_request(request)
if s:
pid = s["practice_id"]
if not name:
return JSONResponse(content={"success": False})
accounts = _load_accounts()
if action == "delete":
to_del = [uid for uid, a in accounts.items()
if a["display_name"] == name and a.get("practice_id") == pid]
for uid in to_del:
del accounts[uid]
_save_accounts(accounts)
elif action == "rename":
new_name = (body.get("new_name") or "").strip()
if new_name:
for a in accounts.values():
if a["display_name"] == name and a.get("practice_id") == pid:
a["display_name"] = new_name
_save_accounts(accounts)
else:
exists = any(a["display_name"] == name and a.get("practice_id") == pid
for a in accounts.values())
if not exists:
uid = uuid.uuid4().hex[:12]
pw_hash, pw_salt = _hash_password(name.lower())
accounts[uid] = {
"user_id": uid,
"practice_id": pid,
"display_name": name,
"role": "mpa",
"pw_hash": pw_hash,
"pw_salt": pw_salt,
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
}
_save_accounts(accounts)
users = _practice_users(pid)
return JSONResponse(content={
"success": True,
"users": [u["display_name"] for u in users],
"practice_id": pid,
})
# =====================================================================
# MESSAGE ROUTES (practice-scoped)
# =====================================================================
2026-04-16 15:23:14 +02:00
class EmpfangMessage(BaseModel):
medikamente: str = ""
therapieplan: str = ""
procedere: str = ""
kommentar: str = ""
patient: str = ""
absender: str = ""
zeitstempel: str = ""
practice_id: str = ""
2026-04-16 15:23:14 +02:00
extras: dict = Field(default_factory=dict)
@router.post("/send")
async def empfang_send(msg: EmpfangMessage, request: Request):
s = _session_from_request(request)
pid = msg.practice_id.strip() or (s["practice_id"] if s else DEFAULT_PRACTICE_ID)
absender = msg.absender.strip()
if s and not absender:
absender = s["display_name"]
2026-04-19 20:41:37 +02:00
msg_id = uuid.uuid4().hex[:12]
messages = _load_messages()
thread_id = msg_id
reply_to = (msg.extras or {}).get("reply_to", "")
if reply_to:
for m in messages:
if m.get("id") == reply_to:
thread_id = m.get("thread_id", reply_to)
break
else:
thread_id = reply_to
2026-04-16 15:23:14 +02:00
entry = {
2026-04-19 20:41:37 +02:00
"id": msg_id,
"thread_id": thread_id,
"practice_id": pid,
2026-04-16 15:23:14 +02:00
"medikamente": msg.medikamente.strip(),
"therapieplan": msg.therapieplan.strip(),
"procedere": msg.procedere.strip(),
"kommentar": msg.kommentar.strip(),
"patient": msg.patient.strip(),
"absender": absender,
2026-04-16 15:23:14 +02:00
"zeitstempel": msg.zeitstempel.strip() or time.strftime("%Y-%m-%d %H:%M:%S"),
"empfangen": time.strftime("%Y-%m-%d %H:%M:%S"),
"status": "offen",
"user_id": s["user_id"] if s else "",
2026-04-16 15:23:14 +02:00
}
if msg.extras:
entry["extras"] = msg.extras
messages.insert(0, entry)
_save_messages(messages)
return JSONResponse(content={
"success": True, "id": msg_id, "thread_id": thread_id,
"practice_id": pid,
})
2026-04-16 15:23:14 +02:00
@router.get("/messages")
async def empfang_list(request: Request, practice_id: Optional[str] = Query(None)):
s = _session_from_request(request)
pid = (practice_id or "").strip() or (s["practice_id"] if s else DEFAULT_PRACTICE_ID)
2026-04-16 15:23:14 +02:00
messages = _load_messages()
filtered = _filter_by_practice(messages, pid)
return JSONResponse(content={"success": True, "messages": filtered})
2026-04-16 15:23:14 +02:00
2026-04-19 20:41:37 +02:00
@router.get("/thread/{thread_id}")
async def empfang_thread(thread_id: str, request: Request,
practice_id: Optional[str] = Query(None)):
s = _session_from_request(request)
pid = (practice_id or "").strip() or (s["practice_id"] if s else DEFAULT_PRACTICE_ID)
2026-04-19 20:41:37 +02:00
messages = _load_messages()
thread = [m for m in messages
if m.get("thread_id") == thread_id and _msg_practice(m) == pid]
2026-04-19 20:41:37 +02:00
thread.sort(key=lambda m: m.get("empfangen", ""))
return JSONResponse(content={"success": True, "messages": thread})
2026-04-16 15:23:14 +02:00
@router.post("/messages/{msg_id}/done")
async def empfang_done(msg_id: str):
messages = _load_messages()
2026-04-19 20:41:37 +02:00
target = next((m for m in messages if m.get("id") == msg_id), None)
if not target:
raise HTTPException(status_code=404, detail="Nachricht nicht gefunden")
tid = target.get("thread_id", msg_id)
pid = _msg_practice(target)
2026-04-16 15:23:14 +02:00
for m in messages:
if m.get("thread_id") == tid and _msg_practice(m) == pid:
2026-04-16 15:23:14 +02:00
m["status"] = "erledigt"
2026-04-19 20:41:37 +02:00
_save_messages(messages)
return JSONResponse(content={"success": True})
2026-04-16 15:23:14 +02:00
@router.delete("/messages/{msg_id}")
async def empfang_delete(msg_id: str):
messages = _load_messages()
2026-04-19 20:41:37 +02:00
target = next((m for m in messages if m.get("id") == msg_id), None)
if not target:
2026-04-16 15:23:14 +02:00
raise HTTPException(status_code=404, detail="Nachricht nicht gefunden")
2026-04-19 20:41:37 +02:00
tid = target.get("thread_id", msg_id)
pid = _msg_practice(target)
2026-04-19 20:41:37 +02:00
if tid == msg_id:
new = [m for m in messages
if not (m.get("thread_id", m.get("id")) == msg_id and _msg_practice(m) == pid)
and not (m.get("id") == msg_id)]
2026-04-19 20:41:37 +02:00
else:
new = [m for m in messages if m.get("id") != msg_id]
2026-04-16 15:23:14 +02:00
_save_messages(new)
return JSONResponse(content={"success": True})
# =====================================================================
# TASKS (practice-scoped, server-side)
# =====================================================================
2026-04-19 20:41:37 +02:00
@router.get("/tasks")
async def empfang_tasks_list(request: Request):
s = _session_from_request(request)
pid = s["practice_id"] if s else DEFAULT_PRACTICE_ID
tasks = _load_tasks()
filtered = [t for t in tasks if t.get("practice_id", DEFAULT_PRACTICE_ID) == pid]
return JSONResponse(content={"success": True, "tasks": filtered})
2026-04-19 20:41:37 +02:00
@router.post("/tasks")
async def empfang_tasks_create(request: Request):
s = _session_from_request(request)
pid = s["practice_id"] if s else DEFAULT_PRACTICE_ID
2026-04-19 20:41:37 +02:00
try:
body = await request.json()
2026-04-19 20:41:37 +02:00
except Exception:
body = {}
text = (body.get("text") or "").strip()
if not text:
raise HTTPException(status_code=400, detail="Text erforderlich")
task = {
"task_id": uuid.uuid4().hex[:12],
"practice_id": pid,
"text": text,
"done": False,
"assignee": (body.get("assignee") or "").strip(),
"created_by": s["display_name"] if s else "",
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
}
tasks = _load_tasks()
tasks.insert(0, task)
_save_tasks(tasks)
return JSONResponse(content={"success": True, "task": task})
2026-04-19 20:41:37 +02:00
@router.post("/tasks/{task_id}/update")
async def empfang_tasks_update(task_id: str, request: Request):
2026-04-19 20:41:37 +02:00
try:
body = await request.json()
except Exception:
body = {}
tasks = _load_tasks()
target = next((t for t in tasks if t.get("task_id") == task_id), None)
if not target:
raise HTTPException(status_code=404, detail="Aufgabe nicht gefunden")
if "done" in body:
target["done"] = bool(body["done"])
if "text" in body:
target["text"] = (body["text"] or "").strip() or target["text"]
if "assignee" in body:
target["assignee"] = (body["assignee"] or "").strip()
_save_tasks(tasks)
return JSONResponse(content={"success": True, "task": target})
@router.delete("/tasks/{task_id}")
async def empfang_tasks_delete(task_id: str):
tasks = _load_tasks()
tasks = [t for t in tasks if t.get("task_id") != task_id]
_save_tasks(tasks)
return JSONResponse(content={"success": True})
2026-04-19 20:41:37 +02:00
# =====================================================================
# CLEANUP + PRACTICE INFO
# =====================================================================
2026-04-19 20:41:37 +02:00
@router.post("/cleanup")
async def empfang_cleanup(request: Request):
try:
body = await request.json()
except Exception:
body = {}
max_days = int(body.get("max_age_days", 30))
s = _session_from_request(request)
pid = (body.get("practice_id") or "").strip() or (
s["practice_id"] if s else DEFAULT_PRACTICE_ID)
cutoff = time.strftime(
"%Y-%m-%d %H:%M:%S",
time.localtime(time.time() - max_days * 86400),
)
2026-04-19 20:41:37 +02:00
messages = _load_messages()
before = len(messages)
kept = [
m for m in messages
if _msg_practice(m) != pid
or (m.get("empfangen") or m.get("zeitstempel", "")) >= cutoff
]
2026-04-19 20:41:37 +02:00
removed = before - len(kept)
if removed > 0:
_save_messages(kept)
return JSONResponse(content={
"success": True, "removed": removed, "remaining": len(kept),
})
@router.get("/practice/info")
async def empfang_practice_info(request: Request):
s = _session_from_request(request)
pid = s["practice_id"] if s else DEFAULT_PRACTICE_ID
users = _practice_users(pid)
messages = _filter_by_practice(_load_messages(), pid)
open_count = sum(1 for m in messages if m.get("status") == "offen")
practices = _load_practices()
p = practices.get(pid, {})
result = {
"practice_id": pid,
"practice_name": p.get("name", ""),
"user_count": len(users),
"message_count": len(messages),
"open_count": open_count,
}
if s and s.get("role") == "admin":
result["invite_code"] = p.get("invite_code", "")
return JSONResponse(content=result)
2026-04-19 20:41:37 +02:00
# =====================================================================
# HTML PAGE
# =====================================================================
2026-04-19 20:41:37 +02:00
2026-04-16 15:23:14 +02:00
@router.get("/", response_class=HTMLResponse)
async def empfang_page(request: Request):
html_path = Path(__file__).resolve().parent / "web" / "empfang.html"
if html_path.is_file():
2026-04-19 20:41:37 +02:00
return HTMLResponse(
content=html_path.read_text(encoding="utf-8"),
headers={"Cache-Control": "no-cache, no-store, must-revalidate",
"Pragma": "no-cache", "Expires": "0"},
)
2026-04-16 15:23:14 +02:00
return HTMLResponse(content="<h1>empfang.html nicht gefunden</h1>", status_code=404)