68 lines
4.9 KiB
Markdown
68 lines
4.9 KiB
Markdown
# 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`:
|
|
```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.
|