Backup automatico script del 2026-08-23 12:04

This commit is contained in:
daniele committed 2026-08-23 12:04:16 +02:00
1 parent 11823447a2
commit 795a15a7b6
66 files changed
+8546

No files matched your search

@@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
"""Client Turni-Live — turni.loogle.it (JWT Bearer)."""
import logging
import os
import threading
import time
from typing import Any, Optional
from urllib.parse import urljoin
import httpx
LOGGER = logging.getLogger("loogle_mcp.turni")
MCP_USERS = ("daniele", "lucia", "davide", "luca")
MCP_TO_SERVICE_USER = {
"daniele": "daniely",
"lucia": "lucia",
"davide": "davide",
"luca": "luca",
}
PUBLIC_URL = os.environ.get("TURNI_URL", "https://turni.loogle.it").rstrip("/")
API_URL = os.environ.get("TURNI_API_URL", PUBLIC_URL).rstrip("/")
_jwt_cache: dict[str, tuple[str, float]] = {}
_jwt_lock = threading.Lock()
JWT_TTL = 3600 * 6
def _service_username(mcp_username: str) -> str:
return MCP_TO_SERVICE_USER.get(mcp_username.lower(), mcp_username.lower())
def _password_for_user(mcp_username: str) -> Optional[str]:
user = mcp_username.lower()
pwd = os.environ.get(f"TURNI_PASSWORD_{user.upper()}", "").strip()
if pwd:
return pwd
return os.environ.get("TURNI_PASSWORD", "").strip() or None
def _jwt_for_user(mcp_username: str) -> Optional[str]:
user = mcp_username.lower()
direct = os.environ.get(f"TURNI_JWT_{user.upper()}", "").strip()
if direct:
return direct
return os.environ.get("TURNI_JWT", "").strip() or None
def is_configured(username: Optional[str] = None) -> bool:
user = (username or "daniele").lower()
if _jwt_for_user(user):
return True
if _password_for_user(user):
return True
return False
def _store_jwt(mcp_username: str, token: str) -> None:
with _jwt_lock:
_jwt_cache[mcp_username.lower()] = (token, time.time() + JWT_TTL)
def _cached_jwt(mcp_username: str) -> Optional[str]:
with _jwt_lock:
row = _jwt_cache.get(mcp_username.lower())
if not row:
return None
token, expires = row
if time.time() > expires:
_jwt_cache.pop(mcp_username.lower(), None)
return None
return token
def login(mcp_username: str) -> str:
cached = _cached_jwt(mcp_username)
if cached:
return cached
preset = _jwt_for_user(mcp_username)
if preset:
_store_jwt(mcp_username, preset)
return preset
password = _password_for_user(mcp_username)
if not password:
raise RuntimeError(
f"Turni non configurato per {mcp_username}. "
f"Imposta TURNI_PASSWORD_{mcp_username.upper()} o TURNI_JWT_{mcp_username.upper()}"
)
service_user = _service_username(mcp_username)
verify = os.environ.get("TURNI_VERIFY_SSL", "true").strip().lower() not in (
"0", "false", "no", "off",
)
url = urljoin(API_URL + "/", "api/auth/login")
with httpx.Client(timeout=30.0, verify=verify) as client:
resp = client.post(url, json={"username": service_user, "password": password})
if resp.status_code != 200:
raise RuntimeError(f"Login Turni fallito: HTTP {resp.status_code}")
data = resp.json()
token = data.get("token")
if not token:
raise RuntimeError("Login Turni: token JWT mancante")
_store_jwt(mcp_username, token)
return token
def _request(
method: str,
path: str,
*,
mcp_username: str,
params: Optional[dict] = None,
) -> Any:
token = login(mcp_username)
verify = os.environ.get("TURNI_VERIFY_SSL", "true").strip().lower() not in (
"0", "false", "no", "off",
)
url = path if path.startswith("http") else urljoin(API_URL + "/", path.lstrip("/"))
headers = {"Authorization": f"Bearer {token}"}
with httpx.Client(timeout=60.0, verify=verify) as client:
resp = client.request(method, url, headers=headers, params=params)
if resp.status_code == 401:
with _jwt_lock:
_jwt_cache.pop(mcp_username.lower(), None)
headers["Authorization"] = f"Bearer {login(mcp_username)}"
resp = client.request(method, url, headers=headers, params=params)
if resp.status_code >= 400:
raise RuntimeError(f"Turni {path}: HTTP {resp.status_code} {resp.text[:200]}")
return resp.json()
def get_status() -> dict:
verify = os.environ.get("TURNI_VERIFY_SSL", "true").strip().lower() not in (
"0", "false", "no", "off",
)
url = urljoin(API_URL + "/", "api/status")
with httpx.Client(timeout=30.0, verify=verify) as client:
resp = client.get(url)
resp.raise_for_status()
return resp.json()
def list_doctors(mcp_username: str) -> Any:
return _request("GET", "/api/doctors", mcp_username=mcp_username)
def get_shift_assignments(
mcp_username: str,
*,
from_date: Optional[str] = None,
to_date: Optional[str] = None,
limit: int = 100,
) -> Any:
params: dict = {}
if from_date:
params["from"] = from_date
if to_date:
params["to"] = to_date
data = _request("GET", "/api/shift-assignments", mcp_username=mcp_username, params=params or None)
if isinstance(data, list):
return data[:limit]
if isinstance(data, dict):
items = data.get("assignments") or data.get("items") or data.get("results")
if isinstance(items, list):
return items[:limit]
return data
def get_my_shifts(
mcp_username: str,
*,
from_date: Optional[str] = None,
to_date: Optional[str] = None,
limit: int = 50,
) -> dict:
"""Turni dell'utente MCP: filtra per doctorId collegato o per nome medico."""
user_info = _request("GET", "/api/users/me", mcp_username=mcp_username)
doctor_id = user_info.get("doctorId")
assignments = get_shift_assignments(
mcp_username, from_date=from_date, to_date=to_date, limit=500,
)
if not isinstance(assignments, list):
return {"user": user_info, "assignments": assignments}
if doctor_id:
mine = [a for a in assignments if a.get("doctorId") == doctor_id or a.get("doctor_id") == doctor_id]
else:
service_user = _service_username(mcp_username)
doctors = list_doctors(mcp_username)
doc_ids = set()
if isinstance(doctors, list):
for doc in doctors:
name = (doc.get("name") or doc.get("fullName") or "").lower()
if service_user.lower() in name or mcp_username.lower() in name:
doc_ids.add(doc.get("id") or doc.get("doctorId"))
mine = [
a for a in assignments
if (a.get("doctorId") or a.get("doctor_id")) in doc_ids
] if doc_ids else assignments[:limit]
return {
"user": {
"username": user_info.get("username"),
"role": user_info.get("role"),
"doctorId": doctor_id,
},
"assignments": mine[:limit],
"count": len(mine),
}