@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 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.0",
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,