from alembic.runtime.migration import MigrationContext from alembic.config import Config from alembic.script import ScriptDirectory from sqlalchemy.ext.asyncio import AsyncEngine from sqlalchemy import select, insert from app.database.models import Inquilino, RbacUser, RbacPapel, rbac_papeis_usuario from app.database.session import sessionmanager from app.rbac.auth import get_user_db, get_user_manager from app.rbac.schemas import UserCreate from fastapi_users.exceptions import UserAlreadyExists from sqlalchemy import inspect import json # ------------------------------------------------------------------------------ # Debug helpers (compartilhados) # ------------------------------------------------------------------------------ from app.common.debug import make_dbg, make_dbg_lazy _DBG_NS = "NovoInquilino" # rótulo curto para logar a origem deste módulo _dbg = make_dbg(_DBG_NS) _dbg_lazy = make_dbg_lazy(_DBG_NS) async def check_migrations(engine: AsyncEngine): """ Verifica se todas as migrações foram aplicadas. """ _dbg("check_migrations(): início") alembic_config = Config("alembic.ini") script = ScriptDirectory.from_config(alembic_config) async with engine.begin() as conn: def sync_check_migrations(sync_conn): context = MigrationContext.configure(sync_conn) current_revision = context.get_current_revision() latest_revision = script.get_current_head() _dbg(f"check_migrations(): current={current_revision}, latest={latest_revision}") if current_revision != latest_revision: raise RuntimeError( f"Database is not up-to-date. Current: {current_revision}, Latest: {latest_revision}. " "Execute migrations before adding new tenants." ) await conn.run_sync(sync_check_migrations) _dbg("check_migrations(): OK (up-to-date)") async def create_user(session, fk_inquilino_uuid, email, password, nome, is_superuser=False): """ Cria um usuário no sistema utilizando o gerenciador de usuários do FastAPI Users. """ _dbg("create_user(): início") try: _dbg("create_user(): preparando user_db/user_manager") user_db = await get_user_db(session).__anext__() user_manager = await get_user_manager(user_db).__anext__() _dbg("create_user(): user_manager OK") try: _dbg( "create_user(): chamando user_manager.create(UserCreate(...)) " f"payload={{'nome_completo': {nome!r}, 'email': {email!r}, " f"'is_superuser': {is_superuser!r}, 'fk_inquilino_uuid': {fk_inquilino_uuid}}}" ) user = await user_manager.create( UserCreate( nome_completo=nome, email=email, password=password, is_superuser=is_superuser, is_active=True, fk_inquilino_uuid=fk_inquilino_uuid, ) ) _dbg(f"create_user(): OK user_id={user.id}") return user.id except UserAlreadyExists: _dbg(f"create_user(): UserAlreadyExists para email={email!r}, consultando id existente...") result_user = await session.execute(select(RbacUser).filter_by(email=email)) existing_user = result_user.scalars().first() raise RuntimeError(f"Usuário '{email}' já existe, seu ID é {existing_user.id}") except Exception as e: _dbg(f"create_user(): ERRO -> {e!r}") raise RuntimeError(f"Erro ao criar usuário '{email}': {e}") async def tenant_create(nome: str, email: str, password: str, cpf_cnpj: str): """ Cria um novo tenant (inquilino) no sistema, configura o schema específico e registra um usuário inicial relacionado ao inquilino. """ _dbg(f"tenant_create(): início payload={{'nome': {nome!r}, 'email': {email!r}, 'cpf_cnpj': {cpf_cnpj!r}}}") async with sessionmanager.session() as db: try: # Verificar se o tenant já existe _dbg("tenant_create(): verificando existência do inquilino por cpf_cnpj...") result = await db.execute(select(Inquilino).filter_by(cpf_cnpj=cpf_cnpj)) existing_tenant = result.scalars().first() if existing_tenant is None: _dbg("tenant_create(): inquilino inexistente (OK para criar)") else: data = {c.key: getattr(existing_tenant, c.key) for c in inspect(existing_tenant).mapper.columns} _dbg("tenant_create(): inquilino já existe -> " + json.dumps(data, ensure_ascii=False, default=str, indent=2)) _dbg("tenant_create(): criando registro do inquilino...") if existing_tenant: _dbg("tenant_create(): ABORT -> tenant já existe") raise RuntimeError( f"Tenant com CPF/CNPJ '{cpf_cnpj}' já existe. Nome: {existing_tenant.nome}, " f"UUID: {existing_tenant.uuid}" ) # Criar o registro do tenant tenant = Inquilino(nome=nome, cpf_cnpj=cpf_cnpj) db.add(tenant) await db.commit() await db.refresh(tenant) _dbg(f"tenant_create(): inquilino criado uuid={tenant.uuid}") # Criar o usuário inicial _dbg("tenant_create(): criando usuário inicial (superuser)...") user_id = await create_user( session=db, nome=nome, fk_inquilino_uuid=tenant.uuid, email=email, password=password, is_superuser=True, ) _dbg(f"tenant_create(): usuário criado id={user_id}") # Nova sessão para associar o papel ao usuário _dbg("tenant_create(): vinculando papel 'Super Administrador' ao usuário...") async with sessionmanager.session() as new_db: # Buscar o papel "Super Administrador" result_papel = await new_db.execute(select(RbacPapel).filter_by(nome="Super Administrador")) papel = result_papel.scalars().first() if not papel: _dbg("tenant_create(): ERRO -> Papel 'Super Administrador' não encontrado") raise RuntimeError("Papel 'Super Administrador' não encontrado.") # Relacionar o papel ao usuário await new_db.execute( insert(rbac_papeis_usuario).values( user_uuid=user_id, papel_uuid=papel.uuid, ) ) await new_db.commit() _dbg("tenant_create(): papel vinculado OK") _dbg("tenant_create(): fim OK") return tenant.uuid except RuntimeError as e: _dbg(f"tenant_create(): ERRO (RuntimeError) -> {e!r}") raise RuntimeError(f"Erro inesperado durante a criação do cliente: '{nome}': {e}") except Exception as e: _dbg(f"tenant_create(): ERRO (Exception) -> {e!r}") raise RuntimeError(f"Erro inesperado durante a criação do cliente: '{nome}': {e}")