"""A user's diagnostics level (E22 S5): what they consented to, and for how long. Three levels, named in the UI Alap / Részletes / Teljes: * INFO — the base, for everyone, no consent. The browser keeps its log in memory; nothing extra is sent or stored. * DEBUG — consented. The server logs this user's requests in detail and stores every line of them, and the browser streams its own log to `POST /api/diagnostics/events`. * TRACE — consented. As DEBUG, plus what the user typed into a search. A raised level always lapses after `ELEVATION_HOURS`: the expiry is computed HERE at consent time, never taken from the client, and an expired level reads as INFO without anything having to clear it. The shared demo account cannot raise its level — its log would mix every visitor's session. Core layer: imports `models` and `sysconfig`. """ from datetime import datetime, timedelta, timezone from typing import Literal, get_args from sqlalchemy.orm import Session from app import sysconfig from app.models import User DiagnosticsLevel = Literal["INFO", "DEBUG", "TRACE"] LEVELS: tuple[str, ...] = get_args(DiagnosticsLevel) BASE: DiagnosticsLevel = "INFO" ELEVATION_HOURS = 24 def effective_level(user: User, now: datetime | None = None) -> DiagnosticsLevel: """The level in force for `user` right now: the consented one until it expires, else BASE.""" level, until = user.diagnostics_level, user.diagnostics_until if level not in LEVELS or level == BASE or until is None: return BASE if until <= (now or datetime.now(timezone.utc)): return BASE return level # type: ignore[return-value] — narrowed by the LEVELS check above def state(db: Session, user: User) -> dict: """The `/api/me` view of it: the level in force, when a raised one lapses, and how long stored log lines are kept — the consent text states that number, so it must be the instance's own.""" level = effective_level(user) return { "level": level, "until": user.diagnostics_until.isoformat() if level != BASE else None, "retention_days": sysconfig.get_int(db, "log_retention_days"), } def set_level(user: User, level: DiagnosticsLevel) -> None: """Record a consent (or its withdrawal). The caller commits. Raising the level again restarts the clock — each consent is for the next ELEVATION_HOURS, not an extension of an older one.""" if level == BASE: user.diagnostics_level = None user.diagnostics_until = None return user.diagnostics_level = level user.diagnostics_until = datetime.now(timezone.utc) + timedelta(hours=ELEVATION_HOURS)