@seedgrid/ai-toolkit 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/adapters/claude.js +114 -0
- package/dist/adapters/codex.js +108 -0
- package/dist/adapters/index.js +17 -0
- package/dist/adapters/shared.js +142 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +203 -0
- package/dist/commands/common.js +98 -0
- package/dist/commands/doctor.js +290 -0
- package/dist/commands/init.js +9 -0
- package/dist/commands/install.js +107 -0
- package/dist/commands/remove.js +54 -0
- package/dist/commands/review.js +127 -0
- package/dist/commands/status.js +73 -0
- package/dist/commands/update.js +8 -0
- package/dist/commands/validate.js +27 -0
- package/dist/core/apply.js +181 -0
- package/dist/core/blocks.js +64 -0
- package/dist/core/content.js +528 -0
- package/dist/core/env.js +40 -0
- package/dist/core/frontmatter.js +29 -0
- package/dist/core/hash.js +14 -0
- package/dist/core/inspect.js +51 -0
- package/dist/core/lock.js +124 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/plan.js +242 -0
- package/dist/core/tools.js +62 -0
- package/dist/core/types.js +11 -0
- package/dist/core/updates.js +121 -0
- package/docs/arquitetura.md +80 -0
- package/docs/compatibilidade.md +50 -0
- package/docs/formato.md +155 -0
- package/docs/instalacao.md +105 -0
- package/docs/manutencao.md +115 -0
- package/package.json +43 -4
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-enums.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -0
- package/seedgridKB/SeedGrid/Extensions/Indice.md +63 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Enums.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +97 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao-Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Enums.md +33 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Exemplos.md +121 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Rust.md +126 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao.md +127 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Enums.md +66 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Exemplos.md +151 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Rust.md +120 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao-Rust.md +136 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao.md +319 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Enums.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Exemplos.md +210 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Rust.md +135 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Visao-geral.md +150 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao-Rust.md +86 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao.md +243 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Enums.md +165 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Rust.md +109 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Visao-geral.md +181 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Configuracao-Rust.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao-Rust.md +73 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Enums.md +36 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Rust.md +81 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Configuracao.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Enums.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +122 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Enums.md +29 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +82 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +118 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao-Rust.md +110 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Exemplos.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Rust.md +70 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Visao-geral.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao-Rust.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +195 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Enums.md +71 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Rust.md +139 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +130 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Configuracao.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Enums.md +52 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Visao-geral.md +113 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao-Rust.md +67 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao.md +94 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Enums.md +79 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Exemplos.md +172 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Rust.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao-Rust.md +72 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Exemplos.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Visao-geral.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao-Rust.md +68 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Enums.md +96 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Exemplos.md +229 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Rust.md +80 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Configuracao-Rust.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao-Rust.md +327 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao.md +510 -0
- package/seedgridKB/SeedGrid/Extensions/security/Enums.md +95 -0
- package/seedgridKB/SeedGrid/Extensions/security/Exemplos.md +218 -0
- package/seedgridKB/SeedGrid/Extensions/security/Rust.md +203 -0
- package/seedgridKB/SeedGrid/Extensions/security/Visao-geral.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust.md +188 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +345 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Enums.md +117 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Rust.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +142 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao-Rust.md +75 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao.md +152 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Enums.md +198 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Exemplos.md +173 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Rust.md +83 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/API.md +257 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Exemplos.md +307 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Visao-geral.md +51 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/API.md +174 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Exemplos.md +236 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Visao-geral.md +76 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/API.md +339 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Exemplos.md +311 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Visao-geral.md +57 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +125 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Exemplos.md +116 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Exemplos.md +544 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Visao-geral.md +69 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/API.md +113 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Exemplos.md +159 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Visao-geral.md +92 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/API.md +516 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Visao-geral.md +80 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/API.md +615 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Exemplos.md +384 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/API.md +661 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Exemplos.md +312 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/API.md +1090 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Exemplos.md +1172 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-apikeys.md +118 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-audit.md +88 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-backup.md +131 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-platform.md +132 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-user.md +120 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-login-tenancy.md +119 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-security.md +427 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-self-enter.md +104 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-subdomain-tenancy.md +110 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +561 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Visao-geral.md +82 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/Visao-geral.md +68 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-commons.md +61 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-core.md +140 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-playground.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-theme.md +97 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/API.md +177 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Exemplos.md +332 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Visao-geral.md +106 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/showcase.md +70 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +53 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +45 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +43 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +41 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +44 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +40 -0
- package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +78 -0
- package/seedgridKB/SeedGrid/Toolkit/references/glossario.md +15 -0
- package/seedgridKB/SeedGrid/Toolkit/references/modos-shared-native.md +27 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +54 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +39 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/formato-entrega.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +47 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/personalizacao-local.md +22 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/planejar-mudanca/SKILL.md +38 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/SKILL.md +33 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/references/checklist.md +25 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-backend-crud/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-bump/SKILL.md +26 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/SKILL.md +221 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/checklist.md +31 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/patterns.md +185 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/routing.md +66 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/SKILL.md +56 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/checklist.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/steps.md +114 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-review/SKILL.md +61 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/validar-mudanca/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/especificacao-tarefa.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/relatorio-validacao.md +30 -0
- package/seedgridKB/SeedGrid/Versoes.md +64 -0
- package/seedgridKB/SeedGrid/Visao-geral.md +35 -0
- package/seedgridKB/manifest.json +142 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, subdomain-tenancy, exemplos]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-subdomain-tenancy, java/examples/seedgrid-subdomain-tenancy-example
|
|
4
|
+
branch: main
|
|
5
|
+
commit: fe62530a29
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# subdomain-tenancy — Exemplos (Quarkus)
|
|
10
|
+
|
|
11
|
+
Visão geral: [[SeedGrid/Extensions/subdomain-tenancy/Visao-geral|Visão geral do subdomain-tenancy]] · Catálogo de propriedades: [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração do subdomain-tenancy]] · [[SeedGrid/Extensions/subdomain-tenancy/Rust|Porte Rust]]
|
|
12
|
+
|
|
13
|
+
Fontes: app de exemplo `java/examples/seedgrid-subdomain-tenancy-example` (repositório git próprio, commit `cd83edd` — fora da tabela de commits da análise), `README.md` da extension e o código do runtime (commit `fe62530a29`). Outro consumidor no monorepo: `java/seedgrid-test-multitenancy`.
|
|
14
|
+
|
|
15
|
+
## 1. Dependência
|
|
16
|
+
|
|
17
|
+
```xml
|
|
18
|
+
<dependency>
|
|
19
|
+
<groupId>br.com.seedgrid</groupId>
|
|
20
|
+
<artifactId>seedgrid-subdomain-tenancy</artifactId>
|
|
21
|
+
<version>2026.10.14-1</version>
|
|
22
|
+
</dependency>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
O exemplo usa a propriedade `seedgrid.subdomain-tenancy.version` (`2026.10.13` no pom dele) e importa o parent da extension como BOM para não pinar commons/security/billing… à mão (comentário no `pom.xml` do exemplo).
|
|
26
|
+
|
|
27
|
+
## 2. Configuração do host (trecho real do exemplo)
|
|
28
|
+
|
|
29
|
+
`java/examples/seedgrid-subdomain-tenancy-example/src/main/resources/application.properties` — segredos substituídos por placeholders e só com as chaves que têm efeito:
|
|
30
|
+
|
|
31
|
+
```properties
|
|
32
|
+
# Entidades do app somadas às da lib (reaproveita as chaves da extension)
|
|
33
|
+
quarkus.hibernate-orm.packages=${seedgrid.subdomain-tenancy.tenant.entity-packages},br.com.seedgrid.security.entities,br.com.seedgrid.backup.entities,br.com.seedgrid.apikeys.entities
|
|
34
|
+
quarkus.hibernate-orm.master.packages=${seedgrid.subdomain-tenancy.master.entity-packages},br.com.seedgrid.subdomaintenancy.entities
|
|
35
|
+
|
|
36
|
+
seedgrid.subdomain-tenancy.product-prefix=${PRODUCT_PREFIX:kb}
|
|
37
|
+
|
|
38
|
+
# Ponte do datasource dos TENANTS com env vars curtas (criadas pelo exemplo, não pela lib)
|
|
39
|
+
seedgrid.subdomain-tenancy.tenant.datasource.jdbc.url=${SEEDGRID_SUBDOMAIN_TENANCY_TENANT_DB_URL:jdbc:postgresql://localhost:5439}
|
|
40
|
+
seedgrid.subdomain-tenancy.tenant.datasource.username=${SEEDGRID_SUBDOMAIN_TENANCY_TENANT_DB_USERNAME:postgres}
|
|
41
|
+
seedgrid.subdomain-tenancy.tenant.datasource.password=<SEGREDO:tenant-db-password>
|
|
42
|
+
|
|
43
|
+
# Bootstrap de startup do security desligado (pressupõe banco único)
|
|
44
|
+
seedgrid.security.bootstrap.enabled=false
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Observação (**Confirmado** no código da extension): `domain-suffix`, `master.datasource.*`, `mailer.*`, `admin-user.*`, `cleanup.*` vêm do `microprofile-config.properties` da extension e são ajustadas por env vars `SEEDGRID_SUBDOMAIN_TENANCY_*` (lista em [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração]]).
|
|
48
|
+
|
|
49
|
+
## 3. Entidades e repositórios: master × tenant
|
|
50
|
+
|
|
51
|
+
PU default = banco do tenant (resolvido pelo host). PU `master` = banco global. Exemplo real (`repository/PaymentPlanRepository.java`):
|
|
52
|
+
|
|
53
|
+
```java
|
|
54
|
+
@ApplicationScoped
|
|
55
|
+
@PersistenceUnit("master")
|
|
56
|
+
public class PaymentPlanRepository implements PanacheRepository<PaymentPlan> {
|
|
57
|
+
|
|
58
|
+
public List<PaymentPlan> findByDescription(String description) {
|
|
59
|
+
return list("LOWER(description) LIKE LOWER(?1)", "%" + description + "%");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
O `PersonRepository` (entidade `entity/tenant/Person`) não usa `@PersistenceUnit` e, portanto, opera no banco do tenant corrente.
|
|
65
|
+
|
|
66
|
+
## 4. Recurso visível só em tenants (`@TenantOnly`)
|
|
67
|
+
|
|
68
|
+
Trecho real de `resource/PersonResource.java`:
|
|
69
|
+
|
|
70
|
+
```java
|
|
71
|
+
@Path("/api/persons")
|
|
72
|
+
@Produces(MediaType.APPLICATION_JSON)
|
|
73
|
+
@Consumes(MediaType.APPLICATION_JSON)
|
|
74
|
+
@Tag(name = "Persons", description = "Person management (TENANT)")
|
|
75
|
+
@Tag(name = "tenant-only", description = "Endpoints disponíveis apenas no contexto de tenant")
|
|
76
|
+
@SecurityRequirement(name = "bearerAuth")
|
|
77
|
+
@TenantOnly
|
|
78
|
+
@Blocking
|
|
79
|
+
public class PersonResource {
|
|
80
|
+
|
|
81
|
+
final PersonService service;
|
|
82
|
+
public PersonResource(PersonService service) {
|
|
83
|
+
this.service = service;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@GET
|
|
87
|
+
@Operation(summary = "List all persons")
|
|
88
|
+
@Tag(name = "tenant-only")
|
|
89
|
+
public List<PersonDTO> listAll() {
|
|
90
|
+
return service.findAll();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`@TenantOnly`/`@MasterOnly` só afetam o spec servido em `GET /public/tenant-openapi`; não bloqueiam acesso. O exemplo repete `@Tag(name = "tenant-only")` nos métodos — o filtro (`TenantOpenApiController#copyIfAllowed`) trabalha por operação, então a repetição garante que a tag chegue em cada operação do spec (**Inferência**: não foi verificado se a meta-anotação na classe é propagada pelo SmallRye OpenAPI).
|
|
96
|
+
|
|
97
|
+
## 5. Sobrescrevendo uma SPI (PasswordEncoder)
|
|
98
|
+
|
|
99
|
+
Exemplo real (`multitenancy/CustomPasswordEncoder.java`). Um bean normal vence o `BCryptPasswordEncoder` `@DefaultBean` da extension:
|
|
100
|
+
|
|
101
|
+
```java
|
|
102
|
+
@ApplicationScoped
|
|
103
|
+
public class CustomPasswordEncoder implements PasswordEncoder {
|
|
104
|
+
|
|
105
|
+
private static final int SALT_ROUNDS = 12;
|
|
106
|
+
|
|
107
|
+
@Override
|
|
108
|
+
public String encode(String plainPassword) {
|
|
109
|
+
if (plainPassword == null) {
|
|
110
|
+
throw new IllegalArgumentException("Password cannot be null");
|
|
111
|
+
}
|
|
112
|
+
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(SALT_ROUNDS));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 6. Canal próprio para o OTP (SubdomainTenancyNotificationService)
|
|
118
|
+
|
|
119
|
+
Assinaturas reais: `void sendValidationNotification(TenantValidationNotification notification)`; o record tem `recipient`, `recipientName`, `tradeName`, `validationCode`, `expiryMinutes`, `subject`, `htmlContent`, `channelHint` (o `TenantService` envia `channelHint="email"`). Esboço (o corpo é do host):
|
|
120
|
+
|
|
121
|
+
```java
|
|
122
|
+
@ApplicationScoped
|
|
123
|
+
public class WhatsAppValidationNotifier implements SubdomainTenancyNotificationService {
|
|
124
|
+
|
|
125
|
+
@Override
|
|
126
|
+
public void sendValidationNotification(TenantValidationNotification n) {
|
|
127
|
+
// enviar n.validationCode() para n.recipient(), válido por n.expiryMinutes() minutos
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Por ser bean normal, substitui o `MailerNotificationService` (`@DefaultBean`). Se `sendValidationNotification` lançar `RuntimeException`, o `TenantService` não desfaz o cadastro: a resposta do signup vem com `emailDelivered=false`.
|
|
133
|
+
|
|
134
|
+
Para só trocar o HTML do e-mail padrão, coloque um template Qute em `src/main/resources/templates/subdomain-tenancy/confirmation.html` (ou `subdomain-tenancy/html/confirmation.html`) — o `DefaultTemplateSubdomainTenancyProvider` tenta esses caminhos antes do template embutido. Os dados passados ao template não foram detalhados aqui (ver `spi/impl/DefaultTemplateSubdomainTenancyProvider`).
|
|
135
|
+
|
|
136
|
+
## 7. Migrations do host
|
|
137
|
+
|
|
138
|
+
Estrutura real do exemplo:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
src/main/resources/db/migration/
|
|
142
|
+
├── master/ V2000__create_payment_plans_table.sql, V2001__seed_payment_plans_permissions.sql, V2002__create_role_teste_apikey.sql
|
|
143
|
+
└── tenant/ V1__create_persons_table.sql
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`master/` roda no boot (history `flyway_schema_history_master`); `tenant/` roda no provisionamento e nas migrações por tenant (history `flyway_schema_history_tenant`). Para pastas extras use `seedgrid.subdomain-tenancy.{master,tenant}.migrations.location.additional` com versões que não colidam com a base.
|
|
147
|
+
|
|
148
|
+
## 8. Ciclo de vida de um tenant (HTTP)
|
|
149
|
+
|
|
150
|
+
Body conforme `TenantSignupRequest` (`subdomain` `^[a-z0-9_]{5,50}$`, `cnpj` com `@CNPJ`, `rootEmail` `@Email`, representante legal com nome/sobrenome/telefone obrigatórios). Fonte: README da extension, conferido com o DTO.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# 1) signup (público, 100/min por IP)
|
|
154
|
+
curl -X POST https://crm.example.com/public/tenants \
|
|
155
|
+
-H "Content-Type: application/json" \
|
|
156
|
+
-d '{
|
|
157
|
+
"subdomain": "clientea",
|
|
158
|
+
"cnpj": "<cnpj>",
|
|
159
|
+
"corporateName": "Cliente A Ltda",
|
|
160
|
+
"tradeName": "Cliente A",
|
|
161
|
+
"rootEmail": "admin@clientea.example",
|
|
162
|
+
"plainRootPassword": "<SEGREDO:senha-root>",
|
|
163
|
+
"address": { "street": "Rua X", "number": "100", "city": "São Paulo", "state": "SP", "postalCode": "01000-000", "district": "Centro" },
|
|
164
|
+
"legalRepresentative": { "firstName": "Maria", "lastName": "Silva", "cpf": "<cpf>", "birthDate": "1990-01-01", "phone": "11999990000" }
|
|
165
|
+
}'
|
|
166
|
+
# -> 201 {"status":"CREATED","emailDelivered":true,"result":"Email de confirmacao enviado para ..."}
|
|
167
|
+
|
|
168
|
+
# 2) validar OTP (aceita 123456 ou 123-456) -> cria banco crm_clientea, migra, cria root, conta de billing
|
|
169
|
+
curl -X POST https://crm.example.com/public/tenants/validate-email \
|
|
170
|
+
-H "Content-Type: application/json" -d '{"subdomain":"clientea","code":"123-456"}'
|
|
171
|
+
|
|
172
|
+
# 3) reenviar OTP (5/h por IP)
|
|
173
|
+
curl -X POST "https://crm.example.com/public/tenants/resend-email?subdomain=clientea"
|
|
174
|
+
|
|
175
|
+
# 4) a partir daqui, o tenant é resolvido pelo host
|
|
176
|
+
curl https://clientea.crm.example.com/api/persons -H "Authorization: Bearer <token>"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
> Atenção: com `domain-suffix=crm.example.com`, um subdomínio com `_` passa no signup mas não é extraído do host (o extractor aceita só `[a-z0-9-]`). Prefira nomes sem `_`.
|
|
180
|
+
|
|
181
|
+
## 9. Operação (contexto master)
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Forçar migração de um tenant
|
|
185
|
+
curl -X POST "https://crm.example.com/internal/migrations/tenants/clientea/trigger?force=true" -H "Authorization: Bearer <token-master>"
|
|
186
|
+
# Estado da migração
|
|
187
|
+
curl https://crm.example.com/internal/migrations/tenants/clientea/status -H "Authorization: Bearer <token-master>"
|
|
188
|
+
# Health do tenant (status HTTP derivado do código: OK, TENANT_INACTIVE, DATABASE_UNREACHABLE, MIGRATION_*...)
|
|
189
|
+
curl https://crm.example.com/internal/health/tenants/clientea -H "Authorization: Bearer <token-master>"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Chamados a partir de um host de tenant, esses endpoints respondem `403 {"error":"master_scope_required"}`. Durante uma migração disparada pelo login, `POST /auth/login` no tenant recebe `503` com `Retry-After: 15` e `{"error":"tenant_migration_in_progress", ...}`.
|
|
193
|
+
|
|
194
|
+
## 10. Lendo o tenant corrente no código do host
|
|
195
|
+
|
|
196
|
+
`TenantContext` é `@RequestScoped` e preenchido pelo `TenantResolutionFilter`:
|
|
197
|
+
|
|
198
|
+
```java
|
|
199
|
+
@Inject
|
|
200
|
+
TenantContext tenantContext;
|
|
201
|
+
|
|
202
|
+
String tenant = tenantContext.getCurrentTenant(); // "master" quando o host não traz subdomínio
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
O `QuarkusTenantResolver` dá prioridade ao valor já gravado no `TenantContext`; portanto `setCurrentTenant("outro")` dentro da request muda o banco usado pelas **próximas** sessões Hibernate da PU default (**Inferência** a partir de `QuarkusTenantResolver#resolveTenantId`; não há exemplo/teste do host fazendo isso). `TenantScopedExecutor#call(Supplier<T>)` apenas abre uma transação `REQUIRES_NEW`.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, subdomain-tenancy, rust]
|
|
3
|
+
fonte: rust/seedgrid-rust-crate-subdomain-tenancy
|
|
4
|
+
branch: main
|
|
5
|
+
commit: be738a2d1e
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# `seedgrid-rust-crate-subdomain-tenancy` — porte Rust
|
|
10
|
+
|
|
11
|
+
Lado Java: [[SeedGrid/Extensions/subdomain-tenancy/Visao-geral|Visão geral do subdomain-tenancy (Quarkus)]] · [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração Java]]
|
|
12
|
+
Configuração deste crate: [[SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust|Configuração Rust do subdomain-tenancy]]
|
|
13
|
+
|
|
14
|
+
## Objetivo
|
|
15
|
+
|
|
16
|
+
Porte da extensão `seedgrid-quarkus-ext-subdomain-tenancy` (antiga *multitenancy*): multitenancy **por subdomínio**, modelo **SILO** (um banco Postgres por tenant). Principais capacidades (Confirmado em `README.md`, `src/lib.rs`, `src/orchestrator.rs`):
|
|
17
|
+
|
|
18
|
+
- Resolução do tenant pelo host `{tenant}.{domain-suffix}` (prioriza `X-Forwarded-Host`; fallback `master`).
|
|
19
|
+
- Registro de tenants no banco **master** (`tenants`, `tenant_validation_tokens`, `tenant_migration_state`).
|
|
20
|
+
- Signup público de tenant com OTP por e-mail, validação e **provisionamento** (`CREATE DATABASE` + migrations security/tenant/audit/backup/apikeys + admin root).
|
|
21
|
+
- **Coordenação de migrations por tenant** (lock com lease/fencing token, heartbeat, concorrência limitada) e **gate de login** enquanto o tenant migra.
|
|
22
|
+
- Pools por tenant com LRU (`max-active-datasources`) e circuit breaker; middleware que devolve 404 para tenant inexistente/inativo.
|
|
23
|
+
- Schedulers de cleanup de tenants não validados e de trial.
|
|
24
|
+
- Orquestrador `assemble_all`: monta tenancy + security + billing + audit + backup, injeta os hooks de tenancy no security e devolve `Router` e OpenAPI agregados.
|
|
25
|
+
- PII at-rest do `Tenant` via `CryptoProvider` global do `seedgrid-privacy`; auditoria de criação de tenant.
|
|
26
|
+
|
|
27
|
+
## Coordenadas
|
|
28
|
+
|
|
29
|
+
| Item | Valor |
|
|
30
|
+
|---|---|
|
|
31
|
+
| Diretório / pacote / lib | `rust/seedgrid-rust-crate-subdomain-tenancy` / `seedgrid-rust-crate-subdomain-tenancy` / `seedgrid_subdomain_tenancy` |
|
|
32
|
+
| Versão | `version.workspace = true` → `2026.10.14` |
|
|
33
|
+
| Features | `default = ["postgres"]`, `postgres = ["dep:sqlx"]` |
|
|
34
|
+
| Reexporta | `seedgrid_apikeys`, `seedgrid_audit`, `seedgrid_backup`, `seedgrid_billing`, `seedgrid_cache`, `seedgrid_privacy`, `seedgrid_resilience`, `seedgrid_security` (usa também `seedgrid_runtime::events`) |
|
|
35
|
+
|
|
36
|
+
## API pública (principais)
|
|
37
|
+
|
|
38
|
+
| Símbolo | Papel |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `SubdomainTenancyConfig` (+ `from_env`, `apply_datasource_env`) | ver [[SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust\|Configuração Rust]] |
|
|
41
|
+
| `assemble_all(Arc<SubdomainTenancyConfig>, Option<PgPool>, PlatformOverrides)` → `AssembledPlatform { tenancy, security, billing, audit_scoped, backup_scoped, router, openapi }` | "CDI" do host |
|
|
42
|
+
| `TenancyPlatform::assemble(...)` → `AssembledTenancy` (`provisioning_router()`, `shutdown_schedulers()`), `HostOverrides` | montagem só da tenancy |
|
|
43
|
+
| `TenantService`, `TenantProvisioner`, `TenantProvisioningService`, `TenantRepository`, `TenantValidationTokenRepository` | signup/validação/provisionamento |
|
|
44
|
+
| `TenantMigrationCoordinator` (em `migration`), `PgTenantMigrationStore`, `DefaultTenantSchemaMigrator`, `TenantBusinessSchemaMigrator` | migração por tenant |
|
|
45
|
+
| `TenantPoolProvider`, `TenantConnectionResolver`, `TenantScoped`/`TenantStateFactory`, `TenantContext` | roteamento de pools/estado por tenant |
|
|
46
|
+
| `tenant_migration_gate` / `TenantMigrationGateState`, `tenant_middleware::*` | middlewares |
|
|
47
|
+
| `TenantJwtTokenCustomizer`, `TenantScopeResolver` (`spi`) | hooks para o security |
|
|
48
|
+
| `TenantHealthService`, `TenantHealthReport` | health por tenant |
|
|
49
|
+
| `extract_tenant_from_host`, `resolve_host_header`, `resolve_tenant` | resolução |
|
|
50
|
+
| `spawn_backup_scheduler`, `schedulers::*` | jobs |
|
|
51
|
+
| `migrate(&PgPool)` / `migrate_startup(...)` / `MIGRATOR` | migrations do master `0100..0109` |
|
|
52
|
+
| `router(SubdomainTenancyState)`, `provisioning_router(TenantProvisioningApiState)`, `openapi()` | rotas |
|
|
53
|
+
|
|
54
|
+
## Endpoints (Confirmado em `src/routes.rs` e `src/provisioning_routes.rs`)
|
|
55
|
+
|
|
56
|
+
| Método | Path |
|
|
57
|
+
|---|---|
|
|
58
|
+
| POST | `/public/tenants` (signup) |
|
|
59
|
+
| POST | `/public/tenants/validate-email` |
|
|
60
|
+
| POST | `/public/tenants/resend-email` |
|
|
61
|
+
| GET | `/internal/health/tenants/{tenantId}` |
|
|
62
|
+
| POST | `/internal/migrations/tenants/{tenantId}/trigger` |
|
|
63
|
+
| GET | `/internal/migrations/tenants/{tenantId}/status` |
|
|
64
|
+
| POST | `/internal/tenants/{tenantId}/provision` (`provisioning_router`) |
|
|
65
|
+
| GET | `/tenants`, `/tenants/{subdomain}` (`provisioning_router`; resumo do registro master) |
|
|
66
|
+
|
|
67
|
+
Rotas restritas ao contexto master respondem 403 `master_scope_required` quando chamadas sob outro tenant (`src/routes.rs`).
|
|
68
|
+
|
|
69
|
+
O `router` agregado do `assemble_all` inclui ainda security, billing, `/audit-logs` e `/exports` (backup), com middleware de resolução de tenant, gate de migração e auth admin via security.
|
|
70
|
+
|
|
71
|
+
## Exemplos
|
|
72
|
+
|
|
73
|
+
Montagem completa (Inferência a partir das assinaturas de `assemble_all` e `SubdomainTenancyConfig`; não há exemplo no repositório usando `assemble_all`):
|
|
74
|
+
|
|
75
|
+
```rust
|
|
76
|
+
use std::sync::Arc;
|
|
77
|
+
use seedgrid_subdomain_tenancy::{assemble_all, PlatformOverrides, SubdomainTenancyConfig};
|
|
78
|
+
|
|
79
|
+
async fn boot(master_url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
80
|
+
let mut cfg = SubdomainTenancyConfig::from_env();
|
|
81
|
+
cfg.apply_datasource_env(Some(master_url));
|
|
82
|
+
let master_pool = sqlx::PgPool::connect(master_url).await?;
|
|
83
|
+
let assembled = assemble_all(Arc::new(cfg), Some(master_pool), PlatformOverrides::default())
|
|
84
|
+
.await
|
|
85
|
+
.map_err(|e| e.to_string())?;
|
|
86
|
+
let app = axum::Router::new().nest("/api/v1", assembled.router);
|
|
87
|
+
let _ = app;
|
|
88
|
+
Ok(())
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Exemplo executável (Confirmado): `rust/examples/seedgrid-subdomain-tenancy-example/src/main.rs` — monta à mão `TenantPoolProvider`, `DefaultTenantSchemaMigrator`, `TenantMigrationCoordinator`, `TenantProvisioningService` e `TenantService` (trial 14 dias, OTP 10 min, `NoopPasswordEncoder` só para dev) e percorre signup → validação → provisionamento. Usa envs próprias do exemplo (`DATABASE_URL`, `TENANT_URL_PATTERN`, `TENANT_DB_USER`, `TENANT_DB_PASSWORD`, `EXAMPLE_TENANT`).
|
|
93
|
+
|
|
94
|
+
## Migrations (master)
|
|
95
|
+
|
|
96
|
+
`0100_create_tenants_table.sql` … `0109_privacy_search_hashes.sql` (10 arquivos): tenants, root user, trial, snapshot de usuário, `tenant_migration_state` (+ fencing token), índices, `tenant_validation_tokens` (+ base entity), hashes de privacidade. Os bancos de tenant recebem as migrations de security/audit/backup/apikeys + negócio via `DefaultTenantSchemaMigrator`.
|
|
97
|
+
|
|
98
|
+
## Testes
|
|
99
|
+
|
|
100
|
+
Unitários em `src/**` (config, resolver, gate, service...) e `tests/db_it.rs` (Testcontainers). **Não executados** nesta análise.
|
|
101
|
+
|
|
102
|
+
## Notas de comportamento
|
|
103
|
+
|
|
104
|
+
- O crate lê `SEEDGRID_MULTITENANCY_*` (o módulo Java usa `SEEDGRID_SUBDOMAIN_TENANCY_*`) — ver [[SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust|Configuração Rust]].
|
|
105
|
+
- Gate de login mais permissivo (`ends_with` e bypass do `default_tenant`).
|
|
106
|
+
- `validate_tenant` provisiona antes de marcar tokens usados.
|
|
107
|
+
- O admin do master é semeado pelo bootstrap do security (envs `SEEDGRID_SECURITY_FIRST_ADMIN_USER_*`), não por `subdomain-tenancy.admin-user.*`.
|
|
108
|
+
|
|
109
|
+
## Enums
|
|
110
|
+
|
|
111
|
+
Equivalentes Java em [[SeedGrid/Extensions/subdomain-tenancy/Enums|Enums (Quarkus)]].
|
|
112
|
+
|
|
113
|
+
| Enum | Arquivo | Usado em | Equivalente Java |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `TenantMigrationStatus` | `src/migration/mod.rs` | coluna `tenant_migration_state.status`; corpo de `/internal/migrations/...` | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantMigrationStatus\|TenantMigrationStatus]] |
|
|
116
|
+
| `TenantMigrationDecision` | `src/migration/mod.rs` | `TenantMigrationOutcome`; gate de login | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantMigrationDecision\|TenantMigrationDecision]] |
|
|
117
|
+
| `TenantHealthStatus` | `src/health.rs` | corpo de `GET /internal/health/tenants/{tenantId}` | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantHealthStatus\|TenantHealthStatus]] |
|
|
118
|
+
| `TenantHealthCode` | `src/health.rs` | idem | [[SeedGrid/Extensions/subdomain-tenancy/Enums#TenantHealthCode\|TenantHealthCode]] |
|
|
119
|
+
| `SignupOutcome` | `src/service.rs` | resposta de `POST /public/tenants` | [[SeedGrid/Extensions/subdomain-tenancy/Enums#SignupOutcome\|SignupOutcome]] |
|
|
120
|
+
| `StateEnum` | `src/models/embeddables.rs` | `state` do endereço do signup e do tenant | `StateEnum` da lib commons |
|
|
121
|
+
| `TenantAccess` | `src/pool_resolver.rs` | resultado do gate de tenant no middleware | sem equivalente (específico do Rust) |
|
|
122
|
+
| `TenantServiceError`, `ProvisioningError`, `AssemblyError` | `service.rs`, `provisioning.rs`, `platform.rs` | erros | exceções do Java |
|
|
123
|
+
|
|
124
|
+
### TenantMigrationStatus
|
|
125
|
+
|
|
126
|
+
`Pending` → `PENDING`, `Ready` → `READY`, `Migrating` → `MIGRATING`, `Failed` → `FAILED` (via `name()`). Coluna `status VARCHAR(20) NOT NULL DEFAULT 'PENDING'`, sem CHECK (`migrations/0104_create_tenant_migration_state.sql`). `parse(Option<&str>)` é tolerante: apara, ignora caixa e transforma qualquer valor desconhecido ou ausente em `Pending`. Java: mesmos 4 valores na mesma ordem.
|
|
127
|
+
|
|
128
|
+
### TenantMigrationDecision
|
|
129
|
+
|
|
130
|
+
`Ready` → `READY`, `InProgress` → `IN_PROGRESS`, `Failed` → `FAILED`. Não é persistido. No gate de login, `Ready` deixa passar; qualquer outra decisão responde **503** com `Retry-After: 15` e `error` = `tenant_migration_in_progress` ou `tenant_migration_failed`. Java: mesmos 3 valores.
|
|
131
|
+
|
|
132
|
+
### TenantHealthStatus e TenantHealthCode
|
|
133
|
+
|
|
134
|
+
- `TenantHealthStatus`: `Up` (`UP`), `Down` (`DOWN`), `OutOfService` (`OUT_OF_SERVICE`).
|
|
135
|
+
- `TenantHealthCode`: `Ok`, `InvalidTenant`, `TenantNotFound`, `TenantInactive`, `DatabaseUnreachable`, `MigrationPending`, `MigrationRunning`, `MigrationFailed`. O `name()` sai em SCREAMING_SNAKE (`OK`, `INVALID_TENANT`, ...).
|
|
136
|
+
- HTTP do health (`resolve_health_status`): `UP` → 200; `INVALID_TENANT` → 400; `TENANT_NOT_FOUND` → 404; os demais → 503.
|
|
137
|
+
- Java: mesmos valores e mesma ordem nos dois enums. Não são persistidos; o corpo JSON usa `name()`.
|
|
138
|
+
|
|
139
|
+
### SignupOutcome
|
|
140
|
+
|
|
141
|
+
`Created` → `CREATED` e `UpdatedPending` → `UPDATED_PENDING`, com resposta 201 e 200, respectivamente, em `POST /public/tenants`. Java: `CREATED`, `UPDATED_PENDING`, idêntico.
|
|
142
|
+
|
|
143
|
+
### StateEnum (subdomain-tenancy)
|
|
144
|
+
|
|
145
|
+
27 UFs com variantes em maiúsculas (`AC` ... `TO`), com `Serialize`/`Deserialize` por derive: serializa e desserializa **somente a sigla exata em maiúsculas**. **Atenção:** o `StateEnum` do login-tenancy Rust também aceita minúsculas e o nome completo do estado (como o `fromJson` do Java); este aqui não.
|
|
146
|
+
|
|
147
|
+
### TenantAccess
|
|
148
|
+
|
|
149
|
+
`Master` (escopo master, segue no pool master), `Active` (tenant existente e ativo, pool aquecido) e `Rejected` (inexistente ou inativo: o middleware responde 404, fail-closed). Sem equivalente Java.
|
|
150
|
+
|
|
151
|
+
### Erros
|
|
152
|
+
|
|
153
|
+
- `TenantServiceError`: `SubdomainAlreadyExists`, `CnpjAlreadyExists`, `TenantNotFound`, `TenantAlreadyActive`, `ValidationCodeExpired`, `Db`, `OtpGeneration`, `Provisioning`, `WeakPassword(Vec<String>)`.
|
|
154
|
+
- `ProvisioningError`: `Locked(String)` (lock de provisionamento já tomado) e `Db(String)`.
|
|
155
|
+
- `AssemblyError`: `Migration(MigrateError)`.
|
|
156
|
+
|
|
157
|
+
### Internos
|
|
158
|
+
|
|
159
|
+
| Enum | Arquivo |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `SchedulerStep` (`Cleanup`, `Queue`, `Notify`) | `src/backup_scheduler.rs` |
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, subdomain-tenancy]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-subdomain-tenancy
|
|
4
|
+
branch: main
|
|
5
|
+
commit: fe62530a29
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# subdomain-tenancy — Visão geral (Quarkus)
|
|
10
|
+
|
|
11
|
+
Configuração: [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração do subdomain-tenancy]] · Enums: [[SeedGrid/Extensions/subdomain-tenancy/Enums|Enums do subdomain-tenancy]] · Exemplos: [[SeedGrid/Extensions/subdomain-tenancy/Exemplos|Exemplos do subdomain-tenancy]] · [[SeedGrid/Extensions/subdomain-tenancy/Rust|Porte Rust]]
|
|
12
|
+
|
|
13
|
+
> [!note] Fontes
|
|
14
|
+
> Lido no código do commit `fe62530a29`. O `README.md` e os `docs/` da extension foram revisados contra o código em 2026-10-14. Os relatórios `AJUSTES_REALIZADOS.md`, `AUDITORIA_FINAL_CONFORMIDADE.md`, `CORRECOES_FINALIZADAS.md`, `IMPLEMENTATION_SUMMARY.md`, `RELATORIO_CONFORMIDADE.md`, `REVISAO_FINAL_CONFORMIDADE.md` e `SETUP_ENVIRONMENT.md` (raiz do repo) são históricos.
|
|
15
|
+
|
|
16
|
+
## Objetivo
|
|
17
|
+
|
|
18
|
+
Multi-tenancy **um banco PostgreSQL por tenant**, com o tenant identificado pelo **subdomínio** do host (`cliente1.crm.example.com` → tenant `cliente1`). Inclui:
|
|
19
|
+
|
|
20
|
+
- Resolução de tenant por request (`X-Forwarded-Host`/`Host`) e `TenantResolver`/`TenantConnectionResolver` do Hibernate ORM em modo `DATABASE`.
|
|
21
|
+
- Banco **master** (persistence unit `master`) com o catálogo `tenants`, security, billing, apikeys e afins.
|
|
22
|
+
- **Signup self-service** com validação por OTP de 6 dígitos por e-mail e **provisionamento** automático (CREATE DATABASE + migrations + usuário root).
|
|
23
|
+
- Cache de pools JDBC por tenant com limite, evicção por TTL/LRU e **circuit breaker** por tenant.
|
|
24
|
+
- **Orquestração de migrations por tenant** (sob demanda no login, manual, ou todas no boot) com lock distribuído e heartbeat.
|
|
25
|
+
- Health por tenant, OpenAPI filtrado por contexto (master × tenant), métricas Micrometer e spans OpenTelemetry.
|
|
26
|
+
- Bootstrap do admin global no master, limpeza de cadastros não validados, integração com billing (conta + trial), audit, backup e security.
|
|
27
|
+
|
|
28
|
+
## Módulos e coordenadas Maven
|
|
29
|
+
|
|
30
|
+
| Módulo | Coordenada | Observação |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| parent | `br.com.seedgrid:seedgrid-subdomain-tenancy-parent:2026.10.14-1` (pom) | Parent: `br.com.seedgrid:seedgrid-parent:2026.07.02` (Quarkus 3.30.8). Centraliza as versões da foundation em `<properties>` e `dependencyManagement` |
|
|
33
|
+
| runtime | `br.com.seedgrid:seedgrid-subdomain-tenancy:2026.10.14-1` | O que o host declara |
|
|
34
|
+
| deployment | `br.com.seedgrid:seedgrid-subdomain-tenancy-deployment:2026.10.14-1` | `SubdomainTenancyProcessor` |
|
|
35
|
+
|
|
36
|
+
**Confirmado** em `pom.xml`, `runtime/pom.xml`, `deployment/pom.xml`.
|
|
37
|
+
|
|
38
|
+
### Dependências SeedGrid (versões do parent)
|
|
39
|
+
|
|
40
|
+
| Artefato | Versão | Uso aqui |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `seedgrid-commons` | 2026.10.08 | `BaseEntity`, `commons.spi.TenantProvider`, `SchemaUtils`, `ApiExceptionExtension` |
|
|
43
|
+
| `seedgrid-security` | 2026.10.14 | Usuários/papéis, JWT, `PasswordService`, SPIs `JwtTokenCustomizer`, `JwtTokenValidator`, `SecurityScopeResolverProvider`, `SecurityLinkBuilder` |
|
|
44
|
+
| `seedgrid-privacy` | 2026.07.27 | Mascaramento/criptografia de PII do `Tenant` |
|
|
45
|
+
| `seedgrid-audit-core` | 2026.09.25 | `AuditMetadataContributor`, `AuditReadScopeProvider` (`seedgrid-audit-postgresql` só em teste) |
|
|
46
|
+
| `seedgrid-backup` | 2026.10.09 | `backup.spi.TenantProvider` |
|
|
47
|
+
| `seedgrid-billing-core` | 2026.10.14-1 | Conta/trial por tenant, SPIs `CurrentAccountResolver`, `AccountMembersGateway` (`seedgrid-billing-stripe` só em `dependencyManagement`) |
|
|
48
|
+
| `seedgrid-apikeys` | 2026.10.14 | Schema de API keys no master e nos tenants |
|
|
49
|
+
| `seedgrid-notification` (+ `-deployment`) | 2026.10.15 | Catálogo de eventos, regras, outbox e envio por e-mail/WhatsApp; vem transitivamente, o host não precisa declará-la — ver [[SeedGrid/Extensions/notification/Visao-geral\|notification]] |
|
|
50
|
+
| `seedgrid-resilience-core` | 2026.10.13 | `@SeedGridRateLimit` nos endpoints públicos |
|
|
51
|
+
|
|
52
|
+
Quarkus no runtime: `arc`, `hibernate-orm(-panache)`, `jdbc-postgresql`, `agroal`, `flyway`, `rest-jackson`, `hibernate-validator`, `smallrye-openapi`, `opentelemetry`, `micrometer(-registry-prometheus)`, `smallrye-fault-tolerance`, `mailer`, `qute`, `quarkus-mongodb-client` (**optional**), além de `jbcrypt`. O deployment acrescenta `quarkus-scheduler-deployment`, `quarkus-virtual-threads-deployment` e `quarkus-grpc-common-deployment`.
|
|
53
|
+
|
|
54
|
+
Extensions relacionadas: [[SeedGrid/Extensions/security/Visao-geral|security]], [[SeedGrid/Extensions/billing/Visao-geral|billing]], [[SeedGrid/Extensions/audit/Visao-geral|audit]], [[SeedGrid/Extensions/backup/Visao-geral|backup]], [[SeedGrid/Extensions/apikeys/Visao-geral|apikeys]], [[SeedGrid/Extensions/resilience/Visao-geral|resilience]], [[SeedGrid/Extensions/privacy/Visao-geral|privacy]], [[SeedGrid/Extensions/commons/Visao-geral|commons]]. Alternativa com banco compartilhado/login: [[SeedGrid/Extensions/login-tenancy/Visao-geral|login-tenancy]].
|
|
55
|
+
|
|
56
|
+
## Arquitetura e fluxo
|
|
57
|
+
|
|
58
|
+
```mermaid
|
|
59
|
+
flowchart LR
|
|
60
|
+
R[Request] --> F1[TenantResolutionFilter<br/>prio AUTHENTICATION-1]
|
|
61
|
+
F1 -->|host -> tenant| C[(TenantContext<br/>RequestScoped)]
|
|
62
|
+
F1 --> F2[TenantMigrationGateFilter<br/>prio AUTHENTICATION]
|
|
63
|
+
F2 -->|login-paths| MC[TenantMigrationCoordinator]
|
|
64
|
+
C --> QR[QuarkusTenantResolver]
|
|
65
|
+
QR --> H[Hibernate PU default<br/>multitenant=DATABASE]
|
|
66
|
+
H --> TCR[TenantConnectionResolverImpl]
|
|
67
|
+
TCR -->|master| MDS[(DataSource master)]
|
|
68
|
+
TCR -->|tenant| POOL[Pool por tenant<br/>cache + LRU + circuit breaker]
|
|
69
|
+
POOL --> TDB[(banco prefix_tenant)]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **Resolução** (Confirmado): `TenantResolutionFilter` lê `X-Forwarded-Host` (prioritário) ou `Host`, extrai o tenant com `TenantHostExtractor` e grava no `TenantContext` e no MDC (`tenantId`); sem tenant → `master`. O Javadoc do filtro deixa explícito que o default para `master` **não** é fronteira de segurança: o isolamento vem do JWT/API key serem resolvidos no banco do próprio tenant.
|
|
73
|
+
- **JWT**: `SubdomainTenancyJwtTokenCustomizer` adiciona as claims `subdomain` e `audience` (`platform`/`client`); `SubdomainTenancyJwtTokenValidator` rejeita token cujo `subdomain` difere do host.
|
|
74
|
+
- **Conexões**: `TenantConnectionResolverImpl` verifica no master se o tenant existe e está ativo (`TenantNotFoundException` caso contrário), cria `SimpleDriverDataSource` por tenant, aplica limite/evicção e circuit breaker. Detalhes dos parâmetros em [[SeedGrid/Extensions/subdomain-tenancy/Configuracao|Configuração]].
|
|
75
|
+
- **Startup** (`MigrationService#onStart`, `@Priority(APPLICATION)`): no master roda, em ordem, security → audit (se `jpa`) → backup → billing → apikeys → ai-pricing/exchange (se habilitados) → subdomain-tenancy → `db/migration/master` (+ additional), depois `AdminUserBootstrapService#seedAdminUserIfNeeded`. Tenants não migram no boot por padrão.
|
|
76
|
+
- **Signup → provisionamento**: `POST /public/tenants` grava o tenant pendente e envia OTP; `POST /public/tenants/validate-email` valida o código e chama `TenantProvisioningService#createTenantWithAdmin` (lock local + `pg_try_advisory_lock`, `CREATE DATABASE` idempotente, migrations security → tenant → audit → backup → apikeys, usuário root; rollback com `DROP DATABASE` se o banco foi criado no fluxo), depois `SubdomainBillingAccounts#ensureFor` (conta + trial no billing) e ativa o tenant.
|
|
77
|
+
- **Migração por tenant**: `TenantMigrationCoordinator` com estado em `tenant_migration_state` (`PENDING`/`MIGRATING`/`READY`/`FAILED`), lock com fencing token e heartbeat. Gatilhos: login (gate devolve `503` + `Retry-After: 15`), endpoint manual, ou boot.
|
|
78
|
+
- **Limpeza**: `TenantCleanupScheduler` remove do master os cadastros não validados mais velhos que o TTL (não remove o banco — ver Limitações).
|
|
79
|
+
|
|
80
|
+
## APIs públicas e pontos de integração
|
|
81
|
+
|
|
82
|
+
### Endpoints REST
|
|
83
|
+
|
|
84
|
+
| Método e path | Classe | Acesso | Observação |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `POST /public/tenants` | `TenantSignupController#signupTenant` | público, rate limit `tenant-signup` (100/min) | `201` CREATED ou `200` UPDATED_PENDING; corpo traz `emailDelivered` |
|
|
87
|
+
| `POST /public/tenants/validate-email` | `#validateEmail` | público, `tenant-validate-email` (10/min) | Body `{subdomain, code}`; dispara o provisionamento |
|
|
88
|
+
| `POST /public/tenants/resend-email?subdomain=` | `#resendEmail` | público, `tenant-resend-email` (5/h) | `400` sem `subdomain` |
|
|
89
|
+
| `GET /public/tenant-openapi` | `TenantOpenApiController#getFilteredOpenApi` | `@PermitAll` + permissão HTTP `permit` no `MPC` | Remove tag `tenant-only` no master; `master-only` + `master-only-paths` nos tenants |
|
|
90
|
+
| `POST /internal/migrations/tenants/{tenantId}/trigger?force=` | `TenantMigrationController#triggerMigration` | só contexto master (`403 master_scope_required`) | Não há `@RolesAllowed` na classe; a autenticação depende da política global do security (**Não confirmado**) |
|
|
91
|
+
| `GET /internal/migrations/tenants/{tenantId}/status` | `#migrationStatus` | idem | — |
|
|
92
|
+
| `GET /internal/health/tenants/{tenantId}` | `TenantHealthController#tenantHealth` | idem | Códigos `OK`, `INVALID_TENANT`, `TENANT_NOT_FOUND`, `TENANT_INACTIVE`, `DATABASE_UNREACHABLE`, `MIGRATION_PENDING/RUNNING/FAILED` |
|
|
93
|
+
|
|
94
|
+
Paths centralizados em `RestControllerPath`. Exceções mapeadas por `SubdomainTenancyExceptionExtension` (`ApiExceptionExtension` do commons): `TenantNotFoundException` → 404, `SubdomainAlreadyExistsException`/`CnpjAlreadyExistsException` → 409, `TenantAlreadyActiveException`/`ValidationCodeExpiredException` → 400.
|
|
95
|
+
|
|
96
|
+
### Anotações
|
|
97
|
+
- `@MasterOnly` / `@TenantOnly` (`annotations/`): meta-anotações com `@Tag("master-only")`/`@Tag("tenant-only")` — **só documentação/OpenAPI**, não controlam acesso.
|
|
98
|
+
|
|
99
|
+
### SPIs para o host
|
|
100
|
+
|
|
101
|
+
| Interface | Default fornecido | Uso |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `spi.SubdomainTenancyNotificationService#sendValidationNotification(TenantValidationNotification)` | `MailerNotificationService` (`@DefaultBean`, mailer `subdomaintenancy`) | Entrega do OTP |
|
|
104
|
+
| `spi.TemplateSubdomainTenancyProvider#buildValidationEmail(...)` | `DefaultTemplateSubdomainTenancyProvider` (`@DefaultBean`, Qute: procura `templates/subdomain-tenancy/confirmation.html` ou `subdomain-tenancy/html/confirmation.html` do host e cai em `subdomaintenancy-default/html/confirmation.html` da extension) | HTML do OTP quando não há `EmailTemplateProvider` |
|
|
105
|
+
| `spi.EmailTemplateProvider#buildConfirmationEmailHtml(...)`, `#getConfirmationEmailSubject(...)` | nenhum | Opcional; se resolvível, define assunto/HTML |
|
|
106
|
+
| `spi.PasswordEncoder#encode(String)` | `BCryptPasswordEncoder` (`@DefaultBean`) | Injetado como `Instance<PasswordEncoder>` em `TenantService`/`TenantProvisioningService` |
|
|
107
|
+
|
|
108
|
+
Com os defaults `@DefaultBean`, o envio do OTP funciona só com o SMTP configurado (**Confirmado**).
|
|
109
|
+
|
|
110
|
+
### Beans CDI úteis ao host
|
|
111
|
+
- `TenantContext` (`@RequestScoped`): `getCurrentTenant()`, `setCurrentTenant(String)`, `clear()`.
|
|
112
|
+
- `TenantScopedExecutor#call(Supplier<T>)`: executa em nova transação (`REQUIRES_NEW`).
|
|
113
|
+
- `TenantHostExtractor#extractTenantFromHost(String)`.
|
|
114
|
+
- `TenantAwareMongoProducer` (`@Produces @DefaultBean MongoDatabase`, banco `<prefix>_<tenant>_audit_db`) e `MongoTenantResolverProducer` → `MongoTenantResolver` quando há `MongoClient`.
|
|
115
|
+
- Implementações de SPIs de outras extensions: `MultiTenantProvider` (commons), `MultiTenantBackupProvider` (backup), `TenantAuditScopeProvider` (audit), `SubdomainCurrentAccountResolver`/`SubdomainAccountMembersGateway` (billing), `SubdomainTenancySecurityScopeResolver`, `TenantSecurityLinks` (`@Alternative @Priority(1)`, reescreve links de e-mail do security para `<tenant>.<host>`).
|
|
116
|
+
|
|
117
|
+
### Métricas (Micrometer)
|
|
118
|
+
`seedgrid.subdomain-tenancy.tenant.datasource.provisioning.failures`, `…connection.failures`, `…connection.circuit.open`, `…active.pools`, `…active.connections.total`, `…connections.active{tenant}`, `…evictions{reason=ttl|lru}`, `seedgrid.subdomain-tenancy.migration.failures`.
|
|
119
|
+
|
|
120
|
+
## Migrations
|
|
121
|
+
|
|
122
|
+
| Location | Versões | History | Onde roda |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| `db/migration/subdomain-tenancy` | `V100`–`V110` (tenants, root user, campos de trial legados, snapshots, `tenant_migration_state`, fencing token, índices, `tenant_validation_tokens`, hashes de busca de privacy, conta de billing) | `flyway_schema_history_subdomain_tenancy` | master |
|
|
125
|
+
| `db/migration/tenancy` | `V706__timestamps_com_fuso.sql` | — | **Não aplicada** pela extension (nenhuma location aponta para ela). `java/MIGRATION_RANGES.md` documenta a faixa 100–199 e trata a V706 como exceção legada |
|
|
126
|
+
| `db/migration/master` / `db/migration/tenant` (do host) | livres | `flyway_schema_history_master` / `_tenant` | master no boot / tenant no provisionamento e migração |
|
|
127
|
+
|
|
128
|
+
## Testes
|
|
129
|
+
|
|
130
|
+
`runtime/src/test/java/br/com/seedgrid/subdomaintenancy/` — 17 classes, ~76 métodos `@Test`. **Não executados** nesta análise.
|
|
131
|
+
|
|
132
|
+
- Unitários (Mockito): `TenantHostExtractorTest` (10), `TenantsRepositoryTest` (7), `TenantHealthServiceTest`, `TenantProvisioningServiceTest`, `TenantServiceTest`, `TenantMigrationCoordinatorTest`, `TenantMigrationStateStoreTest`, `TenantResolutionFilterTest`, `TenantHealthControllerTest`, `TenantOpenApiControllerFilterTest`, `TenantAuditScopeProviderTest`, `TenantSecurityLinksTest`, `TenantToStringMaskingTest`.
|
|
133
|
+
- `@QuarkusTest` com Postgres via Testcontainers (`SubdomainTenancyPostgresTestResource`): `TenantRegisterResourceIntegrationTest` (9), `TenantsRepositoryIntegrationTest` (6), `TenantBillingIntegrationTest`, `TenantEncryptedFieldsTest` (perfil com criptografia de privacy).
|
|
134
|
+
- Há também `perf/jmeter/login-concurrency.jmx` (carga de login concorrente).
|
|
135
|
+
|
|
136
|
+
## Limitações
|
|
137
|
+
|
|
138
|
+
- **Somente PostgreSQL** (`CREATE DATABASE`, `pg_try_advisory_lock`, `pg_terminate_backend`); o usuário de tenant precisa de `CREATE DATABASE`.
|
|
139
|
+
- **Subdomínios com `_`**: o signup aceita `^[a-z0-9_]{5,50}$`, mas o extractor só aceita `^[a-z0-9-]+$` → esses tenants caem em `master` pelo host (**Confirmado**; ver Dúvidas na Configuração).
|
|
140
|
+
- Teto de `max-active-datasources` sem pool ocioso → `TenantProvisioningException` na resolução.
|
|
141
|
+
- `TenantCleanupScheduler` remove só o registro em `tenants`, não o banco físico (contradiz o Javadoc). Se houver banco criado para cadastro não validado, ele fica órfão (o provisionamento só ocorre após validação, então na prática isso só acontece em cenários de falha — **Inferência**).
|
|
142
|
+
- Vários literais `"master"` fixos: `default-tenant` diferente de `master` não é consistente.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, whatsapp, rust, configuracao]
|
|
3
|
+
fonte: rust/seedgrid-rust-crate-whatsapp
|
|
4
|
+
branch: main
|
|
5
|
+
commit: be738a2d1e
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# whatsapp — Configuração Rust
|
|
10
|
+
|
|
11
|
+
Visão do crate: [[SeedGrid/Extensions/whatsapp/Rust|Porte Rust do whatsapp]] · Java: [[SeedGrid/Extensions/whatsapp/Configuracao|Configuração do whatsapp (Java)]].
|
|
12
|
+
|
|
13
|
+
## Cabeçalho
|
|
14
|
+
| Item | Valor |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Struct | `WhatsAppConfig` — `rust/seedgrid-rust-crate-whatsapp/src/config.rs` |
|
|
17
|
+
| Mecanismo | `WhatsAppConfig::from_env()` (`std::env::var`) ou struct do host |
|
|
18
|
+
| Prefixo Java | `seedgrid.whatsapp` |
|
|
19
|
+
| Quem chama `from_env` | security (`src/state.rs`, para o canal 2FA WhatsApp) |
|
|
20
|
+
| Leitura | quando `from_env()` é chamado (boot); sem recarga |
|
|
21
|
+
|
|
22
|
+
Os nomes de env são exatamente os das expressões `${...}` de `java/seedgrid-quarkus-ext-whatsapp/seedgrid-whatsapp-core/src/main/resources/META-INF/microprofile-config.properties` (**Confirmado**), com os mesmos padrões.
|
|
23
|
+
|
|
24
|
+
## Variáveis de ambiente
|
|
25
|
+
| Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Leitura | Origem |
|
|
26
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
27
|
+
| `SEEDGRID_WHATSAPP_API` (Java `seedgrid.whatsapp.api`) | String | `default`, `evolution`, `whatsapp-cloud` (case-insensitive, com `trim`); vazio/desconhecido → `default` | `default` | Não | escolhe o provider em `create_whatsapp_api` | `factory.rs`; `enums.rs` → `WhatsAppProvider::from_config` | define quais variáveis abaixo importam | `from_env()` | `config.rs` → `from_env` |
|
|
28
|
+
| `SEEDGRID_WHATSAPP_REST_CLIENT_EVOLUTION_URL` (Java `seedgrid.whatsapp.rest-client.evolution.url`) | `Option<String>` | URL base da Evolution API | — (vira `""` no transporte) | Condicional: `api=evolution` | base das requisições | `EvolutionWhatsAppApi::new` | `EVOLUTION_API_KEY` | `from_env()` | `evolution.rs` |
|
|
29
|
+
| 🔒 `SEEDGRID_WHATSAPP_EVOLUTION_API_KEY` (Java `seedgrid.whatsapp.evolution.api-key`) | `Option<String>` | texto | — | Condicional: `api=evolution` | ausente/branco → `is_configured() = false` e `IllegalState` na chamada | `EvolutionWhatsAppApi::require_api_key` | — | `from_env()` | `evolution.rs` |
|
|
30
|
+
| `SEEDGRID_WHATSAPP_REST_CLIENT_CLOUD_URL` (Java `seedgrid.whatsapp.rest-client.cloud.url`) | String | URL | `https://graph.facebook.com/v21.0` (`DEFAULT_CLOUD_URL`; vazio → padrão) | Não | base da Graph API | `OfficialCloudWhatsAppApi::new` | — | `from_env()` | `config.rs`, `cloud.rs` |
|
|
31
|
+
| 🔒 `SEEDGRID_WHATSAPP_CLOUD_ACCESS_TOKEN` (Java `seedgrid.whatsapp.cloud.access-token`) | `Option<String>` | token Meta | — | Condicional: `api=whatsapp-cloud` | `Authorization: Bearer`; ausente → `is_configured() = false` e `IllegalState` na chamada | `cloud.rs` | — | `from_env()` | `cloud.rs` |
|
|
32
|
+
|
|
33
|
+
Valores em branco contam como ausentes (`opt_env`), exceto `SEEDGRID_WHATSAPP_API`, que é lido cru (o branco cai em `default` pela fábrica).
|
|
34
|
+
|
|
35
|
+
## Struct `WhatsAppConfig`
|
|
36
|
+
| Campo | `Default` |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `api` | `"default"` |
|
|
39
|
+
| `evolution_api_key` | `None` |
|
|
40
|
+
| `cloud_access_token` | `None` |
|
|
41
|
+
| `evolution_url` | `None` |
|
|
42
|
+
| `cloud_url` | `DEFAULT_CLOUD_URL` |
|
|
43
|
+
|
|
44
|
+
## Features Cargo
|
|
45
|
+
Nenhuma.
|
|
46
|
+
|
|
47
|
+
## Relação com a security
|
|
48
|
+
O canal 2FA por WhatsApp na security usa também `SEEDGRID_SECURITY_2F_WHATSAPP_CHANNEL_ID` (citado em `rust/seedgrid-rust-crate-security/src/state.rs`); essa variável pertence à configuração da security, fora desta nota.
|
|
49
|
+
|
|
50
|
+
## Somente testes
|
|
51
|
+
Nenhuma variável de ambiente lida em `tests/`.
|
|
52
|
+
|
|
53
|
+
## Configuração mínima
|
|
54
|
+
```dotenv
|
|
55
|
+
# Evolution
|
|
56
|
+
SEEDGRID_WHATSAPP_API=evolution
|
|
57
|
+
SEEDGRID_WHATSAPP_REST_CLIENT_EVOLUTION_URL=https://<evolution-host>
|
|
58
|
+
SEEDGRID_WHATSAPP_EVOLUTION_API_KEY=<SEGREDO:evolution-api-key>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Configuração completa comentada
|
|
62
|
+
```dotenv
|
|
63
|
+
SEEDGRID_WHATSAPP_API=whatsapp-cloud # default | evolution | whatsapp-cloud
|
|
64
|
+
# Evolution
|
|
65
|
+
SEEDGRID_WHATSAPP_REST_CLIENT_EVOLUTION_URL=https://<evolution-host>
|
|
66
|
+
SEEDGRID_WHATSAPP_EVOLUTION_API_KEY=<SEGREDO:evolution-api-key>
|
|
67
|
+
# Cloud oficial
|
|
68
|
+
SEEDGRID_WHATSAPP_REST_CLIENT_CLOUD_URL=https://graph.facebook.com/v21.0
|
|
69
|
+
SEEDGRID_WHATSAPP_CLOUD_ACCESS_TOKEN=<SEGREDO:meta-access-token>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Receitas
|
|
73
|
+
- **Desligado/dev**: não definir nada → `default` (no-op que loga; `is_configured()` falso).
|
|
74
|
+
- **Evolution**: três variáveis do bloco Evolution.
|
|
75
|
+
- **Cloud oficial**: `SEEDGRID_WHATSAPP_API=whatsapp-cloud` + access token.
|