"""User management service: CRUD helpers against DuckDB.""" from typing import Any from ..db import get_conn, get_write_lock from .auth import hash_password async def get_user(user_id: str) -> dict[str, Any] | None: conn = get_conn() row = conn.execute( "SELECT id, email, role FROM users WHERE id = ?", [user_id] ).fetchone() if row is None: return None return {"id": str(row[0]), "email": row[1], "role": row[2]} async def list_users() -> list[dict[str, Any]]: conn = get_conn() rows = conn.execute( "SELECT id, email, role FROM users ORDER BY email").fetchall() return [{"id": str(r[0]), "email": r[1], "role": r[2]} for r in rows] async def update_user( user_id: str, email: str | None = None, password: str | None = None, ) -> dict[str, Any] | None: """Update email and/or password. Returns updated user or None if not found.""" conn = get_conn() lock = get_write_lock() if email is None and password is None: return await get_user(user_id) async with lock: if email is not None: existing = conn.execute( "SELECT id FROM users WHERE email = ? AND id != ?", [ email, user_id] ).fetchone() if existing: raise ValueError("Email already in use.") conn.execute( "UPDATE users SET email = ?, updated_at = now() WHERE id = ?", [email, user_id], ) if password is not None: conn.execute( "UPDATE users SET password_hash = ?, updated_at = now() WHERE id = ?", [hash_password(password), user_id], ) row = conn.execute( "SELECT id, email, role FROM users WHERE id = ?", [user_id] ).fetchone() if row is None: return None return {"id": str(row[0]), "email": row[1], "role": row[2]} async def set_user_role(user_id: str, role: str) -> dict[str, Any] | None: conn = get_conn() lock = get_write_lock() async with lock: conn.execute( "UPDATE users SET role = ?, updated_at = now() WHERE id = ?", [role, user_id], ) row = conn.execute( "SELECT id, email, role FROM users WHERE id = ?", [user_id] ).fetchone() if row is None: return None return {"id": str(row[0]), "email": row[1], "role": row[2]} async def delete_user(user_id: str) -> bool: """Delete user and their refresh tokens. Returns True if a row was removed.""" conn = get_conn() lock = get_write_lock() async with lock: conn.execute("DELETE FROM refresh_tokens WHERE user_id = ?", [user_id]) conn.execute("DELETE FROM users WHERE id = ?", [user_id]) return True