api-admim/app/multi_tenant/criar_tenant.py

166 lines
7.1 KiB
Python

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}")