@horizon-framework/website-dev-docs 2.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/agents/AGENTE_FULLSTACK.md +209 -0
- package/agents/AGENTE_LAYOUT_DESIGNER.md +930 -0
- package/checklists/ADICIONAR_CAMPO.md +95 -0
- package/checklists/CRIAR_SITE_NOVO.md +163 -0
- package/checklists/PUBLICAR_SITE.md +75 -0
- package/checklists/TROCAR_CRM.md +96 -0
- package/commercial/PACOTES_PAGINAS.md +86 -0
- package/commercial/POSSIBILIDADES_VENDA.md +52 -0
- package/index.md +54 -0
- package/knowledge/API_PROPERTY.md +566 -0
- package/knowledge/ARQUITETURA_GERAL.md +169 -0
- package/knowledge/ARQUITETURA_MODULOS_WEB.md +241 -0
- package/knowledge/BATCH_TOTALS_API.md +200 -0
- package/knowledge/CAPABILITIES.md +190 -0
- package/knowledge/COMPONENTES_GLOBAIS_UI.md +407 -0
- package/knowledge/CRMS_INTEGRACOES.md +151 -0
- package/knowledge/DESIGN_AVANCADO.md +403 -0
- package/knowledge/DESIGN_SYSTEM_CATALOG.md +349 -0
- package/knowledge/DESIGN_TEMPLATES_CATALOG.md +61 -0
- package/knowledge/DOMINIO_ENTIDADES.md +328 -0
- package/knowledge/HOOKS_REGISTRY.md +127 -0
- package/knowledge/MODULE_CREATION_PATTERN.md +624 -0
- package/knowledge/NAVEGACAO_DINAMICA.md +233 -0
- package/knowledge/PAGINAS_BASICAS.md +63 -0
- package/knowledge/SEARCH_ENGINE_API.md +1038 -0
- package/knowledge/SISTEMA_MODULAR.md +109 -0
- package/knowledge/SISTEMA_SCHEMAS.md +126 -0
- package/knowledge/SSOT_SPECIFICATION_V2.md +333 -0
- package/knowledge/UI_KIT_COMPLETO.md +125 -0
- package/modules/property/MODULO_IMOBILIARIO.md +356 -0
- package/package.json +17 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
# Padrão de Criação de Módulos - API v2
|
|
2
|
+
|
|
3
|
+
> Guia completo para criar novos módulos/entidades no sistema (ex: Branch, Lead, City)
|
|
4
|
+
|
|
5
|
+
## Arquitetura Completa de um Módulo
|
|
6
|
+
|
|
7
|
+
Um módulo completo possui **4 componentes principais**:
|
|
8
|
+
|
|
9
|
+
| Componente | Localização | Responsabilidade |
|
|
10
|
+
|------------|-------------|------------------|
|
|
11
|
+
| **NPM Package** | `/horizon-npm-packages/horizon-modules/{entity}/domain-data/` | Schema base (TypeScript + Zod) |
|
|
12
|
+
| **Database** | `/apps/api/database/tables/{entity}/` | SQL puro (CREATE, índices, FTS) |
|
|
13
|
+
| **API Module** | `/apps/api/src/modules/{entity}/` | Controllers, Services, Mappers |
|
|
14
|
+
| **API Routes** | `/apps/api/src/app/api/{entity}/` | Endpoints Next.js |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Estrutura de Pastas Completa
|
|
19
|
+
|
|
20
|
+
### NPM Package (`@horizon-domains/{entity}-model`)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
horizon-npm-packages/horizon-modules/{entity}/domain-data/
|
|
24
|
+
├── package.json # @horizon-domains/{entity}-model
|
|
25
|
+
├── tsconfig.json # Configuração TypeScript
|
|
26
|
+
├── tsup.config.ts # Build config (CJS, ESM, DTS)
|
|
27
|
+
├── vitest.config.ts # Testes
|
|
28
|
+
└── src/
|
|
29
|
+
├── index.ts # Exports principais
|
|
30
|
+
└── schemas/
|
|
31
|
+
├── types.ts # Tipos base (EntitySchema, FieldSchema, etc.)
|
|
32
|
+
├── horizon-{entity}-schema-base.ts # Schema TypeScript
|
|
33
|
+
└── horizon-{entity}-schema-base.zod.ts # Schema Zod para validação
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Database (SQL)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
apps/api/database/tables/{entity}/
|
|
40
|
+
├── create.sql # CREATE TABLE + índices
|
|
41
|
+
├── generated-columns.sql # FTS tsvector, campos derivados
|
|
42
|
+
└── drop.sql # DROP TABLE (desenvolvimento)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### API Module
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
apps/api/src/modules/{entity}/
|
|
49
|
+
├── schemas/ # Config/Schemas (renomeado de config/)
|
|
50
|
+
│ ├── entity-schema.ts # Schema TypeScript (SSOT local)
|
|
51
|
+
│ ├── entity-schema.zod.ts # Validação Zod
|
|
52
|
+
│ └── search-schema.ts # Schema de busca (se aplicável)
|
|
53
|
+
│
|
|
54
|
+
├── controllers/
|
|
55
|
+
│ ├── find.controller.ts # POST busca única
|
|
56
|
+
│ ├── search.controller.ts # POST busca avançada
|
|
57
|
+
│ ├── sync.controller.ts # PUT upsert / DELETE
|
|
58
|
+
│ └── schemas.controller.ts # GET schemas JSON
|
|
59
|
+
│
|
|
60
|
+
├── services/
|
|
61
|
+
│ ├── find.service.ts # Lógica de busca única
|
|
62
|
+
│ ├── search.service.ts # Lógica de busca múltipla
|
|
63
|
+
│ └── sync.service.ts # Lógica de sync/upsert/delete
|
|
64
|
+
│
|
|
65
|
+
├── mappers/
|
|
66
|
+
│ ├── computedFields/ # Campos computados
|
|
67
|
+
│ │ ├── index.ts # Orquestra computações
|
|
68
|
+
│ │ └── buildSlug.ts # Gera slug (exemplo)
|
|
69
|
+
│ │
|
|
70
|
+
│ └── dataModifier/ # Regras de negócio
|
|
71
|
+
│ └── index.ts # Transformações cliente-específicas
|
|
72
|
+
│
|
|
73
|
+
└── lib/ # Funções auxiliares (opcional)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### API Routes
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
apps/api/src/app/api/{entity}/
|
|
80
|
+
├── find/
|
|
81
|
+
│ └── route.ts # POST /api/{entity}/find
|
|
82
|
+
├── search/
|
|
83
|
+
│ └── route.ts # POST /api/{entity}/search
|
|
84
|
+
├── sync/
|
|
85
|
+
│ └── route.ts # PUT/DELETE /api/{entity}/sync
|
|
86
|
+
└── schemas/
|
|
87
|
+
├── entity/
|
|
88
|
+
│ └── route.ts # GET /api/{entity}/schemas/entity
|
|
89
|
+
└── search/
|
|
90
|
+
└── route.ts # GET /api/{entity}/schemas/search
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Passo a Passo para Criar Novo Módulo
|
|
96
|
+
|
|
97
|
+
### Passo 1: Criar NPM Package
|
|
98
|
+
|
|
99
|
+
**1.1 Criar estrutura de diretórios:**
|
|
100
|
+
```bash
|
|
101
|
+
mkdir -p /horizon-npm-packages/horizon-modules/{entity}/domain-data/src/schemas
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**1.2 Criar `package.json`:**
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"name": "@horizon-domains/{entity}-model",
|
|
108
|
+
"version": "1.0.0",
|
|
109
|
+
"description": "Schema definitions for {Entity}",
|
|
110
|
+
"main": "dist/index.js",
|
|
111
|
+
"module": "dist/index.mjs",
|
|
112
|
+
"types": "dist/index.d.ts",
|
|
113
|
+
"exports": {
|
|
114
|
+
".": {
|
|
115
|
+
"import": "./dist/index.mjs",
|
|
116
|
+
"require": "./dist/index.js",
|
|
117
|
+
"types": "./dist/index.d.ts"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"scripts": {
|
|
121
|
+
"build": "tsup",
|
|
122
|
+
"test": "vitest"
|
|
123
|
+
},
|
|
124
|
+
"dependencies": {
|
|
125
|
+
"zod": "^3.24.2"
|
|
126
|
+
},
|
|
127
|
+
"devDependencies": {
|
|
128
|
+
"tsup": "^8.0.0",
|
|
129
|
+
"typescript": "^5.0.0",
|
|
130
|
+
"vitest": "^1.0.0"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**1.3 Criar `tsconfig.json`:**
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"compilerOptions": {
|
|
139
|
+
"target": "ES2020",
|
|
140
|
+
"module": "ESNext",
|
|
141
|
+
"moduleResolution": "bundler",
|
|
142
|
+
"declaration": true,
|
|
143
|
+
"declarationMap": true,
|
|
144
|
+
"strict": true,
|
|
145
|
+
"esModuleInterop": true,
|
|
146
|
+
"skipLibCheck": true,
|
|
147
|
+
"outDir": "./dist"
|
|
148
|
+
},
|
|
149
|
+
"include": ["src/**/*"],
|
|
150
|
+
"exclude": ["node_modules", "dist"]
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**1.4 Criar `tsup.config.ts`:**
|
|
155
|
+
```typescript
|
|
156
|
+
import { defineConfig } from "tsup";
|
|
157
|
+
|
|
158
|
+
export default defineConfig({
|
|
159
|
+
entry: ["src/index.ts"],
|
|
160
|
+
format: ["cjs", "esm"],
|
|
161
|
+
dts: true,
|
|
162
|
+
clean: true,
|
|
163
|
+
sourcemap: true,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**1.5 Criar `src/schemas/types.ts`:**
|
|
168
|
+
```typescript
|
|
169
|
+
// Reexportar tipos base do property-model ou definir localmente
|
|
170
|
+
export interface FieldSchema {
|
|
171
|
+
type: "string" | "number" | "boolean" | "date" | "array" | "object" | "json";
|
|
172
|
+
required?: boolean;
|
|
173
|
+
description?: string;
|
|
174
|
+
ui?: UIMetadata;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface UIMetadata {
|
|
178
|
+
label?: string;
|
|
179
|
+
placeholder?: string;
|
|
180
|
+
inputType?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface EntitySchema {
|
|
184
|
+
name: string;
|
|
185
|
+
description?: string;
|
|
186
|
+
fields: Record<string, FieldSchema>;
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**1.6 Criar `src/schemas/horizon-{entity}-schema-base.ts`:**
|
|
191
|
+
```typescript
|
|
192
|
+
import type { EntitySchema } from "./types";
|
|
193
|
+
|
|
194
|
+
export const horizon{Entity}SchemaBase: EntitySchema = {
|
|
195
|
+
name: "{Entity}",
|
|
196
|
+
description: "Schema base para {Entity}",
|
|
197
|
+
fields: {
|
|
198
|
+
key: { type: "string", required: true, description: "Identificador único" },
|
|
199
|
+
name: { type: "string", required: true, description: "Nome" },
|
|
200
|
+
slug: { type: "string", required: false, description: "URL amigável" },
|
|
201
|
+
// Adicione campos específicos aqui
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**1.7 Criar `src/schemas/horizon-{entity}-schema-base.zod.ts`:**
|
|
207
|
+
```typescript
|
|
208
|
+
import { z } from "zod";
|
|
209
|
+
|
|
210
|
+
export const Horizon{Entity}SchemaBaseZod = z.object({
|
|
211
|
+
key: z.string().min(1),
|
|
212
|
+
name: z.string().min(1),
|
|
213
|
+
slug: z.string().optional(),
|
|
214
|
+
// Adicione validações Zod aqui
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Versão passthrough (aceita campos extras)
|
|
218
|
+
export const Horizon{Entity}SchemaBaseZodPassthrough = Horizon{Entity}SchemaBaseZod.passthrough();
|
|
219
|
+
|
|
220
|
+
export type Horizon{Entity}SchemaBaseType = z.infer<typeof Horizon{Entity}SchemaBaseZod>;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**1.8 Criar `src/index.ts`:**
|
|
224
|
+
```typescript
|
|
225
|
+
// Schema TypeScript
|
|
226
|
+
export { horizon{Entity}SchemaBase } from "./schemas/horizon-{entity}-schema-base";
|
|
227
|
+
|
|
228
|
+
// Types
|
|
229
|
+
export type { EntitySchema, FieldSchema, UIMetadata } from "./schemas/types";
|
|
230
|
+
|
|
231
|
+
// Schema Zod
|
|
232
|
+
export {
|
|
233
|
+
Horizon{Entity}SchemaBaseZod,
|
|
234
|
+
Horizon{Entity}SchemaBaseZodPassthrough,
|
|
235
|
+
type Horizon{Entity}SchemaBaseType,
|
|
236
|
+
} from "./schemas/horizon-{entity}-schema-base.zod";
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**1.9 Build do pacote:**
|
|
240
|
+
```bash
|
|
241
|
+
cd /horizon-npm-packages/horizon-modules/{entity}/domain-data
|
|
242
|
+
pnpm install
|
|
243
|
+
pnpm build
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### Passo 2: Criar Tabela SQL
|
|
249
|
+
|
|
250
|
+
**2.1 Criar `database/tables/{entity}/create.sql`:**
|
|
251
|
+
```sql
|
|
252
|
+
-- =============================================
|
|
253
|
+
-- TABELA: {entity}
|
|
254
|
+
-- =============================================
|
|
255
|
+
|
|
256
|
+
CREATE TABLE IF NOT EXISTS {entity} (
|
|
257
|
+
-- Sistema
|
|
258
|
+
id SERIAL PRIMARY KEY,
|
|
259
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
260
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
261
|
+
|
|
262
|
+
-- Identificação (obrigatórios)
|
|
263
|
+
key VARCHAR(255) UNIQUE NOT NULL,
|
|
264
|
+
name VARCHAR(500) NOT NULL,
|
|
265
|
+
slug VARCHAR(600),
|
|
266
|
+
|
|
267
|
+
-- Campos específicos
|
|
268
|
+
-- Adicione campos aqui
|
|
269
|
+
|
|
270
|
+
-- Full-text search
|
|
271
|
+
search_tsvector TSVECTOR
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
-- Índices
|
|
275
|
+
CREATE INDEX IF NOT EXISTS idx_{entity}_key ON {entity}(key);
|
|
276
|
+
CREATE INDEX IF NOT EXISTS idx_{entity}_slug ON {entity}(slug);
|
|
277
|
+
CREATE INDEX IF NOT EXISTS idx_{entity}_search ON {entity} USING GIN(search_tsvector);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**2.2 Criar `database/tables/{entity}/generated-columns.sql`:**
|
|
281
|
+
```sql
|
|
282
|
+
-- =============================================
|
|
283
|
+
-- GENERATED COLUMNS: {entity}
|
|
284
|
+
-- Full-text search e campos derivados
|
|
285
|
+
-- =============================================
|
|
286
|
+
|
|
287
|
+
-- Função para atualizar tsvector
|
|
288
|
+
CREATE OR REPLACE FUNCTION update_{entity}_search_tsvector()
|
|
289
|
+
RETURNS TRIGGER AS $$
|
|
290
|
+
BEGIN
|
|
291
|
+
NEW.search_tsvector :=
|
|
292
|
+
setweight(to_tsvector('portuguese', COALESCE(NEW.name, '')), 'A');
|
|
293
|
+
-- Adicione mais campos conforme necessário
|
|
294
|
+
RETURN NEW;
|
|
295
|
+
END;
|
|
296
|
+
$$ LANGUAGE plpgsql;
|
|
297
|
+
|
|
298
|
+
-- Trigger para atualização automática
|
|
299
|
+
DROP TRIGGER IF EXISTS trigger_update_{entity}_search ON {entity};
|
|
300
|
+
CREATE TRIGGER trigger_update_{entity}_search
|
|
301
|
+
BEFORE INSERT OR UPDATE ON {entity}
|
|
302
|
+
FOR EACH ROW
|
|
303
|
+
EXECUTE FUNCTION update_{entity}_search_tsvector();
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**2.3 Criar `database/tables/{entity}/drop.sql`:**
|
|
307
|
+
```sql
|
|
308
|
+
-- DROP (apenas desenvolvimento)
|
|
309
|
+
DROP TABLE IF EXISTS {entity} CASCADE;
|
|
310
|
+
DROP FUNCTION IF EXISTS update_{entity}_search_tsvector();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**2.4 Executar no banco e sincronizar Prisma:**
|
|
314
|
+
```bash
|
|
315
|
+
# Executar SQL no banco (psql ou DBeaver)
|
|
316
|
+
psql -d your_db -f database/tables/{entity}/create.sql
|
|
317
|
+
psql -d your_db -f database/tables/{entity}/generated-columns.sql
|
|
318
|
+
|
|
319
|
+
# Sincronizar Prisma (pull do banco)
|
|
320
|
+
pnpm prisma db pull
|
|
321
|
+
pnpm prisma generate
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### Passo 3: Criar Módulo na API
|
|
327
|
+
|
|
328
|
+
**3.1 Criar estrutura de pastas:**
|
|
329
|
+
```bash
|
|
330
|
+
mkdir -p src/modules/{entity}/{schemas,controllers,services,mappers/computedFields,mappers/dataModifier}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**3.2 Criar `schemas/entity-schema.ts`:**
|
|
334
|
+
```typescript
|
|
335
|
+
/**
|
|
336
|
+
* Entity Schema do {Entity} - SSOT Local
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
// Importa do NPM Package (ou define localmente)
|
|
340
|
+
// import { horizon{Entity}SchemaBase } from '@horizon-domains/{entity}-model';
|
|
341
|
+
|
|
342
|
+
export const {entity}EntitySchema = {
|
|
343
|
+
name: "{Entity}",
|
|
344
|
+
fields: {
|
|
345
|
+
key: { type: "string", required: true },
|
|
346
|
+
name: { type: "string", required: true },
|
|
347
|
+
slug: { type: "string", required: false },
|
|
348
|
+
// ...campos
|
|
349
|
+
},
|
|
350
|
+
// Override local se necessário
|
|
351
|
+
displayOverrides: {}
|
|
352
|
+
};
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**3.3 Criar `mappers/computedFields/index.ts`:**
|
|
356
|
+
```typescript
|
|
357
|
+
/**
|
|
358
|
+
* 🔄 Computed Fields do {Entity}
|
|
359
|
+
*/
|
|
360
|
+
import { buildSlug } from "./buildSlug";
|
|
361
|
+
|
|
362
|
+
export function compute{Entity}Fields(data: any, config?: any): any {
|
|
363
|
+
const enriched = { ...data };
|
|
364
|
+
|
|
365
|
+
// 1. Gerar slug
|
|
366
|
+
if (!enriched.slug && enriched.name) {
|
|
367
|
+
enriched.slug = buildSlug(enriched);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 2. Timestamps
|
|
371
|
+
if (!enriched.created_at) {
|
|
372
|
+
enriched.created_at = new Date();
|
|
373
|
+
}
|
|
374
|
+
enriched.updated_at = new Date();
|
|
375
|
+
|
|
376
|
+
return enriched;
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**3.4 Criar `mappers/computedFields/buildSlug.ts`:**
|
|
381
|
+
```typescript
|
|
382
|
+
/**
|
|
383
|
+
* Build Slug
|
|
384
|
+
*/
|
|
385
|
+
export function buildSlug(data: any): string {
|
|
386
|
+
if (!data.name) return data.key || "{entity}";
|
|
387
|
+
|
|
388
|
+
const baseSlug = data.name
|
|
389
|
+
.toLowerCase()
|
|
390
|
+
.normalize("NFD")
|
|
391
|
+
.replace(/[\u0300-\u036f]/g, "") // Remove acentos
|
|
392
|
+
.replace(/[^a-z0-9]+/g, "-") // Substitui especiais
|
|
393
|
+
.replace(/^-+|-+$/g, "") // Remove - bordas
|
|
394
|
+
.substring(0, 50);
|
|
395
|
+
|
|
396
|
+
return data.key ? `${baseSlug}-${data.key}` : baseSlug;
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**3.5 Criar `mappers/dataModifier/index.ts`:**
|
|
401
|
+
```typescript
|
|
402
|
+
/**
|
|
403
|
+
* Data Modifier do {Entity}
|
|
404
|
+
* Aplica regras de negócio (passthrough por padrão)
|
|
405
|
+
*/
|
|
406
|
+
export function modify{Entity}Data<T extends Record<string, any>>(data: T): T {
|
|
407
|
+
// Passthrough - adicione regras conforme necessidade
|
|
408
|
+
return data;
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**3.6 Criar `services/sync.service.ts`:**
|
|
413
|
+
```typescript
|
|
414
|
+
import { prisma } from '@/core/prisma';
|
|
415
|
+
import { modify{Entity}Data } from '../mappers/dataModifier';
|
|
416
|
+
import { compute{Entity}Fields } from '../mappers/computedFields';
|
|
417
|
+
|
|
418
|
+
export interface UpsertResponse {
|
|
419
|
+
inserted: number;
|
|
420
|
+
updated: number;
|
|
421
|
+
keys: string[];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export async function upsert{Entity}sService(
|
|
425
|
+
items: any[],
|
|
426
|
+
config?: any
|
|
427
|
+
): Promise<UpsertResponse> {
|
|
428
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
429
|
+
return { inserted: 0, updated: 0, keys: [] };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const prepared: any[] = [];
|
|
433
|
+
const keys: string[] = [];
|
|
434
|
+
|
|
435
|
+
for (const item of items) {
|
|
436
|
+
const modified = modify{Entity}Data(item);
|
|
437
|
+
const enriched = compute{Entity}Fields(modified, config);
|
|
438
|
+
prepared.push(enriched);
|
|
439
|
+
keys.push(item.key);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const result = await prisma.$transaction(async (tx) => {
|
|
443
|
+
const deleteResult = await tx.{entity}.deleteMany({
|
|
444
|
+
where: { key: { in: keys } }
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const insertResult = await tx.{entity}.createMany({
|
|
448
|
+
data: prepared,
|
|
449
|
+
skipDuplicates: true
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return { deleted: deleteResult.count, inserted: insertResult.count };
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
return { inserted: result.inserted, updated: result.inserted, keys };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export async function delete{Entity}sService(keys: string[]): Promise<any> {
|
|
459
|
+
if (!Array.isArray(keys) || keys.length === 0) {
|
|
460
|
+
return { deleted: 0, keys: [] };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const result = await prisma.{entity}.deleteMany({
|
|
464
|
+
where: { key: { in: keys } }
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
return { deleted: result.count, keys };
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**3.7 Criar Controllers e Routes:**
|
|
472
|
+
|
|
473
|
+
Seguir o mesmo padrão dos módulos existentes (property, broker).
|
|
474
|
+
Exportar handlers nos arquivos route.ts.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### Passo 4: Testar Endpoints
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
# 1. Inserir dados (Sync)
|
|
482
|
+
curl -X PUT http://localhost:3000/api/{entity}/sync \
|
|
483
|
+
-H "Content-Type: application/json" \
|
|
484
|
+
-d '{ "{entity}s": [{ "key": "test-001", "name": "Test" }] }'
|
|
485
|
+
|
|
486
|
+
# 2. Buscar por critério (Find)
|
|
487
|
+
curl -X POST http://localhost:3000/api/{entity}/find \
|
|
488
|
+
-H "Content-Type: application/json" \
|
|
489
|
+
-d '{ "criteria": { "key": "test-001" } }'
|
|
490
|
+
|
|
491
|
+
# 3. Busca avançada (Search)
|
|
492
|
+
curl -X POST http://localhost:3000/api/{entity}/search \
|
|
493
|
+
-H "Content-Type: application/json" \
|
|
494
|
+
-d '{ "list": { "page": 1, "limit": 10 } }'
|
|
495
|
+
|
|
496
|
+
# 4. Deletar (Sync DELETE)
|
|
497
|
+
curl -X DELETE http://localhost:3000/api/{entity}/sync \
|
|
498
|
+
-H "Content-Type: application/json" \
|
|
499
|
+
-d '{ "keys": ["test-001"] }'
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## 📌 Checklist de Validação
|
|
505
|
+
|
|
506
|
+
### NPM Package
|
|
507
|
+
- [ ] `package.json` com nome `@horizon-domains/{entity}-model`
|
|
508
|
+
- [ ] Schema TypeScript criado (`horizon-{entity}-schema-base.ts`)
|
|
509
|
+
- [ ] Schema Zod criado (`horizon-{entity}-schema-base.zod.ts`)
|
|
510
|
+
- [ ] `src/index.ts` exportando tudo
|
|
511
|
+
- [ ] Build funciona (`pnpm build`)
|
|
512
|
+
|
|
513
|
+
### Database
|
|
514
|
+
- [ ] `create.sql` com tabela e índices
|
|
515
|
+
- [ ] `generated-columns.sql` com FTS tsvector
|
|
516
|
+
- [ ] SQL executado no banco
|
|
517
|
+
- [ ] Prisma sincronizado (`prisma db pull && prisma generate`)
|
|
518
|
+
|
|
519
|
+
### API Module
|
|
520
|
+
- [ ] Estrutura de pastas criada (`schemas/`, `controllers/`, `services/`, `mappers/`)
|
|
521
|
+
- [ ] `entity-schema.ts` criado
|
|
522
|
+
- [ ] `computedFields/` com `index.ts` e `buildSlug.ts`
|
|
523
|
+
- [ ] `dataModifier/index.ts` criado (passthrough)
|
|
524
|
+
- [ ] Services implementados (`sync`, `find`, `search`)
|
|
525
|
+
- [ ] Controllers implementados com validação Zod
|
|
526
|
+
- [ ] Routes configuradas em `/app/api/{entity}/`
|
|
527
|
+
|
|
528
|
+
### Testes
|
|
529
|
+
- [ ] PUT sync funciona (inserir)
|
|
530
|
+
- [ ] POST find funciona (buscar)
|
|
531
|
+
- [ ] POST search funciona (listar)
|
|
532
|
+
- [ ] DELETE sync funciona (deletar)
|
|
533
|
+
- [ ] Slug é gerado automaticamente
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Arquitetura WEB - Estrutura de Módulos no Frontend
|
|
538
|
+
|
|
539
|
+
Um módulo no frontend (`/apps/web/src/modules/{entity}/`) segue estrutura padronizada:
|
|
540
|
+
|
|
541
|
+
### **Estrutura Completa WEB**
|
|
542
|
+
|
|
543
|
+
```
|
|
544
|
+
apps/web/src/modules/{entity}/
|
|
545
|
+
├── core/ # Lógica core do módulo
|
|
546
|
+
│ ├── module.config.ts # Configuração do módulo
|
|
547
|
+
│ ├── item/ # Tudo relacionado a UM item
|
|
548
|
+
│ │ ├── index.ts # Exports
|
|
549
|
+
│ │ ├── {entity}-url-builder.ts # Builder de URLs
|
|
550
|
+
│ │ ├── enrichMapper.ts # Mapper para enriquecer dados
|
|
551
|
+
│ │ ├── schemas/ # Schemas do item
|
|
552
|
+
│ │ │ ├── index.ts # Exports dos schemas
|
|
553
|
+
│ │ │ ├── schema.ts # Schema TypeScript (SSOT)
|
|
554
|
+
│ │ │ ├── schema.generated.json # Schema gerado da API
|
|
555
|
+
│ │ │ ├── base-schema.json # Schema base (campos)
|
|
556
|
+
│ │ │ ├── display-layer-schema.json # Overrides de UI
|
|
557
|
+
│ │ │ └── computed-schema.json # Campos computados
|
|
558
|
+
│ │ └── computed-fields/ # Campos computados runtime
|
|
559
|
+
│ │ ├── index.ts
|
|
560
|
+
│ │ ├── {entity}-url.ts # Gerador de URL
|
|
561
|
+
│ │ └── assembleComputedData.ts
|
|
562
|
+
│ ├── list/ # Tudo relacionado a LISTA (se houver)
|
|
563
|
+
│ │ └── ...
|
|
564
|
+
│ ├── libs/ # Bibliotecas específicas (se houver)
|
|
565
|
+
│ │ └── ...
|
|
566
|
+
│ └── services/ # Services do módulo
|
|
567
|
+
│ └── {Entity}Service.ts
|
|
568
|
+
│
|
|
569
|
+
└── features/ # Features/páginas do módulo
|
|
570
|
+
├── item-display/ # Página individual
|
|
571
|
+
│ └── page-display/
|
|
572
|
+
│ ├── components/
|
|
573
|
+
│ └── data/
|
|
574
|
+
└── search-list/ # Listagem com busca (se houver)
|
|
575
|
+
├── components/
|
|
576
|
+
├── config/
|
|
577
|
+
└── hooks/
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### **Arquivos Obrigatórios por Módulo**
|
|
581
|
+
|
|
582
|
+
| Arquivo | Descrição |
|
|
583
|
+
|---------|-----------|
|
|
584
|
+
| `core/item/index.ts` | Exportações do item |
|
|
585
|
+
| `core/item/schemas/index.ts` | Exportações dos schemas |
|
|
586
|
+
| `core/item/schemas/schema.ts` | Schema TypeScript |
|
|
587
|
+
| `core/item/schemas/schema.generated.json` | Schema da API |
|
|
588
|
+
| `core/item/enrichMapper.ts` | Mapper de enriquecimento |
|
|
589
|
+
| `core/item/{entity}-url-builder.ts` | Construtor de URLs |
|
|
590
|
+
|
|
591
|
+
### **Comparação Property vs Broker**
|
|
592
|
+
|
|
593
|
+
| Aspecto | Property (Completo) | Broker (Simplificado) |
|
|
594
|
+
|---------|---------------------|----------------------|
|
|
595
|
+
| `core/item/schemas/` | Completo | Completo |
|
|
596
|
+
| `core/item/computed-fields/` | Sim | Sim |
|
|
597
|
+
| `core/libs/` | 7 módulos | Não precisa |
|
|
598
|
+
| `core/list/` | Sim | Não precisa |
|
|
599
|
+
| `features/search-list/` | 20+ componentes | Fora do escopo |
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Referências
|
|
604
|
+
|
|
605
|
+
| Módulo | Descrição | Complexidade |
|
|
606
|
+
|--------|-----------|--------------|
|
|
607
|
+
| **Property** | Módulo completo de imóveis | Alta (com CRM, rules, computed) |
|
|
608
|
+
| **Broker** | Módulo simplificado de corretores | Baixa (passthrough) |
|
|
609
|
+
|
|
610
|
+
Use o **Broker** como template para módulos simples.
|
|
611
|
+
Use o **Property** como referência para módulos complexos.
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## Convenções
|
|
616
|
+
|
|
617
|
+
| Elemento | Convenção | Exemplo |
|
|
618
|
+
|----------|-----------|---------|
|
|
619
|
+
| Types/Interfaces | PascalCase | `BrokerEntity` |
|
|
620
|
+
| Funções | camelCase | `findBrokerService` |
|
|
621
|
+
| Arquivos | kebab-case | `entity-schema.ts` |
|
|
622
|
+
| Tabelas SQL | snake_case | `broker` |
|
|
623
|
+
| NPM Package | kebab-case | `@horizon-domains/broker-model` |
|
|
624
|
+
| Pastas | kebab-case | `computed-fields/` ou `computedFields/` |
|