Files
2026-09-29 12:12:31 -03:00

4.9 KiB

Prompt 03 — Migrations do db_empresa_<slug> (tenant) e criação dinâmica de database

Contexto

Prompts 01 e 02 concluídos: projeto no ar, db_master com schema completo. Leia 10-tracker-progresso.md antes de começar.

Este prompt cria: (a) o schema que roda dentro de cada db_empresa_<slug>, (b) o mecanismo que cria um novo database Postgres + roda essas migrations quando uma empresa nova é cadastrada, e (c) o cache de conexões por tenant.

Objetivo

Implementar TenantDataSourceProvider (cria/reaproveita conexão por empresa_id) e TenantProvisioner (cria o database físico + roda migrations quando uma empresa é criada), e as migrations do schema operacional (condomínios, contatos, avisos etc. da arquitetura v3.0).

Especificação

1. Migrations — src/main/resources/migrations/tenant/

Criar, na ordem, usando o DDL exato da seção 2 do documento de arquitetura v3.0:

  • 001_condominios.sql
  • 002_contatos.sql
  • 003_contato_condominio.sql
  • 004_funcionario_condominio.sql
  • 005_espacos_comuns.sql
  • 006_assembleias.sql
  • 007_documentos.sql
  • 008_avisos.sql
  • 009_aviso_documentos.sql
  • 010_aviso_destinatarios.sql
  • 011_log_auditoria.sql
  • 012_indices.sql:
CREATE INDEX idx_contato_condominio_condominio ON contato_condominio(condominio_id);
CREATE INDEX idx_contato_condominio_contato ON contato_condominio(contato_id);
CREATE INDEX idx_funcionario_condominio_condominio ON funcionario_condominio(condominio_id);
CREATE INDEX idx_espacos_condominio ON espacos_comuns(condominio_id);
CREATE INDEX idx_assembleias_condominio ON assembleias(condominio_id, data_hora);
CREATE INDEX idx_documentos_condominio ON documentos(condominio_id);
CREATE INDEX idx_avisos_condominio ON avisos(condominio_id, data_publicacao);
CREATE INDEX idx_avisos_assembleia ON avisos(assembleia_id);
CREATE INDEX idx_aviso_destinatarios_aviso ON aviso_destinatarios(aviso_id);

Importante: confira o DDL contra o documento v3.0 antes de escrever — avisos.assembleia_id é opcional (nullable), aviso_destinatarios.destinatario_id NÃO tem FK (referencia contatos.id OU db_master.funcionarios.id dependendo de tipo_destinatario), funcionario_condominio.funcionario_id também não tem FK (cross-database).

2. TenantProvisioner

Classe responsável por, dado um nome e slug de empresa:

  1. Conectar no Postgres com um usuário administrativo (não o app comum) e rodar CREATE DATABASE db_empresa_<slug> (sanitizar o slug: só [a-z0-9_], gerar erro claro se inválido).
  2. Abrir conexão no novo database recém-criado.
  3. Rodar MigrationRunner.runMigrations(conn, "migrations/tenant") nele.
  4. Retornar sucesso/erro de forma que o chamador (Prompt 05, ao cadastrar empresa) possa tratar — se falhar no meio, o ideal é logar claramente e deixar o registro em empresas.status como algo como provisionamento_pendente (pode adicionar esse status à coluna livremente, é VARCHAR).

3. TenantDataSourceProvider

  • Um Map<Long empresaId, Connection> (ou pool simples por tenant) em memória, populado sob demanda: na primeira requisição que precisa do tenant X, abre a conexão em db_empresa_<database_name> (buscando database_name na tabela empresas do master) e guarda no cache.
  • Método getConnection(long empresaId) — se não estiver no cache, busca database_name no master, conecta, guarda, retorna.
  • Não precisa de invalidação de cache sofisticada nesta fase — documentar como ponto de atenção futuro (ex: quando uma empresa é cancelada/bloqueada, talvez fechar a conexão do cache; deixar um método invalidar(empresaId) já pronto, mesmo que não seja chamado ainda).

4. DAOs básicos do tenant (sem endpoints ainda — vêm no Prompt 06)

Criar em com.seconectai.condocontab.tenant.dao, todos recebendo a Connection do tenant como parâmetro (não guardam estado):

  • CondominioDao (inserir, buscarPorId, listarTodos)
  • ContatoDao (inserir, buscarPorId)
  • ContatoCondominioDao (vincular, listarPorCondominio(condominioId), buscarSindicoAtual(condominioId) — query com WHERE tipo='sindico' AND status='ativo')

Critérios de aceite

  • Rodar TenantProvisioner.provisionar("Empresa Teste", "empresa-teste") cria de fato um novo database db_empresa_empresa_teste no Postgres, com as 11 tabelas do schema tenant.
  • Rodar de novo para outro slug cria outro database, isolado do primeiro (inserir um condomínio em um não aparece no outro).
  • TenantDataSourceProvider.getConnection(empresaId) retorna conexão válida e reaproveita a mesma conexão em chamadas subsequentes (validar com log ou teste).
  • Slug inválido (ex: com espaço ou maiúscula) é rejeitado com mensagem de erro clara, sem tentar criar o database.
  • ContatoCondominioDao.buscarSindicoAtual retorna null/vazio quando não há síndico ativo, sem lançar exceção.

Ao concluir

Atualizar 10-tracker-progresso.md: Prompt 03 concluído, data, resumo. Confirmar Prompts 01 e 02 seguem marcados.