"""Account activity and the shared transaction lock for channel ownership.""" from __future__ import annotations import hashlib from datetime import datetime, timedelta from sqlalchemy import func, select, text, update from .models import HockeyWebSession, MatchParticipant ACCOUNT_IDLE_SECONDS = 2 * 60 * 60 def lock_account(session, user_id: str) -> None: if session.get_bind().dialect.name == "postgresql": key = int.from_bytes(hashlib.sha256(("hockey-channel:" + user_id).encode()).digest()[:8], "big", signed=True) session.execute(text("SELECT pg_advisory_xact_lock(:key)"), {"key": key}) else: session.execute(update(MatchParticipant).where(MatchParticipant.user_id == user_id) .values(last_seen_at=MatchParticipant.last_seen_at)) def account_is_idle(session, user_id: str, now: datetime) -> bool: # All browser logins of this account count, including the last action before # logout. Polling, OperatorSession timestamps and Agent traffic never count. latest = session.scalar(select(func.max(HockeyWebSession.last_activity_at)).where( HockeyWebSession.wfl_user_id == user_id)) # Header-based development/test auth has no persisted browser logins. return latest is not None and latest <= now - timedelta(seconds=ACCOUNT_IDLE_SECONDS)