@horizon-framework/website-dev-docs 2.3.0 → 2.3.1
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/index.md +1 -0
- package/knowledge/SYNC_API_PATTERN.md +268 -0
- package/package.json +1 -1
package/index.md
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- [API_PROPERTY.md](./knowledge/API_PROPERTY.md) — Stack, arquitetura, endpoints da API
|
|
20
20
|
- [SEARCH_ENGINE_API.md](./knowledge/SEARCH_ENGINE_API.md) — Motor de busca, facets, pagination, cursor vs offset
|
|
21
21
|
- [BATCH_TOTALS_API.md](./knowledge/BATCH_TOTALS_API.md) — API batch totals, contagens dinamicas
|
|
22
|
+
- [SYNC_API_PATTERN.md](./knowledge/SYNC_API_PATTERN.md) — Padrao /sync (GET/PUT/DELETE), pipeline de transformacao, multi-source (source_key)
|
|
22
23
|
- [SSOT_SPECIFICATION_V2.md](./knowledge/SSOT_SPECIFICATION_V2.md) — Spec SSOT v2.0, metadados hierarquicos
|
|
23
24
|
- [MODULE_CREATION_PATTERN.md](./knowledge/MODULE_CREATION_PATTERN.md) — Padrao de criacao de modulos backend + frontend
|
|
24
25
|
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Sync API — Padrao de Sincronizacao de Modulos
|
|
2
|
+
|
|
3
|
+
> Endpoint /sync que TODO modulo backend Horizon implementa.
|
|
4
|
+
> Ponto de entrada de dados externos (CRM, integradores) para o banco.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visao Geral
|
|
9
|
+
|
|
10
|
+
Cada modulo tem 3 operacoes no /sync:
|
|
11
|
+
|
|
12
|
+
| Metodo | Rota | Autenticacao | Funcao |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| GET | /api/{entity}/sync | Publico (CORS *) | Listing — retorna IDs + refs + updated_at para diff |
|
|
15
|
+
| PUT | /api/{entity}/sync | API Key (x-api-key) | Upsert batch — recebe array, valida, transforma, persiste |
|
|
16
|
+
| DELETE | /api/{entity}/sync | API Key (x-api-key) | Delete por refs/keys — remove items do banco |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Arquitetura de Arquivos
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
apps/api/src/
|
|
24
|
+
├── app/api/{entity}/sync/
|
|
25
|
+
│ └── route.ts # "Ponte seca" — exporta controller
|
|
26
|
+
├── core/
|
|
27
|
+
│ ├── prisma.ts # Singleton Prisma Client
|
|
28
|
+
│ └── route-helpers.ts # withApiKeyProtection() + withPublicCors()
|
|
29
|
+
└── modules/{entity}/
|
|
30
|
+
├── schemas/
|
|
31
|
+
│ └── source-schema.zod.ts # Importa Zod do pacote CRM
|
|
32
|
+
├── controllers/
|
|
33
|
+
│ └── sync.controller.ts # GET/PUT/DELETE — valida body, chama service
|
|
34
|
+
├── services/
|
|
35
|
+
│ └── sync.service.ts # Logica: prepare → transaction → return
|
|
36
|
+
└── mappers/
|
|
37
|
+
├── dataModifier/
|
|
38
|
+
│ ├── index.ts # ObjectTransformer com regras do cliente
|
|
39
|
+
│ └── rules.ts # Regras especificas CRM/cliente (pode estar vazio)
|
|
40
|
+
└── computedFields/
|
|
41
|
+
├── index.ts # Orquestra todos os computed
|
|
42
|
+
├── seo-slug-gen.ts # Gera slug SEO
|
|
43
|
+
├── thumbnails.ts # Gera thumbnails das imagens
|
|
44
|
+
├── endereco-completo.ts# Monta endereco formatado
|
|
45
|
+
├── tipo-subtipo-gen.ts # Mapeia tipo → tipo_gen + subtipo_gen
|
|
46
|
+
└── valor-m2.ts # Calcula valor por m2
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Fluxo PUT (Upsert)
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
1. REQUEST → PUT /api/{entity}/sync (x-api-key header)
|
|
55
|
+
Body: { "{entity}s": [ {...item1}, {...item2} ] }
|
|
56
|
+
|
|
57
|
+
2. ROUTE → withApiKeyProtection() verifica x-api-key
|
|
58
|
+
Se invalido: 401 Unauthorized
|
|
59
|
+
|
|
60
|
+
3. CONTROLLER
|
|
61
|
+
→ Parse body JSON
|
|
62
|
+
→ Validacao Zod RIGOROSA do array
|
|
63
|
+
Property: usa Zod do pacote CRM (ex: HorizonPropertySchemaBySi9Zod)
|
|
64
|
+
Broker: schema inline com .passthrough()
|
|
65
|
+
Se falha: 400 com detalhes Zod
|
|
66
|
+
|
|
67
|
+
4. SERVICE
|
|
68
|
+
→ validateUpsertInput() — array nao vazio, cada item tem key/reference
|
|
69
|
+
→ Loop de preparacao (cada item):
|
|
70
|
+
- dataModifier(item) → Regras de negocio do cliente
|
|
71
|
+
- computeFields(item) → Campos calculados (slug, thumb, endereco, flags)
|
|
72
|
+
- stripUnknownFields(item) → Remove campos que nao existem no model Prisma
|
|
73
|
+
- Salva sync_raw_data → JSONB com snapshot completo
|
|
74
|
+
→ Prisma.$transaction():
|
|
75
|
+
- BACKUP first_synced_at dos existentes
|
|
76
|
+
- deleteMany({ where: { reference: { in: refs } } })
|
|
77
|
+
- createMany({ data: prepared, skipDuplicates: true })
|
|
78
|
+
- Restaura first_synced_at
|
|
79
|
+
|
|
80
|
+
5. RESPONSE
|
|
81
|
+
{ success: true, inserted: N, updated: N, total: N, references: [...] }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Por que delete + insert (e nao upsert nativo)?**
|
|
85
|
+
Prisma upsert opera 1 registro por vez. Para batches de 50+ itens, deleteMany + createMany numa transaction e mais performatico. Garante que campos removidos do CRM sao limpos.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Fluxo GET (Listing)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
GET /api/{entity}/sync?source_key=si9-api-imoveis
|
|
93
|
+
|
|
94
|
+
→ findMany com select minimo: { id, reference, source_updated_at }
|
|
95
|
+
→ Filtro opcional por source_key
|
|
96
|
+
→ Ordenado por source_updated_at DESC
|
|
97
|
+
→ Mapeia source_updated_at → updated_at na saida
|
|
98
|
+
|
|
99
|
+
Response: { properties: [{ id, reference, updated_at }, ...] }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Proposito:** Integrador faz GET listing → compara updated_at com dados do CRM → envia PUT apenas com os que mudaram. Evita re-sincronizar tudo.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Fluxo DELETE
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
DELETE /api/{entity}/sync (x-api-key header)
|
|
110
|
+
Body: { "refs": ["ABC-123", "DEF-456"] }
|
|
111
|
+
(property usa "refs", broker usa "keys")
|
|
112
|
+
|
|
113
|
+
→ Validacao: array nao vazio
|
|
114
|
+
→ prisma.{entity}.deleteMany({ where: { reference: { in: refs } } })
|
|
115
|
+
|
|
116
|
+
Response: { success: true, deleted: N, references: [...] }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Pipeline de Transformacao
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
Dados do CRM (formato CRM)
|
|
125
|
+
↓
|
|
126
|
+
Pacote CRM (@horizon-integrations/{crm}-crm) ← FORA da API, no automations
|
|
127
|
+
Converte formato CRM → formato Horizon
|
|
128
|
+
↓
|
|
129
|
+
PUT /api/{entity}/sync ← ENTRADA NA API
|
|
130
|
+
↓
|
|
131
|
+
source-schema.zod.ts ← Validacao Zod rigorosa
|
|
132
|
+
↓
|
|
133
|
+
dataModifier (mappers/dataModifier/) ← Regras de negocio do CLIENTE
|
|
134
|
+
ObjectTransformer com rules.ts
|
|
135
|
+
Pode estar VAZIO (passthrough)
|
|
136
|
+
↓
|
|
137
|
+
computedFields (mappers/computedFields/) ← Campos CALCULADOS
|
|
138
|
+
seo_slug_gen, thumbnails, endereco_completo,
|
|
139
|
+
tipo_gen, subtipo_gen, has_video, has_tour,
|
|
140
|
+
financiavel, first_synced_at
|
|
141
|
+
↓
|
|
142
|
+
stripUnknownFields() ← Protecao do Prisma
|
|
143
|
+
Filtra via Prisma.{Entity}ScalarFieldEnum
|
|
144
|
+
↓
|
|
145
|
+
sync_raw_data (JSONB) ← Snapshot completo
|
|
146
|
+
↓
|
|
147
|
+
Prisma $transaction (delete + insert) ← Persistencia
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Protecao de Rotas
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// route.ts — padrao para todo modulo
|
|
156
|
+
export const { OPTIONS, GET, PUT, DELETE } = withApiKeyProtection({
|
|
157
|
+
GET: ControllerGET, // Publico (CORS *)
|
|
158
|
+
PUT: ControllerPUT, // Protegido (x-api-key)
|
|
159
|
+
DELETE: ControllerDELETE // Protegido (x-api-key)
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Diferencas Property vs Broker
|
|
166
|
+
|
|
167
|
+
| Aspecto | Property | Broker |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| Chave unica | reference | key |
|
|
170
|
+
| Validacao Zod | Rigorosa (schema CRM) | Flexivel (.passthrough()) |
|
|
171
|
+
| dataModifier | ObjectTransformer com rules | Funcao simples |
|
|
172
|
+
| computedFields | 7+ campos | Poucos (slug, timestamps) |
|
|
173
|
+
| stripUnknownFields | Sim | Nao |
|
|
174
|
+
| sync_raw_data | Sim (JSONB) | Nao |
|
|
175
|
+
| first_synced_at | Sim (preservado) | Nao |
|
|
176
|
+
| GET source_key | Sim | Nao (ainda) |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Multi-Source Sync — Padrao source_key
|
|
181
|
+
|
|
182
|
+
### O Problema
|
|
183
|
+
|
|
184
|
+
Uma integracao pode ter MULTIPLAS fontes de dados para a mesma entidade:
|
|
185
|
+
|
|
186
|
+
| source_key | Fonte | Descricao |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| si9-api-imoveis | SI9 CRM | Imoveis proprios da imobiliaria |
|
|
189
|
+
| nifb-vrsync-imoveis | VRSync/NIFB | Imoveis do Nucleo de Imoveis |
|
|
190
|
+
|
|
191
|
+
Sem source_key, ao sincronizar imoveis do SI9, o deleteMany poderia deletar imoveis do NIFB.
|
|
192
|
+
|
|
193
|
+
### Regra Obrigatoria
|
|
194
|
+
|
|
195
|
+
**Toda integracao que enviar dados via PUT /sync DEVE incluir source_key em cada item.**
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"properties": [
|
|
200
|
+
{
|
|
201
|
+
"source_key": "si9-api-imoveis",
|
|
202
|
+
"reference": "ABC-123",
|
|
203
|
+
"title": "Casa 3 quartos"
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Onde o source_key atua
|
|
210
|
+
|
|
211
|
+
**PUT (Upsert):** Cada item chega com source_key preenchido, salvo no banco.
|
|
212
|
+
|
|
213
|
+
**GET (Listing):** Integrador filtra apenas seus registros:
|
|
214
|
+
```
|
|
215
|
+
GET /api/property/sync?source_key=si9-api-imoveis → So imoveis do SI9
|
|
216
|
+
GET /api/property/sync?source_key=nifb-vrsync-imoveis → So imoveis do NIFB
|
|
217
|
+
GET /api/property/sync → Sem filtro: retorna TODOS
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**DELETE — Scoping por fonte (EVOLUCAO NECESSARIA):**
|
|
221
|
+
Estado atual: DELETE opera por references SEM filtrar por source_key.
|
|
222
|
+
Evolucao: DELETE deveria aceitar source_key como filtro adicional:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Evolucao: DELETE com scoping por source
|
|
226
|
+
await tx.property.deleteMany({
|
|
227
|
+
where: {
|
|
228
|
+
reference: { in: refs },
|
|
229
|
+
source_key: sourceKey // ← Protecao: so deleta da mesma fonte
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Upsert Transaction — Scoping (EVOLUCAO NECESSARIA):**
|
|
235
|
+
Estado atual: deleteMany na transaction deleta por reference sem filtrar source_key.
|
|
236
|
+
Evolucao: Adicionar source_key no where:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const sourceKey = preparedImoveis[0]?.source_key;
|
|
240
|
+
await tx.property.deleteMany({
|
|
241
|
+
where: {
|
|
242
|
+
reference: { in: references },
|
|
243
|
+
...(sourceKey && { source_key: sourceKey })
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Nomenclatura padrao de source_key
|
|
249
|
+
|
|
250
|
+
Formato: `{crm}-{api|sync|vrsync}-{entidade-plural}`
|
|
251
|
+
|
|
252
|
+
Exemplos:
|
|
253
|
+
- `si9-api-imoveis`
|
|
254
|
+
- `jetimob-api-imoveis`
|
|
255
|
+
- `nifb-vrsync-imoveis`
|
|
256
|
+
- `si9-api-corretores`
|
|
257
|
+
- `manual-import-imoveis` (importacoes manuais)
|
|
258
|
+
|
|
259
|
+
### Ao criar modulo com multi-source
|
|
260
|
+
|
|
261
|
+
1. Banco: adicionar coluna `source_key TEXT`
|
|
262
|
+
2. Prisma: campo `source_key String?`
|
|
263
|
+
3. entity-schema.ts: campo com `filterable: true` e enum das fontes
|
|
264
|
+
4. Controller GET: aceitar `?source_key=` como query param
|
|
265
|
+
5. Service listing: filtrar `where: { source_key }` quando presente
|
|
266
|
+
6. Service upsert: garantir que source_key vem no payload e e persistido
|
|
267
|
+
7. Service delete: usar source_key como filtro adicional no deleteMany
|
|
268
|
+
8. Automations: cada integracao DEVE enviar source_key
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@horizon-framework/website-dev-docs",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Documentacao inteligente da plataforma Horizon para agentes de IA. Instale para tornar seu agente mais inteligente.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|