from collections.abc import AsyncGenerator from fastapi import Request from sqlalchemy.ext.asyncio import ( AsyncEngine, create_async_engine, async_sessionmaker, AsyncSession, ) from sqlalchemy import text from utils.db_utils import get_database_url_and_connect_args database_url, connect_args = get_database_url_and_connect_args() sql_engine: AsyncEngine = create_async_engine(database_url, connect_args=connect_args) async_session_maker = async_sessionmaker(sql_engine, expire_on_commit=False) async def get_async_session(request: Request = None) -> AsyncGenerator[AsyncSession, None]: """ Get async database session with RLS (Row-Level Security) context. Sets PostgreSQL session variables for RLS policies based on authenticated user: - app.current_user_id: UUID of authenticated user - app.user_role: User's role (super_admin, client_admin, user) """ async with async_session_maker() as session: # Set RLS session variables if user is authenticated if request and hasattr(request.state, "user") and request.state.user: user = request.state.user try: # PostgreSQL SET LOCAL doesn't support parameterized queries # Must use string formatting (safe because user.id is UUID and role is enum) await session.execute( text(f"SET LOCAL app.current_user_id = '{user.id}'") ) await session.execute( text(f"SET LOCAL app.user_role = '{user.role}'") ) except Exception as e: # Log but don't fail - RLS variables are defense-in-depth print(f"Warning: Failed to set RLS variables: {e}") yield session async def create_db_and_tables(): """No-op: migrations are now handled by Alembic.""" pass