""" User store — PostgreSQL-backed. Keyed by Azure AD oid (object ID). """ import logging from datetime import datetime, timezone from typing import Dict, Optional from ..config_runtime import server_config from ..db.pool import get_pool logger = logging.getLogger(__name__) def _row_to_dict(row) -> Dict: return { 'id': row['id'], 'email': row['email'], 'name': row['name'], 'role': row['role'], 'active': row['active'], 'created': row['created_at'].isoformat() if row['created_at'] else None, 'last_seen': row['last_seen_at'].isoformat() if row['last_seen_at'] else None, } async def get_user(user_id: str) -> Optional[Dict]: pool = get_pool() async with pool.acquire() as conn: row = await conn.fetchrow('SELECT * FROM users WHERE id = $1', user_id) return _row_to_dict(row) if row else None async def upsert_user(user_id: str, email: str, name: str, role: Optional[str] = None) -> Dict: """ Create or update user. On first creation, grants admin if email is in ADMIN_EMAILS. """ pool = get_pool() async with pool.acquire() as conn: existing = await conn.fetchrow('SELECT * FROM users WHERE id = $1', user_id) if existing is None: default_role = 'admin' if email and email.lower() in server_config.ADMIN_EMAILS else 'user' row = await conn.fetchrow(''' INSERT INTO users (id, email, name, role, active) VALUES ($1, $2, $3, $4, TRUE) RETURNING * ''', user_id, email or '', name or '', role or default_role) else: new_role = role if role is not None else existing['role'] row = await conn.fetchrow(''' UPDATE users SET email = $2, name = $3, role = $4, last_seen_at = NOW() WHERE id = $1 RETURNING * ''', user_id, email or existing['email'], name or existing['name'], new_role) return _row_to_dict(row) async def list_users() -> list: pool = get_pool() async with pool.acquire() as conn: rows = await conn.fetch('SELECT * FROM users ORDER BY last_seen_at DESC') return [_row_to_dict(r) for r in rows] async def set_role(user_id: str, role: str) -> Optional[Dict]: if role not in ('user', 'admin'): return None pool = get_pool() async with pool.acquire() as conn: row = await conn.fetchrow( 'UPDATE users SET role = $2 WHERE id = $1 RETURNING *', user_id, role ) return _row_to_dict(row) if row else None async def set_active(user_id: str, active: bool) -> Optional[Dict]: pool = get_pool() async with pool.acquire() as conn: row = await conn.fetchrow( 'UPDATE users SET active = $2 WHERE id = $1 RETURNING *', user_id, active ) return _row_to_dict(row) if row else None