Backup automatico script del 2026-08-23 12:04
This commit is contained in:
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),
|
||||
}
|
||||
Reference in new issue
Block a user