# Prompt 03 — Migrations do `db_empresa_` (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_`, (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`: ```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_` (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` (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_` (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.