funifier-mcp 0.3.15 → 0.3.17

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.
Files changed (48) hide show
  1. package/.cursor/rules/funifier.mdc +1 -0
  2. package/.github/copilot-instructions.md +1 -0
  3. package/AGENTS.md +1 -0
  4. package/datasource-funifier-docs/.coverage.json +10 -3
  5. package/datasource-funifier-docs/.search-index.json +10572 -10093
  6. package/datasource-funifier-docs/.skills-map.json +4 -0
  7. package/datasource-funifier-docs/.validation.json +39 -3
  8. package/datasource-funifier-docs/knowledge/index.md +1 -0
  9. package/datasource-funifier-docs/knowledge/modules/audit.md +357 -0
  10. package/dist/core/api-client.d.ts +4 -1
  11. package/dist/core/api-client.d.ts.map +1 -1
  12. package/dist/core/api-client.js +30 -0
  13. package/dist/core/api-client.js.map +1 -1
  14. package/dist/core/constants.d.ts +1 -0
  15. package/dist/core/constants.d.ts.map +1 -1
  16. package/dist/core/constants.js +2 -0
  17. package/dist/core/constants.js.map +1 -1
  18. package/dist/core/types/Audit.d.ts +25 -0
  19. package/dist/core/types/Audit.d.ts.map +1 -0
  20. package/dist/core/types/Audit.js +3 -0
  21. package/dist/core/types/Audit.js.map +1 -0
  22. package/dist/core/types/index.d.ts +1 -0
  23. package/dist/core/types/index.d.ts.map +1 -1
  24. package/dist/core/types/index.js +1 -0
  25. package/dist/core/types/index.js.map +1 -1
  26. package/dist/mcp/bundle.js +74 -74
  27. package/dist/mcp/tools/_fetch-current.d.ts +1 -1
  28. package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
  29. package/dist/mcp/tools/_fetch-current.js +3 -0
  30. package/dist/mcp/tools/_fetch-current.js.map +1 -1
  31. package/dist/mcp/tools/delete.d.ts.map +1 -1
  32. package/dist/mcp/tools/delete.js +4 -0
  33. package/dist/mcp/tools/delete.js.map +1 -1
  34. package/dist/mcp/tools/get.d.ts.map +1 -1
  35. package/dist/mcp/tools/get.js +4 -0
  36. package/dist/mcp/tools/get.js.map +1 -1
  37. package/dist/mcp/tools/list.d.ts.map +1 -1
  38. package/dist/mcp/tools/list.js +14 -0
  39. package/dist/mcp/tools/list.js.map +1 -1
  40. package/dist/mcp/tools/save.d.ts.map +1 -1
  41. package/dist/mcp/tools/save.js +3 -0
  42. package/dist/mcp/tools/save.js.map +1 -1
  43. package/dist/mcp/tools/save.test.js +12 -0
  44. package/dist/mcp/tools/save.test.js.map +1 -1
  45. package/package.json +2 -2
  46. package/skills/funifier/SKILL.md +1 -0
  47. package/skills/funifier/references/create-aggregate.md +3 -1
  48. package/skills/funifier/references/create-audit.md +115 -0
@@ -30,6 +30,10 @@
30
30
  "knowledge/modules/action.md",
31
31
  "knowledge/modules/action-log.md"
32
32
  ],
33
+ "funifier-create-audit": [
34
+ "knowledge/modules/audit.md",
35
+ "knowledge/modules/database.md"
36
+ ],
33
37
  "funifier-create-point": [
34
38
  "knowledge/modules/point.md",
35
39
  "knowledge/modules/achievement.md"
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-06-11T20:10:00Z",
2
+ "generatedAt": "2026-06-12T12:18:00Z",
3
3
  "skills": {
4
4
  "funifier-configure-security": {
5
5
  "configHash": "fba0f846a53838e369f5c079104cd4a2f16a9d535a0320701d16287e53f35496",
@@ -33,7 +33,7 @@
33
33
  ]
34
34
  },
35
35
  "funifier-create-aggregate": {
36
- "configHash": "77d4c5a8da82be77c50fdc1ed457e14141fd206a0c27425a3e0984b52e6322de",
36
+ "configHash": "9598173385df039c369c8efc036ec22faaa3f9e56cb6c787498bf3d72b18a634",
37
37
  "status": "verified",
38
38
  "claims": [
39
39
  {
@@ -48,6 +48,42 @@
48
48
  }
49
49
  ]
50
50
  },
51
+ "funifier-create-audit": {
52
+ "configHash": "17d53b67ecf952aaa352f0799e465d00de59960cd51cea31dee6f5d9ad5f5c8f",
53
+ "status": "verified",
54
+ "claims": [
55
+ {
56
+ "claim": "Audit config fields are _id, title, active, entity, event, clean, type with event constants create/update/delete and type constants studio/player/all",
57
+ "gitnexusQuery": "Audit",
58
+ "critical": true,
59
+ "status": "verified",
60
+ "evidence": {
61
+ "symbol": "Class:src/main/java/com/funifier/engine/audit/Audit.java:Audit",
62
+ "file": "src/main/java/com/funifier/engine/audit/Audit.java"
63
+ }
64
+ },
65
+ {
66
+ "claim": "AuditManager.log only writes an AuditLog when an active Audit matches entity+event and the config type matches the operation origin",
67
+ "gitnexusQuery": "AuditManager",
68
+ "critical": true,
69
+ "status": "verified",
70
+ "evidence": {
71
+ "symbol": "Class:src/main/java/com/funifier/engine/audit/AuditManager.java:AuditManager",
72
+ "file": "src/main/java/com/funifier/engine/audit/AuditManager.java"
73
+ }
74
+ },
75
+ {
76
+ "claim": "clearExpiredLogs purges audit_log entries older than each config's clean retention and is invoked from AsyncProcessor",
77
+ "gitnexusQuery": "clearExpiredLogs",
78
+ "critical": false,
79
+ "status": "verified",
80
+ "evidence": {
81
+ "symbol": "Method:src/main/java/com/funifier/engine/audit/AuditManager.java:AuditManager.clearExpiredLogs#0",
82
+ "file": "src/main/java/com/funifier/engine/audit/AuditManager.java"
83
+ }
84
+ }
85
+ ]
86
+ },
51
87
  "funifier-create-challenge": {
52
88
  "configHash": "45e50eab442325d3342dddff5d7c48f34002fe0182ed78c31a48fffdb8eafd03",
53
89
  "status": "verified",
@@ -501,7 +537,7 @@
501
537
  ]
502
538
  },
503
539
  "funifier-help": {
504
- "configHash": "6b534cd059dbc6f241443411999eef674aaa792577a757e9abf1f7e6c8ec62ef",
540
+ "configHash": "7af4ce568c8429bd33b797d070dfaed47ad08618ce1074c8532df0aa33fed1c8",
505
541
  "status": "verified",
506
542
  "claims": [
507
543
  {
@@ -86,6 +86,7 @@ A Funifier funciona como um **backend de aplicações**. Independentemente do ti
86
86
  |--------|----------|-----------|-------------|
87
87
  | Auth | `modules/auth.md` | Autenticação e tokens | Gerar tokens de acesso; configurar API keys; autenticação OAuth |
88
88
  | Security | `modules/security.md` | Configuração de segurança | Configurar apps (app_secret, scope, whitelist) e roles de jogador; entender escopos hierárquicos, pipeline de autorização e emissão de tokens; diagnóstico de erros 401 |
89
+ | Audit | `modules/audit.md` | Auditoria de operações | Registrar e consultar quem criou/alterou/removeu jogadores ou documentos de coleções (entity/event/type/retenção); montar trilhas de auditoria robustas e relatórios de "quem fez o quê" |
89
90
  | Folder | `modules/folder.md` | Trilhas e cursos | Criar trilhas de aprendizagem e cursos; organizar conteúdos em hierarquia com monitoramento de progresso do jogador |
90
91
  | Backup | `modules/backup.md` | Backup e restauração | Criar backups da gamificação; restaurar configurações |
91
92
  | Compact | `modules/compact.md` | Compactação de dados | Compactar dados históricos para otimizar performance |
@@ -0,0 +1,357 @@
1
+ # `audit`
2
+
3
+ **Acesso Studio:** `/studio/audit` _(convenção de UI)_
4
+ **API Endpoint:** `/v3/audit` (`com.funifier.rest.v3.rest.AuditRest`)
5
+ **Coleções MongoDB:** `audit` (configuração) e `audit_log` (registros)
6
+
7
+ > Documento de engenharia reversa baseado **exclusivamente** no código de `funifier-service` (`com.funifier.engine.audit`) e `funifier-studio` (`app/scripts/controllers/studio/audit`). Cobre a **configuração de auditoria** (entidade `Audit`, coleção `audit`) e o **registro de eventos** (entidade `AuditLog`, coleção `audit_log`).
8
+
9
+ ---
10
+
11
+ ## 1. Visão Geral
12
+
13
+ A auditoria do Funifier tem **dois objetos distintos**:
14
+
15
+ 1. **`Audit` (configuração)** — declara *o que* rastrear: uma combinação `entity` + `event` (ex.: `player` + `update`). É um cadastro, criado via `POST /v3/audit`. Sem uma config `Audit` **ativa** para um par `entity`/`event`, nenhum log é gravado.
16
+ 2. **`AuditLog` (registro)** — é a *ocorrência* gravada quando uma operação rastreada acontece. Guarda *quem* (`user`/`login`), *quando* (`time`), o *objeto* afetado (`item`) e a *origem* (`type`: `studio`/`player`). Vive na coleção `audit_log`.
17
+
18
+ Papel arquitetural:
19
+
20
+ - **Trilha de auditoria sob demanda.** A auditoria é *opt-in* por par `entity`/`event` — diferente de um log global. Você liga só o que quer.
21
+ - **Retenção automática.** Cada config tem um campo `clean` (ex.: `"30d"`); um job periódico (`AuditManager.clearExpiredLogs`, disparado pelo `AsyncProcessor`) remove os `audit_log` mais velhos que a retenção da sua config.
22
+ - **Filtro por origem.** O campo `type` (`studio`/`player`/`all`) decide se logam operações vindas do Studio (usuário admin autenticado), do Player (token de jogador) ou ambas.
23
+
24
+ > ⚠️ **Cobertura limitada de gatilhos.** Hoje, somente dois pontos do código chamam `AuditManager.log`: o cadastro/atualização de **player** (`PlayerManager`) e as operações de **database** (`DatabaseRest`: create/update/delete em qualquer coleção). Criar uma config para `entity:"challenge"` + `event:"create"` é aceito, mas **nunca produzirá logs** — challenge não dispara auditoria. Vide seção 7.
25
+
26
+ ---
27
+
28
+ ## 2. Arquitetura e Fluxos
29
+
30
+ ### 2.1 Classes envolvidas
31
+
32
+ | Classe | Papel |
33
+ |---|---|
34
+ | `com.funifier.engine.audit.Audit` | Entidade/POJO da configuração (coleção `audit`). |
35
+ | `com.funifier.engine.audit.AuditLog` | Entidade/POJO do registro (coleção `audit_log`). |
36
+ | `com.funifier.engine.audit.AuditManager` | Manager: `save`, `find`, `delete` (cascata), `log`, `clearExpiredLogs`. |
37
+ | `com.funifier.rest.v3.rest.AuditRest` | Controller REST v3 (`/v3/audit`). |
38
+ | `com.funifier.controller.AsyncProcessor` | Dispara `clearExpiredLogs` periodicamente (limpeza de retenção). |
39
+
40
+ `Audit` e `AuditLog` usam `@JsonIgnoreProperties(ignoreUnknown=true)` — **campos desconhecidos são descartados** na desserialização.
41
+
42
+ ### 2.2 Quem dispara um log — `AuditManager.log(entity, event, auth, item)`
43
+
44
+ A gravação só acontece se **todas** as condições forem satisfeitas (`AuditManager.log`):
45
+
46
+ ```
47
+ [1] entity, event e auth não nulos
48
+ [2] determina a origem:
49
+ user = auth.getUserId() // Studio (admin)
50
+ player = auth.getPlayerFromTokenIfExist() // Player
51
+ type = user != null ? "studio"
52
+ : player != null ? "player"
53
+ : null → se null, NÃO loga
54
+ [3] busca config ATIVA: audit.findOne({active:true, entity:#, event:#})
55
+ → se não existe config ativa para esse entity/event, NÃO loga
56
+ [4] checa o filtro de origem da config:
57
+ type_da_config == "all"
58
+ OR (type_da_config == "studio" E user != null)
59
+ OR (type_da_config == "player" E player != null)
60
+ [5] grava AuditLog { audit, time, item, type, user, login }
61
+ (studio → resolve login via UserStudioManager; player → login = id do player)
62
+ [6] também imprime no stdout: "AUDIT - Title : ... - User : ... - Item : ..."
63
+ ```
64
+
65
+ ```mermaid
66
+ flowchart TD
67
+ A["operação rastreável<br/>(player ou database CRUD)"] --> B["AuditManager.log(entity, event, auth, item)"]
68
+ B --> C{"origem identificável?<br/>(studio ou player)"}
69
+ C -- não --> X[não loga]
70
+ C -- sim --> D{"existe Audit<br/>active:true,<br/>entity, event?"}
71
+ D -- não --> X
72
+ D -- sim --> E{"type da config<br/>combina com a origem?"}
73
+ E -- não --> X
74
+ E -- sim --> F["grava audit_log<br/>+ stdout"]
75
+ ```
76
+
77
+ ### 2.3 Pontos que chamam `log` (gatilhos atuais)
78
+
79
+ | Origem | Onde | Chamada |
80
+ |---|---|---|
81
+ | Player — criação | `PlayerManager:296` | `log(Entity.PLAYER.collection, EVENT_CREATE, authBean, player)` |
82
+ | Player — atualização | `PlayerManager:306` | `log(Entity.PLAYER.collection, EVENT_UPDATE, authBean, player)` |
83
+ | Database — insert | `DatabaseRest:233` | `log(collection, EVENT_CREATE, authBean, o)` |
84
+ | Database — update | `DatabaseRest:377` | `log(collection, EVENT_UPDATE, authBean, o)` |
85
+ | Database — delete | `DatabaseRest:488` | `log(collection, EVENT_DELETE, authBean, ids)` |
86
+
87
+ > No delete de database, `item` é o **conjunto de ids removidos**, não o documento completo.
88
+
89
+ ### 2.4 Retenção — `clearExpiredLogs` (cron)
90
+
91
+ `AsyncProcessor` chama periodicamente `AuditManager.clearExpiredLogs`, que, para **cada** config `Audit`, remove de `audit_log` os registros onde `time <= (agora - clean)`:
92
+
93
+ ```
94
+ para cada Audit:
95
+ expire = agora - audit.clean // ex.: clean "30d" → -30 dias
96
+ db.audit_log.remove({audit: audit._id, time: {$lte: expire}})
97
+ ```
98
+
99
+ Ou seja, a retenção é **por config** — cada par `entity`/`event` pode ter um `clean` diferente.
100
+
101
+ ### 2.5 Exclusão — `DELETE /v3/audit/:id` (cascata)
102
+
103
+ `AuditManager.delete` remove a config **e todos os seus logs**:
104
+
105
+ ```
106
+ db.audit.remove({_id: id})
107
+ db.audit_log.remove({audit: id}) // cascata
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 3. Estrutura dos Objetos
113
+
114
+ ### 3.1 `Audit` — configuração (coleção `audit`)
115
+
116
+ | Campo | Tipo | Padrão | Descrição |
117
+ |---|---|---|---|
118
+ | `_id` | String | auto (`Guid.shortTimeMillis()`) | ID da config. Se omitido, gerado no save. No Studio, ao informar, é saneado para `[A-Za-z0-9_]`. |
119
+ | `title` | String | — | Rótulo legível (ex.: `"Alteração de jogadores"`). Usado nos logs do stdout. |
120
+ | `active` | boolean | `true` | Só configs `active:true` produzem logs (`log` filtra por `active:true`). |
121
+ | `entity` | String | — | Nome da coleção/entidade rastreada (ex.: `"player"`). |
122
+ | `event` | String | — | `"create"`, `"update"` ou `"delete"` (constantes `EVENT_CREATE/UPDATE/DELETE`). |
123
+ | `clean` | String | `"30d"` | Retenção dos logs. Se ausente/vazio no save, o backend assume `"30d"`. |
124
+ | `type` | String | `"all"` | `"studio"`, `"player"` ou `"all"` (constantes `TYPE_STUDIO/PLAYER/ALL`). Filtra a origem. |
125
+ | `created` | Date | auto | Preenchido no primeiro save. |
126
+ | `updated` | Date | auto | Atualizado a cada save. |
127
+
128
+ **Comportamento de save (`AuditManager.save`):**
129
+
130
+ - `_id` ausente → gera `Guid.shortTimeMillis()` e define `created`.
131
+ - `clean` ausente/vazio → assume `"30d"`.
132
+ - `updated` sempre redefinido. `c.save` é **upsert por `_id`** (full replace).
133
+
134
+ ### 3.2 `AuditLog` — registro (coleção `audit_log`)
135
+
136
+ | Campo | Tipo | Descrição |
137
+ |---|---|---|
138
+ | `_id` | String | ID do registro (`Guid.newShortGuid()`). |
139
+ | `audit` | String | `_id` da config `Audit` que originou o log. |
140
+ | `user` | String | ID do ator (id do usuário Studio **ou** id do player). |
141
+ | `login` | String | Login do ator (resolvido via `UserStudioManager` para studio; = id para player). |
142
+ | `time` | Date | Momento do evento. |
143
+ | `item` | Object | Objeto auditado (documento) — ou **lista de ids** no delete de database. |
144
+ | `type` | String | Origem: `"studio"` ou `"player"`. |
145
+
146
+ ---
147
+
148
+ ## 4. Endpoints
149
+
150
+ Todos sob `AuditRest` (`@Path("v3/audit")`). Autenticação: Bearer token (`AuthBean`).
151
+
152
+ ### `POST /v3/audit`
153
+
154
+ | Aspecto | Detalhe |
155
+ |---|---|
156
+ | Finalidade | Criar **ou** atualizar uma config de auditoria. |
157
+ | Full replace ou patch | **Full replace** (upsert por `_id` via `c.save`). |
158
+ | Status | `201 CREATED`, retorna a config. |
159
+ | Comportamento real | Gera `_id`/`created` se ausentes; assume `clean:"30d"` se ausente; atualiza `updated`. |
160
+
161
+ **Exemplo:**
162
+
163
+ ```json
164
+ {
165
+ "title": "Alteração de jogadores",
166
+ "active": true,
167
+ "entity": "player",
168
+ "event": "update",
169
+ "type": "all",
170
+ "clean": "90d"
171
+ }
172
+ ```
173
+
174
+ ### `GET /v3/audit/:id`
175
+
176
+ | Aspecto | Detalhe |
177
+ |---|---|
178
+ | Finalidade | Buscar uma config por `_id` exato (`AuditManager.find`). |
179
+ | Resposta | A `Audit` (campos nulos removidos). |
180
+
181
+ ### `DELETE /v3/audit/:id`
182
+
183
+ | Aspecto | Detalhe |
184
+ |---|---|
185
+ | Finalidade | Excluir a config. |
186
+ | Status | `204 NO CONTENT`. |
187
+ | Comportamento real | **Cascata**: remove a config **e todos** os `audit_log` com `audit == id`. |
188
+
189
+ ### Consulta de logs — **não há endpoint dedicado**
190
+
191
+ `audit_log` é uma coleção comum; consulte-a pelo módulo `database` (vide `database.md`):
192
+
193
+ ```
194
+ POST /v3/database/audit_log/aggregate
195
+ ```
196
+
197
+ Pipeline típico (espelha o Studio — `controllers/studio/audit/log/list.js`):
198
+
199
+ ```json
200
+ [
201
+ { "$match": { "audit": "<audit_id>" } },
202
+ { "$match": { "time": { "$gte": { "$date": "-30d-" } } } },
203
+ { "$sort": { "time": -1 } }
204
+ ]
205
+ ```
206
+
207
+ Filtros úteis (todos opcionais, combináveis em `$match`):
208
+
209
+ | Quero filtrar por… | `$match` |
210
+ |---|---|
211
+ | Config específica | `{ "audit": "<audit_id>" }` |
212
+ | Ator (id ou login) | `{ "user": "<id>" }` ou `{ "login": "<login>" }` |
213
+ | Origem | `{ "type": "studio" }` (ou `"player"`) |
214
+ | Janela de tempo | `{ "time": { "$gte": {"$date": "..."}, "$lte": {"$date": "..."} } }` |
215
+ | Campo do objeto auditado | `{ "item.<campo>": <valor> }` |
216
+
217
+ > Para sintaxe de datas relativas/absolutas (`-30d-`, `$date`), veja `date-handling.md`.
218
+
219
+ ---
220
+
221
+ ## 5. Regras de Negócio
222
+
223
+ 1. **Auditoria é opt-in por par `entity`/`event`.** Sem config ativa, nada é logado.
224
+ 2. **`active:false` desliga.** A query de `log` exige `active:true`.
225
+ 3. **Filtro de origem (`type`).** `studio` loga só operações de admin; `player` só de jogador; `all` ambas. Operações sem ator identificável (nem user nem player) **nunca** logam.
226
+ 4. **Retenção por config.** `clean` é avaliado por config no cron; `"30d"` é o default do backend.
227
+ 5. **POST é upsert.** Reenviar com o mesmo `_id` substitui a config inteira.
228
+ 6. **Delete cascateia.** Remover a config apaga seus logs.
229
+ 7. **Multi-tenant.** Cada gamificação (apiKey) isola suas coleções `audit`/`audit_log`.
230
+
231
+ ---
232
+
233
+ ## 6. Comportamentos Automáticos
234
+
235
+ | Comportamento | Trigger | Impacto |
236
+ |---|---|---|
237
+ | Geração de `_id`/`created` | POST sem `_id` | `Guid.shortTimeMillis()` + `created` |
238
+ | Default de `clean` | POST sem `clean` | assume `"30d"` |
239
+ | Atualização de `updated` | todo POST | redefine `updated` |
240
+ | Gravação de `audit_log` | player/database CRUD com config ativa compatível | novo registro + linha no stdout |
241
+ | Limpeza por retenção | cron (`AsyncProcessor`) | remove logs com `time <= agora - clean` |
242
+ | Cascata de exclusão | DELETE da config | apaga config **e** todos os seus logs |
243
+
244
+ ---
245
+
246
+ ## 7. Suportado vs NÃO Suportado
247
+
248
+ ### ✅ Suportado
249
+
250
+ - CRUD de configs: `POST /v3/audit`, `GET /v3/audit/:id`, `DELETE /v3/audit/:id` (cascata).
251
+ - Auditoria de **player** (`create`/`update`) e de **database** (`create`/`update`/`delete` em qualquer coleção).
252
+ - Filtro de origem por `type` (`studio`/`player`/`all`).
253
+ - Retenção automática por config via `clean`.
254
+ - Consulta de logs por `database/audit_log/aggregate` com filtros de tempo, ator, origem e campos do `item`.
255
+
256
+ ### ❌ NÃO Suportado / Armadilhas
257
+
258
+ - **Auditoria de outras entidades** (challenge, level, action, virtual good, etc.) — **não há gatilho**. Só `player` e operações de `database` chamam `log`. Uma config para outra `entity` é inerte.
259
+ - **Endpoint de listagem de configs** — não existe `GET /v3/audit` (lista). Liste via `database/audit/aggregate`.
260
+ - **Endpoint dedicado de logs** — não existe; use `database/audit_log`.
261
+ - **Patch parcial** — POST é full replace (upsert).
262
+ - **`PUT`** — não existe; toda escrita é via POST.
263
+ - **Auditoria de leitura (read)** — só `create`/`update`/`delete`; não há evento de leitura.
264
+
265
+ ---
266
+
267
+ ## 8. Observabilidade e Troubleshooting
268
+
269
+ **Listar configs existentes:**
270
+
271
+ ```
272
+ POST /v3/database/audit/aggregate body: []
273
+ ```
274
+
275
+ **Contar logs por config (como o Studio faz):**
276
+
277
+ ```json
278
+ [
279
+ { "$lookup": {
280
+ "from": "audit_log",
281
+ "let": { "audit": "$_id" },
282
+ "pipeline": [
283
+ { "$match": { "$expr": { "$eq": ["$audit", "$$audit"] } } },
284
+ { "$group": { "_id": "$audit", "total": { "$sum": 1 } } }
285
+ ],
286
+ "as": "logs"
287
+ } }
288
+ ]
289
+ ```
290
+
291
+ **"Configurei a auditoria mas não aparece nenhum log":**
292
+
293
+ - A `entity` é `player` ou uma coleção manipulada via `database`? Outras entidades **não disparam** auditoria.
294
+ - A config está `active:true`?
295
+ - O `type` combina com a origem da operação? (operação de jogador não loga numa config `type:"studio"`.)
296
+ - A operação tinha um ator autenticado? Operações sem user/player não logam.
297
+
298
+ **"Os logs sumiram":** a retenção (`clean`) pode tê-los expirado; ou a config foi deletada (cascata apaga os logs).
299
+
300
+ ---
301
+
302
+ ## 9. Exemplos Práticos
303
+
304
+ ### 9.1 Auditar todas as alterações de jogadores, retidas por 90 dias
305
+
306
+ ```json
307
+ {
308
+ "title": "Alteração de jogadores",
309
+ "active": true,
310
+ "entity": "player",
311
+ "event": "update",
312
+ "type": "all",
313
+ "clean": "90d"
314
+ }
315
+ ```
316
+
317
+ ### 9.2 Auditar exclusões numa coleção de negócio feitas pelo Studio
318
+
319
+ ```json
320
+ {
321
+ "title": "Exclusões em pedidos (admin)",
322
+ "active": true,
323
+ "entity": "order__c",
324
+ "event": "delete",
325
+ "type": "studio",
326
+ "clean": "180d"
327
+ }
328
+ ```
329
+
330
+ ### 9.3 Anti-pattern — o que NÃO fazer
331
+
332
+ ```json
333
+ {
334
+ "title": "Auditar criação de desafios",
335
+ "entity": "challenge",
336
+ "event": "create"
337
+ }
338
+ ```
339
+
340
+ Por quê está errado:
341
+
342
+ - `entity:"challenge"` → **não há gatilho** de auditoria para challenge; a config nunca gera logs.
343
+ - `clean` omitido → assume `"30d"` silenciosamente (defina explicitamente para não perder logs antes do esperado).
344
+ - `type` omitido → assume `"all"` (ok, mas seja deliberado).
345
+
346
+ ---
347
+
348
+ ## Checklist de Configuração
349
+
350
+ - [ ] `entity` é `player` **ou** uma coleção manipulada via `database` (senão a config é inerte).
351
+ - [ ] `event` ∈ `create` | `update` | `delete`.
352
+ - [ ] `active: true`.
353
+ - [ ] `type` escolhido deliberadamente (`studio` | `player` | `all`) conforme a origem a rastrear.
354
+ - [ ] `clean` definido **explicitamente** (não confie no default `30d`); ciente da limpeza por cron.
355
+ - [ ] `_id`/`title` legíveis (`_id` saneado para `[A-Za-z0-9_]` no Studio).
356
+ - [ ] Consulta de logs planejada via `database/audit_log/aggregate` (`$match audit/time`, `$sort time:-1`).
357
+ - [ ] Ciente de que `DELETE` da config apaga **todos** os logs em cascata.
@@ -1,5 +1,5 @@
1
1
  import { FunifierConfig } from "./config";
2
- import { Trigger, Scheduler, Aggregate, Widget, CustomPage, PublicEndpoint, ChallengeAggregate, AiKnowledge, AuthModule, Websocket, Action, Challenge, Point, Level, LevelConfig, Leaderboard, LeaderboardEntry, Quiz, Question, VirtualGoodCatalog, VirtualGoodItem, Folder, FolderContent, FolderContentType, FolderLog, Role, PrincipalRole } from "./types";
2
+ import { Trigger, Scheduler, Aggregate, Widget, CustomPage, PublicEndpoint, ChallengeAggregate, AiKnowledge, AuthModule, Websocket, Action, Challenge, Point, Level, LevelConfig, Leaderboard, LeaderboardEntry, Quiz, Question, VirtualGoodCatalog, VirtualGoodItem, Folder, FolderContent, FolderContentType, FolderLog, Role, PrincipalRole, Audit } from "./types";
3
3
  export declare function serializeToFunifierQuery(filter: Record<string, any>): string;
4
4
  export declare function createAPIClient(config: FunifierConfig): {
5
5
  listTriggers: () => Promise<Trigger[]>;
@@ -100,6 +100,9 @@ export declare function createAPIClient(config: FunifierConfig): {
100
100
  listActions: () => Promise<Action[]>;
101
101
  saveAction: (payload: Action) => Promise<Action>;
102
102
  deleteAction: (id: string) => Promise<any>;
103
+ listAudits: () => Promise<Audit[]>;
104
+ saveAudit: (payload: Audit) => Promise<Audit>;
105
+ deleteAudit: (id: string) => Promise<any>;
103
106
  listChallenges: () => Promise<Challenge[]>;
104
107
  saveChallenge: (payload: Challenge) => Promise<Challenge>;
105
108
  deleteChallenge: (id: string) => Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/core/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/core/api-client.ts"],"names":[],"mappings":"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"}
@@ -744,6 +744,36 @@ function createAPIClient(config) {
744
744
  throw new Error("Failed to delete action: " + extractErrorMessage(e));
745
745
  }
746
746
  },
747
+ // --- Audit ---
748
+ // No list REST endpoint exists; read all configs via aggregate on the "audit" collection.
749
+ listAudits: async () => {
750
+ try {
751
+ const response = await axiosClient.post("/database/audit/aggregate", []);
752
+ return response.data;
753
+ }
754
+ catch (e) {
755
+ console.error(e);
756
+ return [];
757
+ }
758
+ },
759
+ saveAudit: async (payload) => {
760
+ try {
761
+ const response = await axiosClient.post(constants_1.Endpoints.AUDIT, payload);
762
+ return response.data;
763
+ }
764
+ catch (e) {
765
+ throw new Error("Failed to save audit: " + extractErrorMessage(e));
766
+ }
767
+ },
768
+ deleteAudit: async (id) => {
769
+ try {
770
+ const response = await axiosClient.delete(`${constants_1.Endpoints.AUDIT}/${id}`);
771
+ return response.data;
772
+ }
773
+ catch (e) {
774
+ throw new Error("Failed to delete audit: " + extractErrorMessage(e));
775
+ }
776
+ },
747
777
  // --- Challenges ---
748
778
  listChallenges: async () => {
749
779
  try {