@maykonpaulo/maestro-admin 0.1.1 → 0.1.2-next.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/README.md +564 -1
- package/package.json +2 -7
package/README.md
CHANGED
|
@@ -1,6 +1,569 @@
|
|
|
1
1
|
# @maykonpaulo/maestro-admin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Este guia leva um programador do zero a um **painel administrativo governado** (listar,
|
|
4
|
+
filtrar, criar, editar, excluir, exportar, com RBAC e auditoria) sobre **o banco de dados de
|
|
5
|
+
qualquer sistema** — sem escrever CRUD por entidade.
|
|
6
|
+
|
|
7
|
+
> **O que é o Maestro?** Uma engine metadata-driven em TypeScript que você conecta a um banco
|
|
8
|
+
> existente para operá-lo com segurança, sem acesso direto ao banco. Você aponta para o banco,
|
|
9
|
+
> o Maestro descobre o schema (ou você declara), e ele entrega uma API REST + uma UI de admin
|
|
10
|
+
> genérica que se auto-monta. É **agnóstico de framework, de banco e de domínio**.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Índice
|
|
15
|
+
|
|
16
|
+
1. [Pré-requisitos](#1-pré-requisitos)
|
|
17
|
+
2. [Escolha seu banco (providers)](#2-escolha-seu-banco-providers)
|
|
18
|
+
3. [Instalação](#3-instalação)
|
|
19
|
+
4. [Caminho A — Turnkey (recomendado)](#4-caminho-a--turnkey-recomendado)
|
|
20
|
+
5. [Caminho B — Declarativo/curado](#5-caminho-b--declarativocurado)
|
|
21
|
+
6. [Autenticação (`actorResolver`)](#6-autenticação-actorresolver)
|
|
22
|
+
7. [Autorização (RBAC)](#7-autorização-rbac)
|
|
23
|
+
8. [Auditoria](#8-auditoria)
|
|
24
|
+
9. [Operações customizadas (ações de negócio)](#9-operações-customizadas-ações-de-negócio)
|
|
25
|
+
10. [Campos: tipos, sensíveis, soft delete, relações](#10-campos-tipos-sensíveis-soft-delete-relações)
|
|
26
|
+
11. [A UI de admin (`maestro-admin`)](#11-a-ui-de-admin-maestro-admin)
|
|
27
|
+
12. [A API HTTP (para frontends próprios)](#12-a-api-http-para-frontends-próprios)
|
|
28
|
+
13. [Referência rápida](#13-referência-rápida)
|
|
29
|
+
14. [Produção: checklist e ressalvas](#14-produção-checklist-e-ressalvas)
|
|
30
|
+
15. [Solução de problemas](#15-solução-de-problemas)
|
|
31
|
+
16. [Pacotes e versões](#16-pacotes-e-versões)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 1. Pré-requisitos
|
|
36
|
+
|
|
37
|
+
- **Node.js 18+** e um gerenciador de pacotes (`npm`, `pnpm` ou `yarn`).
|
|
38
|
+
- Acesso ao banco de dados do sistema que você quer administrar (string de conexão).
|
|
39
|
+
- Um mecanismo de autenticação seu (JWT, cookie de sessão, etc.) — o Maestro **não** faz auth;
|
|
40
|
+
ele consome o "ator" que você já autenticou.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. Escolha seu banco (providers)
|
|
45
|
+
|
|
46
|
+
O Maestro fala com o banco através de um **provider**. Existem 9 pacotes oficiais publicados —
|
|
47
|
+
cada um implementa leitura de schema (introspecção), CRUD real e criação de coleção/tabela:
|
|
48
|
+
|
|
49
|
+
| Banco | Pacote |
|
|
50
|
+
|---|---|
|
|
51
|
+
| PostgreSQL · MySQL · SQLite · SQL Server | `@maykonpaulo/maestro-provider-sql` |
|
|
52
|
+
| MongoDB | `@maykonpaulo/maestro-provider-mongodb` |
|
|
53
|
+
| Redis | `@maykonpaulo/maestro-provider-redis` |
|
|
54
|
+
| Elasticsearch | `@maykonpaulo/maestro-provider-elasticsearch` |
|
|
55
|
+
| DynamoDB | `@maykonpaulo/maestro-provider-dynamodb` |
|
|
56
|
+
| Cassandra | `@maykonpaulo/maestro-provider-cassandra` |
|
|
57
|
+
| Couchbase | `@maykonpaulo/maestro-provider-couchbase` |
|
|
58
|
+
| Firestore | `@maykonpaulo/maestro-provider-firestore` |
|
|
59
|
+
| Neo4j | `@maykonpaulo/maestro-provider-neo4j` |
|
|
60
|
+
|
|
61
|
+
> Banco fora dessa lista? Você pode implementar a interface `DatasourceProvider` (6 métodos) —
|
|
62
|
+
> ver seção [Solução de problemas](#15-solução-de-problemas). Mas para qualquer um acima, é só instalar.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 3. Instalação
|
|
67
|
+
|
|
68
|
+
Instale a **engine**, o **provider** do seu banco, o **servidor turnkey** e a **UI**:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# engine + servidor + UI
|
|
72
|
+
npm i @maykonpaulo/maestro-core @maykonpaulo/maestro-server @maykonpaulo/maestro-admin
|
|
73
|
+
|
|
74
|
+
# provider do seu banco (exemplo: MongoDB)
|
|
75
|
+
npm i @maykonpaulo/maestro-provider-mongodb
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Troque o provider pelo do seu banco (ex.: `@maykonpaulo/maestro-provider-sql`).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 4. Caminho A — Turnkey (recomendado)
|
|
83
|
+
|
|
84
|
+
**Aponte para o banco e tenha um admin completo, sem declarar entidade nenhuma.** O Maestro
|
|
85
|
+
introspecta o banco, transforma cada coleção/tabela em uma entidade administrável e sobe um
|
|
86
|
+
servidor REST. É o caminho mais rápido.
|
|
87
|
+
|
|
88
|
+
### 4.1 Conectar o provider
|
|
89
|
+
|
|
90
|
+
Escolha a fábrica do seu banco (confira as opções exatas de conexão no README do provider):
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// MongoDB
|
|
94
|
+
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
|
|
95
|
+
const provider = createMongoProvider({ uri: process.env.MONGO_URL!, database: 'app' });
|
|
96
|
+
|
|
97
|
+
// PostgreSQL (mesma família: mysql/sqlite/mssql via `dialect`)
|
|
98
|
+
import { createSqlProvider } from '@maykonpaulo/maestro-provider-sql';
|
|
99
|
+
const provider = createSqlProvider({ dialect: 'postgres', connectionString: process.env.PG_URL! });
|
|
100
|
+
// sqlite: createSqlProvider({ dialect: 'sqlite', databasePath: './app.db' })
|
|
101
|
+
// mysql: createSqlProvider({ dialect: 'mysql', uri: process.env.MYSQL_URL! })
|
|
102
|
+
|
|
103
|
+
// Outros
|
|
104
|
+
import { createRedisProvider } from '@maykonpaulo/maestro-provider-redis';
|
|
105
|
+
const provider = createRedisProvider({ uri: 'redis://localhost:6379' });
|
|
106
|
+
// createElasticsearchProvider({ node, auth })
|
|
107
|
+
// createDynamoDbProvider({ region })
|
|
108
|
+
// createCassandraProvider({ contactPoints, localDataCenter, keyspace })
|
|
109
|
+
// createCouchbaseProvider({ connectionString, username, password, bucket })
|
|
110
|
+
// createFirestoreProvider({ projectId })
|
|
111
|
+
// createNeo4jProvider({ uri, username, password })
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4.2 Construir o engine por introspecção
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { createIntrospectedEngine } from '@maykonpaulo/maestro-server';
|
|
118
|
+
|
|
119
|
+
const engine = await createIntrospectedEngine({
|
|
120
|
+
provider,
|
|
121
|
+
access: 'full', // 'full' = list/detalhe/criar/editar/excluir/exportar
|
|
122
|
+
// 'readonly' = só navegar (sem risco de alterar)
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`createIntrospectedEngine` descobre toda coleção/tabela e habilita as capacidades de escrita
|
|
127
|
+
(o padrão do core seria só-leitura — este helper é o que liga o CRUD).
|
|
128
|
+
|
|
129
|
+
**Opções:**
|
|
130
|
+
|
|
131
|
+
| Opção | Descrição |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `provider` | O provider conectado (obrigatório). |
|
|
134
|
+
| `access` | `'full'` (padrão) ou `'readonly'`. |
|
|
135
|
+
| `datasourceId` | id do datasource (padrão `'main'`). |
|
|
136
|
+
| `policies` | Política RBAC (ver §7). Padrão: papel `admin` com `['*']`. |
|
|
137
|
+
| `overrides` | Sobreposição curada (rótulos, campos, RBAC) mesclada sobre o schema descoberto. |
|
|
138
|
+
| `operations` | Operações customizadas (ver §9). |
|
|
139
|
+
| `audit` | Repositório de auditoria (ver §8). |
|
|
140
|
+
|
|
141
|
+
### 4.3 Subir o servidor
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { createMaestroServer } from '@maykonpaulo/maestro-server';
|
|
145
|
+
|
|
146
|
+
const server = createMaestroServer({ engine /*, actorResolver, cors, basePath */ });
|
|
147
|
+
await server.listen(3000);
|
|
148
|
+
// → REST admin para cada coleção em http://localhost:3000/entities/:colecao
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 4.4 Autenticação
|
|
152
|
+
|
|
153
|
+
Sem `actorResolver`, o servidor usa um resolver **só-dev** que concede um ator admin a toda
|
|
154
|
+
requisição. **Em produção, sempre passe o seu** — ver [§6](#6-autenticação-actorresolver).
|
|
155
|
+
|
|
156
|
+
### 4.5 Subir a UI
|
|
157
|
+
|
|
158
|
+
A UI genérica lê `GET /metadata` e se auto-monta (navegação, tabela, detalhe, formulários)
|
|
159
|
+
para **todas** as entidades. Rodando como app:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
VITE_MAESTRO_API_URL=http://localhost:3000 \
|
|
163
|
+
npm --prefix node_modules/@maykonpaulo/maestro-admin run dev
|
|
164
|
+
# abra http://localhost:5173
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Ou embutida no seu app React — ver [§11](#11-a-ui-de-admin-maestro-admin).
|
|
168
|
+
|
|
169
|
+
### 4.6 Servidor completo (copy-paste)
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// server.ts
|
|
173
|
+
import { createMongoProvider } from '@maykonpaulo/maestro-provider-mongodb';
|
|
174
|
+
import { createMaestroServer, createIntrospectedEngine } from '@maykonpaulo/maestro-server';
|
|
175
|
+
import { InMemoryAuditRepository, type RbacPolicy } from '@maykonpaulo/maestro-core';
|
|
176
|
+
|
|
177
|
+
const provider = createMongoProvider({ uri: process.env.MONGO_URL!, database: 'app' });
|
|
178
|
+
|
|
179
|
+
const policies: RbacPolicy = {
|
|
180
|
+
roles: {
|
|
181
|
+
admin: { id: 'admin', name: 'Admin', permissions: ['*'] },
|
|
182
|
+
viewer: { id: 'viewer', name: 'Viewer', permissions: ['entity.*.list', 'entity.*.detail'] },
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const engine = await createIntrospectedEngine({
|
|
187
|
+
provider,
|
|
188
|
+
access: 'full',
|
|
189
|
+
policies,
|
|
190
|
+
audit: new InMemoryAuditRepository(), // troque por seu repositório em produção (§8)
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const server = createMaestroServer({
|
|
194
|
+
engine,
|
|
195
|
+
actorResolver: async (req) => {
|
|
196
|
+
// derive o ator da SUA autenticação (JWT/cookie)
|
|
197
|
+
const token = String(req.headers['authorization'] ?? '').replace('Bearer ', '');
|
|
198
|
+
const user = await verifyJwt(token); // sua função
|
|
199
|
+
return { actor: { id: user.sub, type: 'user', roles: user.roles } };
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await server.listen(3000);
|
|
204
|
+
console.log('Admin API em http://localhost:3000');
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 5. Caminho B — Declarativo/curado
|
|
210
|
+
|
|
211
|
+
Quando quiser **rótulos bonitos, RBAC fino, tipos de campo explícitos e formulários controlados**,
|
|
212
|
+
declare as entidades. Você tem controle total; o Maestro monta o resto.
|
|
213
|
+
|
|
214
|
+
### 5.1 Declarar entidades
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { type DeclarativeConfig } from '@maykonpaulo/maestro-core';
|
|
218
|
+
|
|
219
|
+
const declarations: DeclarativeConfig = {
|
|
220
|
+
entities: [
|
|
221
|
+
{
|
|
222
|
+
entity: 'order', // = nome da tabela/coleção
|
|
223
|
+
label: 'Pedido',
|
|
224
|
+
pluralLabel: 'Pedidos',
|
|
225
|
+
fields: {
|
|
226
|
+
id: { type: 'string', primary: true },
|
|
227
|
+
code: { type: 'string', label: 'Código', required: true, searchable: true, sortable: true },
|
|
228
|
+
status: { type: 'enum', label: 'Status',
|
|
229
|
+
enumOptions: [
|
|
230
|
+
{ value: 'open', label: 'Aberto' },
|
|
231
|
+
{ value: 'paid', label: 'Pago' },
|
|
232
|
+
{ value: 'canceled', label: 'Cancelado' },
|
|
233
|
+
] },
|
|
234
|
+
total: { type: 'currency', label: 'Total', sortable: true },
|
|
235
|
+
paid: { type: 'boolean', label: 'Pago' },
|
|
236
|
+
createdAt: { type: 'datetime', label: 'Criado em', sortable: true },
|
|
237
|
+
notes: { type: 'text', label: 'Observações' },
|
|
238
|
+
},
|
|
239
|
+
// capacidades: liste o que este admin pode fazer nesta entidade
|
|
240
|
+
capabilities: { list: true, detail: true, create: true, update: true, delete: true, export: true },
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 5.2 `createMaestro` + servir
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { createMaestro } from '@maykonpaulo/maestro-core';
|
|
250
|
+
import { createMaestroServer } from '@maykonpaulo/maestro-server';
|
|
251
|
+
|
|
252
|
+
const engine = createMaestro({
|
|
253
|
+
datasources: { main: provider }, // o provider do seu banco (§4.1)
|
|
254
|
+
declarations,
|
|
255
|
+
policies, // RBAC (§7)
|
|
256
|
+
audit: myAuditRepository, // (§8)
|
|
257
|
+
operations: [cancelOrder], // ações customizadas (§9)
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await createMaestroServer({ engine, actorResolver }).listen(3000);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 5.3 A vs B (e híbrido)
|
|
264
|
+
|
|
265
|
+
- **A (introspecção):** o mais rápido; toda coleção vira entidade; schema inferido.
|
|
266
|
+
- **B (declarativo):** controle total; ideal para as entidades que os operadores mais usam.
|
|
267
|
+
- **Híbrido:** passe `overrides` (config declarativa) para `createIntrospectedEngine` — o Maestro
|
|
268
|
+
descobre tudo e aplica seus rótulos/regras por cima nas entidades que você curou.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 6. Autenticação (`actorResolver`)
|
|
273
|
+
|
|
274
|
+
Autenticação é **responsabilidade sua**. O único ponto onde ela entra no Maestro é o
|
|
275
|
+
`actorResolver`, que converte cada requisição em um **`Actor`**:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
const server = createMaestroServer({
|
|
279
|
+
engine,
|
|
280
|
+
actorResolver: async (req) => {
|
|
281
|
+
const token = String(req.headers['authorization'] ?? '').replace('Bearer ', '');
|
|
282
|
+
const user = await verifyJwt(token); // SUA autenticação
|
|
283
|
+
return {
|
|
284
|
+
actor: {
|
|
285
|
+
id: user.sub,
|
|
286
|
+
type: 'user', // 'user' | 'system' | 'job' | 'integration' | 'ai-agent'
|
|
287
|
+
name: user.name,
|
|
288
|
+
email: user.email,
|
|
289
|
+
roles: user.roles, // dão as permissões (§7)
|
|
290
|
+
},
|
|
291
|
+
correlationId: String(req.headers['x-correlation-id'] ?? ''), // opcional (rastreio na auditoria)
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- **Omitir `actorResolver` = rodar sem autenticação** (o default `devActorResolver` concede admin a
|
|
298
|
+
todos). Nunca faça isso em produção.
|
|
299
|
+
- O `Actor` é passado a **toda** operação; o RBAC decide o que ele pode fazer.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 7. Autorização (RBAC)
|
|
304
|
+
|
|
305
|
+
Você define **papéis** e as **permissões** de cada um. O ator recebe permissões pelos seus `roles`.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import { type RbacPolicy } from '@maykonpaulo/maestro-core';
|
|
309
|
+
|
|
310
|
+
const policies: RbacPolicy = {
|
|
311
|
+
roles: {
|
|
312
|
+
admin: { id: 'admin', name: 'Admin', permissions: ['*'] },
|
|
313
|
+
operator: { id: 'operator', name: 'Operador', permissions: ['entity.order.*', 'operation.order.cancel'] },
|
|
314
|
+
viewer: { id: 'viewer', name: 'Leitura', permissions: ['entity.*.list', 'entity.*.detail', 'entity.*.export'] },
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Formato das permissões:**
|
|
320
|
+
|
|
321
|
+
| Permissão | Concede |
|
|
322
|
+
|---|---|
|
|
323
|
+
| `entity.<entidade>.<capacidade>` | Uma ação em uma entidade. Capacidades: `list`, `detail`, `create`, `update`, `clone`, `delete`, `softDelete`, `restore`, `export`. |
|
|
324
|
+
| `operation.<opId>` | Executar uma operação customizada (§9). |
|
|
325
|
+
| `entity.order.*` | Tudo na entidade `order`. |
|
|
326
|
+
| `entity.*.list` | `list` em todas as entidades. |
|
|
327
|
+
| `*` | Tudo. |
|
|
328
|
+
|
|
329
|
+
Curinga: `*` (tudo) e `prefixo.*` (tudo abaixo do prefixo). Um `403` é retornado quando negado — a
|
|
330
|
+
UI trata isso como erro na tela, sem quebrar.
|
|
331
|
+
|
|
332
|
+
> **Importante:** as **capacidades** (o que a entidade permite) e o **RBAC** (o que o ator pode) são
|
|
333
|
+
> checados juntos. No modo introspecção `access: 'full'`, todas as capacidades ficam ligadas; o RBAC
|
|
334
|
+
> é quem restringe por papel.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 8. Auditoria
|
|
339
|
+
|
|
340
|
+
Toda operação emite um evento de auditoria quando você fornece um `AuditRepository`. Para começar,
|
|
341
|
+
use o `InMemoryAuditRepository`; em produção, implemente o contrato gravando no seu store:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { type AuditRepository, type AuditEvent } from '@maykonpaulo/maestro-core';
|
|
345
|
+
|
|
346
|
+
class MyAuditRepository implements AuditRepository {
|
|
347
|
+
async record(event: AuditEvent): Promise<void> {
|
|
348
|
+
await db.collection('audit').insertOne(event); // grave onde quiser
|
|
349
|
+
}
|
|
350
|
+
async list(filter) { /* consulte por ator/ação/entidade/período/correlationId */ return []; }
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Cada evento inclui ação, ator, recurso, nível, `before`/`after` (em update/delete) e `correlationId`.
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 9. Operações customizadas (ações de negócio)
|
|
359
|
+
|
|
360
|
+
Além do CRUD, exponha **ações** (ex.: "cancelar pedido", "reenviar e-mail"). Elas aparecem como
|
|
361
|
+
botões (por linha / em massa / globais) e executam sua lógica com RBAC + auditoria:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import { type OperationDef } from '@maykonpaulo/maestro-core';
|
|
365
|
+
|
|
366
|
+
const cancelOrder: OperationDef = {
|
|
367
|
+
id: 'order.cancel',
|
|
368
|
+
label: 'Cancelar pedido',
|
|
369
|
+
entity: 'order',
|
|
370
|
+
scope: 'record', // 'record' | 'bulk' | 'global'
|
|
371
|
+
requiredPermission: 'operation.order.cancel',
|
|
372
|
+
execute: async ({ record, input, actor }) => {
|
|
373
|
+
// sua lógica de negócio aqui
|
|
374
|
+
await paymentGateway.refund(record.id);
|
|
375
|
+
return { success: true, message: 'Pedido cancelado.' };
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// registre no engine:
|
|
380
|
+
createMaestro({ /* ... */ operations: [cancelOrder] });
|
|
381
|
+
// ou createIntrospectedEngine({ /* ... */ operations: [cancelOrder] });
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Chamada HTTP: `POST /operations/order.cancel` com `{ entityId, record, input }`.
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 10. Campos: tipos, sensíveis, soft delete, relações
|
|
389
|
+
|
|
390
|
+
**Tipos de campo** (`type`): `string`, `text`, `number`, `integer`, `decimal`, `currency`,
|
|
391
|
+
`boolean`, `date`, `datetime`, `time`, `email`, `phone`, `url`, `document`, `uuid`, `enum`,
|
|
392
|
+
`json`, `relation`, `array`. A UI escolhe o controle certo por tipo (checkbox, número, select,
|
|
393
|
+
data, textarea/JSON…).
|
|
394
|
+
|
|
395
|
+
**Flags úteis por campo** (no modo declarativo): `required`, `readonly`, `primary`, `sensitive`,
|
|
396
|
+
`searchable`, `sortable`, `filterable`, `exportable`, `enumOptions` (para `enum`), `relationEntity`
|
|
397
|
+
(para `relation`).
|
|
398
|
+
|
|
399
|
+
- **`sensitive: true`** — mascara o valor (`***`) na exportação CSV e na UI. (Logging/`before`/`after`
|
|
400
|
+
continuam responsabilidade sua.)
|
|
401
|
+
- **Soft delete** — configure um campo que marca "inativo" (ex.: `active`/`deletedAt`). As
|
|
402
|
+
operações `softDelete`/`restore` passam a existir em vez de exclusão física.
|
|
403
|
+
- **Relações** — o Maestro não infere FK em bancos NoSQL; declare relações à mão quando quiser abas
|
|
404
|
+
de relacionamento no detalhe.
|
|
405
|
+
- **Campos cifrados (E2EE / zero-knowledge)** — se o seu backend cifra campos, eles chegam **opacos**;
|
|
406
|
+
a UI os exibe como estão e **nunca descriptografa**. Comece o admin pelas coleções não cifradas.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 11. A UI de admin (`maestro-admin`)
|
|
411
|
+
|
|
412
|
+
A UI (React + Vite + Tailwind) lê `GET /metadata` e monta sozinha: navegação por entidade, tabela
|
|
413
|
+
(com busca/ordenação/paginação/filtros vindos do metadata), detalhe, e formulários de criar/editar.
|
|
414
|
+
Ela respeita as **capacidades** (esconde New/Edit/Delete quando desabilitados) e o **RBAC** (403 vira
|
|
415
|
+
erro na tela). **Uma tela, todas as entidades.**
|
|
416
|
+
|
|
417
|
+
### App standalone
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
VITE_MAESTRO_API_URL=http://localhost:3000 \
|
|
421
|
+
npm --prefix node_modules/@maykonpaulo/maestro-admin run dev # dev server
|
|
422
|
+
# ou build estático:
|
|
423
|
+
VITE_MAESTRO_API_URL=http://localhost:3000 \
|
|
424
|
+
npm --prefix node_modules/@maykonpaulo/maestro-admin run build:app
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
`VITE_MAESTRO_ROLE=viewer` (ou `?role=viewer` na URL) define o header `X-Role` — útil para testar RBAC.
|
|
428
|
+
|
|
429
|
+
### Embutida no seu app React
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { MaestroAdmin } from '@maykonpaulo/maestro-admin';
|
|
433
|
+
import '@maykonpaulo/maestro-admin/src/styles.css';
|
|
434
|
+
|
|
435
|
+
export function AdminPage() {
|
|
436
|
+
return (
|
|
437
|
+
<MaestroAdmin
|
|
438
|
+
apiUrl="http://localhost:3000"
|
|
439
|
+
headers={() => ({ Authorization: `Bearer ${getToken()}` })} // pode ser função (token fresco a cada request)
|
|
440
|
+
title="Admin"
|
|
441
|
+
/>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Também exportamos os blocos (`EntityList`, `EntityDetail`, `EntityForm`, `Sidebar`, `useMetadata`,
|
|
447
|
+
`useEntityList`, `MaestroClient`, …) para montar um layout próprio.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## 12. A API HTTP (para frontends próprios)
|
|
452
|
+
|
|
453
|
+
Se você quiser construir sua própria UI (ou integrar outro sistema), o servidor expõe:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
GET /health
|
|
457
|
+
GET /metadata # descreve todas as entidades (campos, tipos, capacidades)
|
|
458
|
+
GET /metadata/:entidade
|
|
459
|
+
GET /entities/:entidade # list — query: page, pageSize, search, searchFields, sort, filter
|
|
460
|
+
POST /entities/:entidade # criar
|
|
461
|
+
GET /entities/:entidade/:id # detalhe
|
|
462
|
+
PATCH /entities/:entidade/:id # editar
|
|
463
|
+
DELETE /entities/:entidade/:id # excluir (hard delete)
|
|
464
|
+
POST /entities/:entidade/:id/clone
|
|
465
|
+
POST /entities/:entidade/:id/soft-delete
|
|
466
|
+
POST /entities/:entidade/:id/restore
|
|
467
|
+
GET /entities/:entidade/export # ?format=csv|json
|
|
468
|
+
POST /operations/:operationId # { entityId, record, input }
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Query string do list:**
|
|
472
|
+
|
|
473
|
+
| Param | Exemplo | Significado |
|
|
474
|
+
|---|---|---|
|
|
475
|
+
| `page`, `pageSize` | `?page=1&pageSize=20` | Paginação. |
|
|
476
|
+
| `search`, `searchFields` | `?search=ada&searchFields=name,email` | Busca textual. |
|
|
477
|
+
| `sort` | `?sort=name:asc` (repetível) | Ordenação. |
|
|
478
|
+
| `filter` | `?filter=status:equals:open` (repetível) | Filtro `campo:operador:valor`. |
|
|
479
|
+
|
|
480
|
+
Todo endpoint checa RBAC e emite auditoria. O `GET /metadata` é o contrato para gerar qualquer UI.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## 13. Referência rápida
|
|
485
|
+
|
|
486
|
+
**Operadores de filtro:** `equals`, `notEquals`, `contains`, `startsWith`, `endsWith`, `in`,
|
|
487
|
+
`notIn`, `gt`, `gte`, `lt`, `lte`, `between`, `isNull`, `isNotNull`, `isTrue`, `isFalse`.
|
|
488
|
+
|
|
489
|
+
**Capacidades de entidade:** `list`, `detail`, `create`, `update`, `clone`, `delete`, `softDelete`,
|
|
490
|
+
`export`, `bulkActions`. Padrão do core: `list`/`detail` ligadas, o resto desligado (por segurança).
|
|
491
|
+
No modo `createIntrospectedEngine({ access: 'full' })`, todas ligadas.
|
|
492
|
+
|
|
493
|
+
**Métodos da engine** (se você usar o core direto, sem o servidor): `list`, `findById`, `create`,
|
|
494
|
+
`update`, `clone`, `softDelete`, `restore`, `hardDelete`, `export`, `executeOperation`, `getMetadata`
|
|
495
|
+
— todos recebem o `actor` e checam RBAC + auditoria.
|
|
496
|
+
|
|
497
|
+
**Actor:** `{ id, type: 'user'|'system'|'job'|'integration'|'ai-agent', name?, email?, roles?, permissions?, metadata? }`.
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
501
|
+
## 14. Produção: checklist e ressalvas
|
|
502
|
+
|
|
503
|
+
- [ ] **Sempre** forneça um `actorResolver` real (JWT/cookie). Sem ele = sem auth.
|
|
504
|
+
- [ ] Defina papéis RBAC de verdade (não deixe todo mundo `admin`/`['*']`).
|
|
505
|
+
- [ ] Comece com `access: 'readonly'` para validar a leitura do banco de produção sem risco; suba
|
|
506
|
+
para `'full'` quando confiar.
|
|
507
|
+
- [ ] Implemente um `AuditRepository` que persista (não o InMemory).
|
|
508
|
+
- [ ] Configure CORS conforme seu frontend (`createMaestroServer({ cors: { origin, credentials } })`).
|
|
509
|
+
- [ ] Comece o admin pelas coleções **operacionais não cifradas** (usuários, configs, etc.). Campos
|
|
510
|
+
E2EE aparecem opacos.
|
|
511
|
+
- [ ] Rode o servidor atrás do seu gateway/HTTPS; o Maestro é só a camada de operação.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## 15. Solução de problemas
|
|
516
|
+
|
|
517
|
+
- **"Nome da coleção não bate" (MongoDB/Mongoose).** No modo introspecção, cada entidade é a coleção
|
|
518
|
+
**real** (ex.: `users`). No modo declarativo, o `entity` da declaração é o nome da coleção — o
|
|
519
|
+
Mongoose costuma pluralizar (`User` → `users`); declare o nome real, ou use o `EntitySchema` de
|
|
520
|
+
baixo nível (`source.table`) para mapear um id amigável a uma coleção diferente.
|
|
521
|
+
- **`_id` (MongoDB).** O provider trata `_id` como `ObjectId` nativo **ou** string de forma
|
|
522
|
+
transparente — funciona sobre coleções existentes sem migração.
|
|
523
|
+
- **Campos `currency`/`decimal` aparecem como texto?** Use a versão mais recente do `maestro-admin`.
|
|
524
|
+
- **Banco não coberto pelos 9 providers.** Implemente `DatasourceProvider` (6 métodos: `list`,
|
|
525
|
+
`findById`, `create`, `update`, `delete`, `count`) e passe em `datasources: { main: seuProvider }`.
|
|
526
|
+
- **Publicou no `@latest` por engano?** Auth e canais de release não fazem parte deste guia — o
|
|
527
|
+
admin em si só precisa dos pacotes instalados e do servidor no ar.
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## 16. Pacotes e versões
|
|
532
|
+
|
|
533
|
+
Instale pela tag `latest` (estável):
|
|
534
|
+
|
|
535
|
+
```bash
|
|
536
|
+
npm i @maykonpaulo/maestro-core \
|
|
537
|
+
@maykonpaulo/maestro-server \
|
|
538
|
+
@maykonpaulo/maestro-admin \
|
|
539
|
+
@maykonpaulo/maestro-provider-<seu-banco>
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
| Pacote | O que é |
|
|
543
|
+
|---|---|
|
|
544
|
+
| `@maykonpaulo/maestro-core` | Engine, contratos, metadata, RBAC, auditoria, operações, export. |
|
|
545
|
+
| `@maykonpaulo/maestro-server` | Servidor HTTP turnkey (`createMaestroServer`, `createIntrospectedEngine`). |
|
|
546
|
+
| `@maykonpaulo/maestro-admin` | UI genérica metadata-driven (app + componentes React). |
|
|
547
|
+
| `@maykonpaulo/maestro-provider-*` | Provider do seu banco (9 opções). |
|
|
548
|
+
| `@maykonpaulo/maestro-cli` | CLI opcional (`introspect`/`generate`/`validate`/`diff`/`snapshot`). |
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
### Resumo em 6 linhas
|
|
553
|
+
|
|
554
|
+
```ts
|
|
555
|
+
const provider = createMongoProvider({ uri, database }); // 1. conecte o banco
|
|
556
|
+
const engine = await createIntrospectedEngine({ provider, access: 'full', policies, audit }); // 2. engine
|
|
557
|
+
const server = createMaestroServer({ engine, actorResolver }); // 3. auth + servidor
|
|
558
|
+
await server.listen(3000); // 4. API no ar
|
|
559
|
+
// 5. rode @maykonpaulo/maestro-admin apontando VITE_MAESTRO_API_URL para :3000
|
|
560
|
+
// 6. pronto: admin de todo o sistema, com RBAC e auditoria
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## Referência do pacote `@maykonpaulo/maestro-admin`
|
|
566
|
+
|
|
4
567
|
|
|
5
568
|
The **generic, metadata-driven admin UI** for Maestro, per [ADR 0008 — Camada Turnkey](../../docs/adr/0008-turnkey-server-and-admin-ui.md).
|
|
6
569
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maykonpaulo/maestro-admin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2-next.0",
|
|
4
4
|
"description": "Generic, metadata-driven admin UI for @maykonpaulo/maestro-core (via @maykonpaulo/maestro-server). Reads GET /metadata and auto-builds navigation, tables, detail views and create/update forms for every entity/collection — no per-entity code. Ships as a runnable React app and as exported components (ADR 0008).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,12 +46,7 @@
|
|
|
46
46
|
"vite": "^6.0.0",
|
|
47
47
|
"vitest": "^2.0.0"
|
|
48
48
|
},
|
|
49
|
-
"
|
|
50
|
-
"type": "git",
|
|
51
|
-
"url": "https://github.com/maykonpaulo/maestro.git",
|
|
52
|
-
"directory": "packages/admin"
|
|
53
|
-
},
|
|
54
|
-
"homepage": "https://github.com/maykonpaulo/maestro#readme",
|
|
49
|
+
"homepage": "https://www.npmjs.com/package/@maykonpaulo/maestro-server",
|
|
55
50
|
"scripts": {
|
|
56
51
|
"dev": "vite",
|
|
57
52
|
"build": "tsup",
|