from __future__ import annotations from typing import Any from fastapi import HTTPException, status from backend.auth.dependencies import ( UserPrincipal, create_access_token, default_tenant_id, verify_password, ) from backend.auth.user_directory import ensure_user_directory_schema async def _get_pool(app: Any): pool = getattr(app.state, "db_pool", None) if pool is None: raise HTTPException(status_code=503, detail="Database unavailable.") return pool async def login_with_directory(*, app: Any, email: str, password: str) -> dict[str, Any]: await ensure_user_directory_schema(app) pool = await _get_pool(app) tenant_fallback = default_tenant_id() async with pool.acquire() as conn: row = await conn.fetchrow( """ SELECT id::text, role, password_hash, COALESCE(NULLIF(tenant_id, ''), $2) AS tenant_id FROM users_and_roles WHERE email = $1 AND is_active = TRUE """, email.strip(), tenant_fallback, ) if not row or not verify_password(password, row["password_hash"]): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password.", ) token = create_access_token( user_id=row["id"], role=row["role"], tenant_id=row["tenant_id"], ) return {"access_token": token, "token_type": "bearer", "expires_in": 28800} async def read_authenticated_user_profile(*, app: Any, user: UserPrincipal) -> dict[str, Any]: await ensure_user_directory_schema(app) pool = await _get_pool(app) tenant_scope = user.tenant_id or default_tenant_id() async with pool.acquire() as conn: row = await conn.fetchrow( """ SELECT full_name, email, avatar_url, COALESCE(NULLIF(tenant_id, ''), $2) AS tenant_id FROM users_and_roles WHERE id = $1::uuid AND COALESCE(NULLIF(tenant_id, ''), $2) = $2 """, user.user_id, tenant_scope, ) return { "user_id": user.user_id, "role": user.role, "tenant_id": row["tenant_id"] if row else tenant_scope, "full_name": row["full_name"] if row else None, "email": row["email"] if row else None, "avatar_url": row["avatar_url"] if row else None, } async def list_tenant_users(*, app: Any, user: UserPrincipal) -> list[dict[str, Any]]: await ensure_user_directory_schema(app) pool = await _get_pool(app) tenant_scope = user.tenant_id or default_tenant_id() async with pool.acquire() as conn: rows = await conn.fetch( """ SELECT id::text AS user_id, role, COALESCE(NULLIF(tenant_id, ''), $1) AS tenant_id, full_name, email, avatar_url FROM users_and_roles WHERE is_active = TRUE AND COALESCE(NULLIF(tenant_id, ''), $1) = $2 ORDER BY COALESCE(NULLIF(full_name, ''), email, id::text) ASC """, default_tenant_id(), tenant_scope, ) return [ { "user_id": row["user_id"], "role": row["role"], "tenant_id": row["tenant_id"], "full_name": row["full_name"], "email": row["email"], "avatar_url": row["avatar_url"], } for row in rows ]