funifier-mcp 0.2.26 → 0.2.28

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 (181) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +4 -1
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/mcp/bundle.js +119 -93
  66. package/dist/mcp/check-update.d.ts +5 -0
  67. package/dist/mcp/check-update.d.ts.map +1 -1
  68. package/dist/mcp/check-update.js +21 -10
  69. package/dist/mcp/check-update.js.map +1 -1
  70. package/dist/mcp/check-update.test.d.ts +2 -0
  71. package/dist/mcp/check-update.test.d.ts.map +1 -0
  72. package/dist/mcp/check-update.test.js +33 -0
  73. package/dist/mcp/check-update.test.js.map +1 -0
  74. package/dist/mcp/index.js +2 -2
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/mcp/prompts/templates.d.ts.map +1 -1
  77. package/dist/mcp/prompts/templates.js +35 -0
  78. package/dist/mcp/prompts/templates.js.map +1 -1
  79. package/dist/mcp/resources/documentation.d.ts +1 -1
  80. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  81. package/dist/mcp/resources/documentation.js +39 -3
  82. package/dist/mcp/resources/documentation.js.map +1 -1
  83. package/dist/mcp/tools/connect.d.ts.map +1 -1
  84. package/dist/mcp/tools/connect.js +18 -8
  85. package/dist/mcp/tools/connect.js.map +1 -1
  86. package/dist/mcp/tools/database.d.ts.map +1 -1
  87. package/dist/mcp/tools/database.js +59 -47
  88. package/dist/mcp/tools/database.js.map +1 -1
  89. package/dist/mcp/tools/database.test.js +2 -2
  90. package/dist/mcp/tools/database.test.js.map +1 -1
  91. package/dist/mcp/tools/delete.d.ts.map +1 -1
  92. package/dist/mcp/tools/delete.js +13 -3
  93. package/dist/mcp/tools/delete.js.map +1 -1
  94. package/dist/mcp/tools/execute.d.ts.map +1 -1
  95. package/dist/mcp/tools/execute.js +20 -9
  96. package/dist/mcp/tools/execute.js.map +1 -1
  97. package/dist/mcp/tools/folder.d.ts.map +1 -1
  98. package/dist/mcp/tools/folder.js +22 -12
  99. package/dist/mcp/tools/folder.js.map +1 -1
  100. package/dist/mcp/tools/get.d.ts.map +1 -1
  101. package/dist/mcp/tools/get.js +16 -6
  102. package/dist/mcp/tools/get.js.map +1 -1
  103. package/dist/mcp/tools/index.d.ts +1 -1
  104. package/dist/mcp/tools/index.d.ts.map +1 -1
  105. package/dist/mcp/tools/index.js +28 -1
  106. package/dist/mcp/tools/index.js.map +1 -1
  107. package/dist/mcp/tools/list.d.ts.map +1 -1
  108. package/dist/mcp/tools/list.js +38 -14
  109. package/dist/mcp/tools/list.js.map +1 -1
  110. package/dist/mcp/tools/logs.d.ts.map +1 -1
  111. package/dist/mcp/tools/logs.js +15 -5
  112. package/dist/mcp/tools/logs.js.map +1 -1
  113. package/dist/mcp/tools/save.d.ts.map +1 -1
  114. package/dist/mcp/tools/save.js +14 -4
  115. package/dist/mcp/tools/save.js.map +1 -1
  116. package/dist/mcp/tools/save.test.js +3 -3
  117. package/dist/mcp/tools/save.test.js.map +1 -1
  118. package/dist/mcp/tools/search-docs.d.ts +3 -0
  119. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  120. package/dist/mcp/tools/search-docs.js +102 -0
  121. package/dist/mcp/tools/search-docs.js.map +1 -0
  122. package/package.json +6 -2
  123. package/skills/acquire-funifier-knowledge/SKILL.md +155 -0
  124. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  125. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  126. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  127. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  128. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  129. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  130. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +86 -0
  131. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  132. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  133. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  134. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  135. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  136. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  137. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  138. package/skills/funifier/SKILL.md +88 -0
  139. package/skills/funifier/references/configure-security.md +96 -0
  140. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  141. package/skills/funifier/references/create-aggregate.md +144 -0
  142. package/skills/funifier/references/create-challenge.md +116 -0
  143. package/skills/funifier/references/create-competition.md +98 -0
  144. package/skills/funifier/references/create-crossword.md +574 -0
  145. package/skills/funifier/references/create-custom-object.md +91 -0
  146. package/skills/funifier/references/create-custom-page.md +135 -0
  147. package/skills/funifier/references/create-folder.md +104 -0
  148. package/skills/funifier/references/create-lastmile.md +643 -0
  149. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  150. package/skills/funifier/references/create-level.md +94 -0
  151. package/skills/funifier/references/create-lottery.md +913 -0
  152. package/skills/funifier/references/create-mystery.md +769 -0
  153. package/skills/funifier/references/create-notification.md +75 -0
  154. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  155. package/skills/funifier/references/create-quiz.md +98 -0
  156. package/skills/funifier/references/create-scheduler.md +141 -0
  157. package/skills/funifier/references/create-story.md +636 -0
  158. package/skills/funifier/references/create-swap.md +95 -0
  159. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  160. package/skills/funifier/references/create-virtual-good.md +96 -0
  161. package/skills/funifier/references/create-webhook.md +72 -0
  162. package/skills/funifier/references/create-websocket.md +71 -0
  163. package/skills/funifier/references/create-widget.md +76 -0
  164. package/skills/funifier/references/debug.md +87 -0
  165. package/skills/funifier/references/help.md +81 -0
  166. package/skills/funifier/references/implement-frontend.md +106 -0
  167. package/skills/funifier/references/import-csv.md +75 -0
  168. package/skills/funifier/references/manage-player.md +82 -0
  169. package/skills/funifier/references/manage-team.md +76 -0
  170. package/skills/funifier/references/upload-file.md +91 -0
  171. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  172. package/skills/funifier-create-challenge/SKILL.md +0 -88
  173. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  174. package/skills/funifier-create-level/SKILL.md +0 -87
  175. package/skills/funifier-create-quiz/SKILL.md +0 -87
  176. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  177. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  178. package/skills/funifier-debug/SKILL.md +0 -92
  179. package/skills/funifier-help/SKILL.md +0 -86
  180. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  181. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,40 +1,618 @@
1
- # Backup (Backup)
1
+ # `backup`
2
2
 
3
3
  **Acesso Studio:** `/studio/backup`
4
4
  **API Endpoint:** `/v3/backup`
5
+ **Coleção MongoDB:** `backup` (configurações) e `backup_log` (logs de execução)
5
6
 
6
- ## O que é
7
+ > Documentação de engenharia reversa baseada **exclusivamente** no código-fonte do projeto `funifier-service` (`com.funifier.engine.backup.*` e `com.funifier.rest.v3.rest.BackupRest`). Onde o comportamento real difere do esperado por convenção REST ou do que o schema sugere, isso está marcado explicitamente.
7
8
 
8
- Backup e remoção controlada de dados antigos. Permite criar rotinas automáticas de backup de registros importantes, além de remover dados antigos de maneira controlada para manter a performance da plataforma.
9
+ ---
9
10
 
10
- ## Quando usar
11
+ ## 1. Visão Geral
11
12
 
12
- - Para fazer backup de logs antigos antes de atualizações
13
- - Para remover registros históricos e otimizar consultas
14
- - Para manter performance em grandes volumes de dados
13
+ O módulo `backup` move/copia documentos de uma coleção MongoDB para outra coleção, opcionalmente aplicando um pipeline de **aggregate** de transformação e opcionalmente **removendo os documentos originais** após a cópia. É o mecanismo usado para arquivar dados antigos (logs, achievements históricos, etc.) e aliviar coleções de alto volume.
15
14
 
16
- ## Checklist de Configuração no Studio
15
+ Papel arquitetural:
17
16
 
18
- - [ ] Definir quais coleções serão backupeadas
19
- - [ ] Configurar frequência do backup
20
- - [ ] Definir critério de remoção de dados antigos
17
+ - Uma **configuração de backup** (documento na coleção `backup`) descreve *o que* copiar (`collection` + `match`), *como transformar* (`aggregate`), *para onde* (`backupCollectionName`), e *o que fazer com a origem* (`deleteOriginalDataAfterBackup`).
18
+ - A execução é **assíncrona e manual/on-demand**: disparada via endpoint `GET /v3/backup/{id}/execute`, processada por uma thread dedicada por tenant (`BackupAsyncProcessor`).
19
+ - Cada execução gera um documento de **log** (coleção `backup_log`) que registra status, contagens e timestamps.
21
20
 
22
- ## API Endpoints
21
+ Relação com outros módulos:
23
22
 
24
- ### Listar Backups
25
- **Método:** GET
26
- **Endpoint:** `/v3/backup`
23
+ - É um quase-clone estrutural do módulo `compact` (`com.funifier.engine.compact.CompactManager`). Ambos seguem o mesmo padrão (config + log + thread assíncrona por tenant). A diferença conceitual é que `backup` foca em copiar/transformar para outra coleção; `compact` foca em compactar.
24
+ - Integra com o módulo `trigger`: ao **finalizar com sucesso**, dispara o evento `after_finish` sobre a entidade `backup_log` (ver Seção 6).
25
+ - É multi-tenant: cada `apiKey` tem sua própria instância de `BackupManager` e sua própria thread/fila assíncrona (ver Seção 5).
27
26
 
28
- ### Criar Backup
29
- **Método:** POST
30
- **Endpoint:** `/v3/backup`
27
+ > **Atenção:** Diferente do que a documentação anterior afirmava, o módulo **não** possui agendamento automático (cron), **não** possui flag `active`, e **não** possui endpoint de listagem. Esses recursos estão ausentes do código (ver Seção 7).
31
28
 
32
- ### Deletar Backup
33
- **Método:** DELETE
34
- **Endpoint:** `/v3/backup/:id`
29
+ ---
35
30
 
36
- ## Validações e Testes
31
+ ## 2. Arquitetura e Fluxos
37
32
 
38
- - [ ] Backup é executado com sucesso
39
- - [ ] Dados antigos são removidos conforme critério
40
- - [ ] Performance melhora após limpeza
33
+ ### 2.1 Pipeline principal disparo (`execute`)
34
+
35
+ Fluxo síncrono executado quando o cliente chama `GET /v3/backup/{id}/execute` (`BackupRest.execute` → `BackupManager.execute`):
36
+
37
+ 1. **[Entrada]** `BackupRest.execute(authBean, id)` resolve o tenant via `FrontController.getInstance(apiKey)` e chama `getBackupManager().execute(id)`.
38
+ 2. **[Lookup config]** `execute(id)` busca a config na coleção `backup`: `findOne("{_id:#}", id)`. Se **não existir**, retorna `null` (sem erro) e o fluxo termina.
39
+ 3. **[Verificação de execução em andamento]** Busca em `backup_log` por um log do mesmo backup **sem o campo `finish`**: `findOne("{backup:#, finish:{$exists:false}}", id)`.
40
+ - Se já existir um log em andamento → **retorna esse log existente sem enfileirar nada** (idempotência / lock lógico). Nenhuma nova execução é iniciada.
41
+ 4. **[Criação do log]** Se não houver log em andamento, cria um novo `BackupLog` com `status="started"`, `start=now`, `total={original:0, backuped:0}` e salva em `backup_log`.
42
+ 5. **[Enfileiramento]** Chama `async.track(log)` que adiciona o log a uma fila em memória (`LinkedBlockingQueue`).
43
+ 6. **[Retorno]** Retorna o log recém-criado (status `"started"`) imediatamente — o trabalho pesado roda em background.
44
+
45
+ > **Síncrono vs assíncrono:** os passos 1–6 são síncronos e rápidos (apenas criam o log e enfileiram). Todo o trabalho de cópia (passos da Seção 2.2) roda **assíncronamente** na thread `Funifier-Backup-Async-<apiKey>`.
46
+
47
+ ### 2.2 Pipeline principal — execução assíncrona (`asyncExecute`)
48
+
49
+ A thread `BackupAsyncProcessor.run()` faz polling da fila a cada 1s (`Thread.sleep(1000)` quando vazia). Ao retirar um log, chama `BackupManager.asyncExecute(log)`:
50
+
51
+ ```
52
+ [1] re-busca a config backup por log.backup -> findOne("{_id:#}", log.backup)
53
+ se config == null -> nada acontece (log fica "started" para sempre)
54
+ [2] source = coleção backup.collection
55
+ [3] log.total.original = source.count(match) -> status="count items", save(log)
56
+ [4] monta pipeline aggregate:
57
+ [{$match: <backup.match>}, ...<backup.aggregate>] -> status="group data", save(log)
58
+ (allowDiskUse=true)
59
+ [5] bkp = coleção backup.backupCollectionName
60
+ [6] se NÃO incremental -> bkp.drop() (apaga a coleção de destino inteira)
61
+ [7] para cada doc do aggregate:
62
+ bkp.save(doc); backuped++
63
+ a cada 50 docs -> status="backuped saved : N", save(log)
64
+ [8] se deleteOriginalDataAfterBackup:
65
+ status="removing original", source.remove(match), save(log)
66
+ [9] log.total.backuped = backuped
67
+ status="finished"; log.finish = now; save(log)
68
+ [10] dispara trigger after_finish sobre backup_log
69
+ ```
70
+
71
+ Em caso de **qualquer exceção** durante os passos acima:
72
+
73
+ ```
74
+ catch(Exception e):
75
+ e.printStackTrace()
76
+ log.status = "error"
77
+ log.finish = now
78
+ log.message = e.getMessage()
79
+ save(log)
80
+ -> o trigger after_finish NÃO é disparado
81
+ ```
82
+
83
+ ### Fluxo de execução — `execute` → `asyncExecute`
84
+
85
+ ```mermaid
86
+ flowchart TD
87
+ A["GET /v3/backup/{id}/execute"] --> B["BackupManager.execute(id)"]
88
+ B --> C{config backup existe?}
89
+ C -- não --> Z["retorna null (HTTP 200, body null)"]
90
+ C -- sim --> D{"existe log sem finish?"}
91
+ D -- sim --> E["retorna log em andamento\n(não re-enfileira)"]
92
+ D -- não --> F["cria BackupLog status=started\nsalva em backup_log"]
93
+ F --> G["async.track(log)\n(fila em memória)"]
94
+ G --> H["retorna log status=started"]
95
+
96
+ subgraph Thread["Thread Funifier-Backup-Async-apiKey"]
97
+ I["poll fila (1s)"] --> J["asyncExecute(log)"]
98
+ J --> K["count items"]
99
+ K --> L["monta aggregate\n[{$match}, ...aggregate]"]
100
+ L --> M{incremental?}
101
+ M -- não --> N["bkp.drop()"]
102
+ M -- sim --> O["mantém destino"]
103
+ N --> P["save cada doc\n(log a cada 50)"]
104
+ O --> P
105
+ P --> Q{deleteOriginalDataAfterBackup?}
106
+ Q -- sim --> R["source.remove(match)"]
107
+ Q -- não --> S["mantém origem"]
108
+ R --> T["status=finished, finish=now"]
109
+ S --> T
110
+ T --> U["trigger after_finish"]
111
+ end
112
+ G -.enfileira.-> I
113
+ ```
114
+
115
+ ### 2.3 Interação entre componentes
116
+
117
+ ```mermaid
118
+ sequenceDiagram
119
+ participant C as Cliente
120
+ participant R as BackupRest
121
+ participant M as BackupManager
122
+ participant Q as BackupAsyncProcessor (thread)
123
+ participant DB as MongoDB
124
+ participant T as TriggerManager
125
+
126
+ C->>R: GET /v3/backup/{id}/execute
127
+ R->>M: execute(id)
128
+ M->>DB: findOne backup {_id}
129
+ M->>DB: findOne backup_log {backup, finish:$exists:false}
130
+ alt log em andamento existe
131
+ M-->>R: log existente
132
+ else
133
+ M->>DB: save(BackupLog status=started)
134
+ M->>Q: track(log)
135
+ M-->>R: log (started)
136
+ end
137
+ R-->>C: 200 + BackupLog
138
+
139
+ Note over Q: polling assíncrono (1s)
140
+ Q->>M: asyncExecute(log)
141
+ M->>DB: count(match) / aggregate / save / remove
142
+ M->>DB: save(log status=finished)
143
+ M->>T: execute(backup, log, "backup_log", "after_finish")
144
+ ```
145
+
146
+ > Não há transações MongoDB. As atualizações de `backup_log`, as inserções na coleção de destino e a remoção da origem são operações independentes e **não atômicas**. Uma falha no meio do passo [7] deixa a coleção de destino parcialmente preenchida e o log com `status="error"`, sem rollback.
147
+
148
+ ---
149
+
150
+ ## 3. Estrutura dos Objetos
151
+
152
+ ### 3.1 `Backup` — documento de configuração (coleção `backup`)
153
+
154
+ Mapeado por `com.funifier.engine.backup.Backup` (`@Data @Builder @JsonIgnoreProperties(ignoreUnknown=true)`).
155
+
156
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
157
+ | ------------------------------ | ------- | ---------------------------- | ----------- | --------- |
158
+ | `_id` | String | auto (`Guid.shortTimeMillis`) | — | Gerado no `save` quando ausente/vazio. Time-based curto. Se enviado, é respeitado (upsert). |
159
+ | `title` | String | null | Não | Rótulo descritivo livre. **Não é validado nem usado pela lógica de execução.** |
160
+ | `collection` | String | null | **Sim** | Coleção de **origem** dos dados. Sem ele, `save` é silenciosamente ignorado. |
161
+ | `match` | String | null | **Sim** | Filtro MongoDB (JSON/BSON estendido Funifier) que seleciona quais documentos copiar. Usado em `count`, `aggregate $match` e `remove`. |
162
+ | `aggregate` | String | null | **Sim** | Pipeline de aggregate (array JSON de estágios) aplicado **após** o `$match`. Transforma os dados antes do backup. |
163
+ | `backupCollectionName` | String | `<collection>_bkp` | Não | Coleção de **destino**. Default automático no `save` quando vazio. |
164
+ | `incremental` | boolean | `false` | Não | `false` → destino é dropado antes da inserção. `true` → destino é preservado (ver Seção 5). |
165
+ | `deleteOriginalDataAfterBackup`| boolean | `false` | Não | `true` → remove da origem todos os documentos que casam com `match`, **após** a cópia. |
166
+ | `created` | Date | auto (na criação) | — | Definido **apenas** quando `_id` é novo. Ver footgun de full-replace na Seção 5. |
167
+ | `updated` | Date | auto (todo save) | — | Sempre redefinido para `now` no `save`. |
168
+
169
+ **Campos legados / removidos (comentados no código — NÃO existem no runtime):**
170
+
171
+ - `active` (`boolean`) — comentado em `Backup.java` linha 28. Indicaria se a rotina está ativa. **Não implementado.**
172
+ - `cron` (`String`) — comentado em `Backup.java` linha 29. Indicaria expressão de agendamento automático. **Não implementado** (não há scheduler).
173
+
174
+ **Comportamento de campos:**
175
+
176
+ - Por `@JsonIgnoreProperties(ignoreUnknown=true)`, **qualquer campo extra enviado no request é silenciosamente descartado** (não persiste, não gera erro).
177
+ - `title` é puramente cosmético: nunca é lido pela lógica de `execute`/`asyncExecute`.
178
+
179
+ ### 3.2 `BackupLog` — documento de log de execução (coleção `backup_log`)
180
+
181
+ Mapeado por `com.funifier.engine.backup.BackupLog`.
182
+
183
+ | Campo | Tipo | Padrão | Descrição |
184
+ | --------- | ----------------------- | ---------- | --------- |
185
+ | `_id` | String | auto | `Guid.shortTimeMillis`. |
186
+ | `backup` | String | — | `_id` da config de backup associada. |
187
+ | `status` | String | `"started"`| Enum implícito (ver 3.3). |
188
+ | `start` | Date | now | Início da execução (definido na criação do log). |
189
+ | `finish` | Date | ausente | Definido apenas ao terminar (`finished`) ou falhar (`error`). **Sua ausência é o que marca um log "em andamento".** |
190
+ | `total` | `Total{original,backuped}` | `{0,0}` | Contagens (ver abaixo). |
191
+ | `message` | String | null | Preenchido **somente** no caminho de erro (`e.getMessage()`). |
192
+ | `extra` | `HashMap<String,Object>`| null | **Declarado mas nunca escrito** por `BackupManager`. Campo morto (ver Seção 7). |
193
+
194
+ Subobjeto `Total`:
195
+
196
+ | Campo | Tipo | Descrição |
197
+ | ---------- | ---- | --------- |
198
+ | `original` | long | Contagem de documentos na origem que casam com `match` (definido em "count items"). |
199
+ | `backuped` | long | Contagem de documentos efetivamente salvos no destino (definido ao finalizar). |
200
+
201
+ > `original` e `backuped` são `long` primitivos → valem `0` por padrão e **não** são removidos por `toJsonRemoveNullFields` (0 não é null). Portanto o JSON de um log sempre traz `total:{original:N, backuped:M}`.
202
+
203
+ ### 3.3 Ciclo de vida do `status` (enum implícito)
204
+
205
+ `status` é uma `String` livre, mas o código só atribui sete valores. Os transientes (`count items`, `group data`, `backuped saved : N`, `removing original`) são gravados em `backup_log` durante o processamento — o cliente pode observá-los relendo o log diretamente do Mongo (não há endpoint REST; ver Seção 7).
206
+
207
+ | Valor | Quando | Terminal? |
208
+ | ---------------------- | ------ | --------- |
209
+ | `started` | log criado, antes do processamento | não |
210
+ | `count items` | contando docs da origem | não |
211
+ | `group data` | pipeline aggregate montado | não |
212
+ | `backuped saved : N` | a cada 50 documentos salvos (N = total acumulado) | não |
213
+ | `removing original` | removendo docs da origem (só se `deleteOriginalDataAfterBackup`) | não |
214
+ | `finished` | concluído com sucesso (`finish` definido) | **sim** |
215
+ | `error` | exceção capturada (`finish` + `message` definidos) | **sim** |
216
+
217
+ ```mermaid
218
+ stateDiagram-v2
219
+ [*] --> started
220
+ started --> count_items: count items
221
+ count_items --> group_data: group data
222
+ group_data --> backuped_saved: backuped saved : N
223
+ backuped_saved --> backuped_saved: a cada 50 docs
224
+ backuped_saved --> removing_original: se deleteOriginal
225
+ backuped_saved --> finished: se não deleteOriginal
226
+ removing_original --> finished
227
+ finished --> [*]
228
+ count_items --> error: exceção
229
+ group_data --> error: exceção
230
+ backuped_saved --> error: exceção
231
+ removing_original --> error: exceção
232
+ error --> [*]
233
+ ```
234
+
235
+ > **Estado órfão não modelado:** se a config for apagada entre `execute` e `asyncExecute`, o passo [1] encontra `config == null` e a função retorna sem alterar o log. O log permanece em `started` indefinidamente (sem `finish`), bloqueando futuras execuções (ver Seção 7).
236
+
237
+ ### 3.4 Técnicas de jogo (`techniques`)
238
+
239
+ **Não aplicável.** O módulo `backup` é infraestrutura de dados e não possui códigos GT (game techniques).
240
+
241
+ ---
242
+
243
+ ## 4. Endpoints
244
+
245
+ Todos sob `@Path("v3/backup")`, definidos em `BackupRest.java`. Produzem `application/json; charset=UTF-8`.
246
+
247
+ ### `POST /v3/backup`
248
+
249
+ | Aspecto | Detalhe |
250
+ | --------------------- | ------- |
251
+ | Finalidade | Criar **ou atualizar** uma configuração de backup. |
252
+ | Handler | `BackupRest.insert` → `BackupManager.save` |
253
+ | Autenticação | `Authorization` (Bearer / Basic / Studio) ou `api_key` |
254
+ | Full replace ou patch | **Full replace** — `col.save(obj)` substitui o documento inteiro por `_id`. |
255
+ | Status de sucesso | `201 CREATED` |
256
+
257
+ **Comportamento real (importante):**
258
+
259
+ - **Upsert disfarçado de "insert":** se o body trouxer um `_id` existente, o documento é **totalmente substituído** (não é patch). Campos omitidos no body são **perdidos**.
260
+ - **Validação silenciosa:** `save` só persiste se `collection`, `match` e `aggregate` forem todos não-nulos. Se algum faltar, **nada é salvo, mas a resposta ainda é `201 CREATED`** com o body recebido (sem `_id`/`created`/`updated`). O cliente é levado a crer que salvou.
261
+ - Defaults aplicados no save: `_id`, `created` (só em novos), `updated` (sempre), `backupCollectionName` (= `collection + "_bkp"` se vazio).
262
+ - A resposta usa `toJsonRemoveNullFields` → campos nulos são omitidos.
263
+
264
+ **Exemplo de request:**
265
+
266
+ ```json
267
+ POST /v3/backup
268
+ {
269
+ "title": "Arquivar achievements antigos",
270
+ "collection": "achievement",
271
+ "match": { "time": { "$lt": { "$date": "2024-01-01T00:00:00Z" } } },
272
+ "aggregate": [ { "$project": { "player": 1, "type": 1, "time": 1 } } ],
273
+ "backupCollectionName": "achievement_archive",
274
+ "incremental": true,
275
+ "deleteOriginalDataAfterBackup": false
276
+ }
277
+ ```
278
+
279
+ **Resposta (201):**
280
+
281
+ ```json
282
+ {
283
+ "_id": "Xa3Bc9",
284
+ "title": "Arquivar achievements antigos",
285
+ "collection": "achievement",
286
+ "match": "...",
287
+ "aggregate": "...",
288
+ "backupCollectionName": "achievement_archive",
289
+ "incremental": true,
290
+ "deleteOriginalDataAfterBackup": false,
291
+ "created": "2026-05-20T12:00:00Z",
292
+ "updated": "2026-05-20T12:00:00Z"
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ### `GET /v3/backup/{id}`
299
+
300
+ | Aspecto | Detalhe |
301
+ | ------------ | ------- |
302
+ | Finalidade | Buscar uma configuração de backup por `_id`. |
303
+ | Handler | `BackupRest.find` → `BackupManager.find` |
304
+ | Autenticação | `Authorization` / `api_key` |
305
+ | Status | `200 OK` |
306
+
307
+ **Path params:**
308
+
309
+ | Param | Tipo | Descrição |
310
+ | ----- | ------ | --------- |
311
+ | `id` | String | `_id` da config. |
312
+
313
+ **Comportamento real:** se o `_id` não existir, `find` retorna `null` e a resposta é `200 OK` com body nulo/vazio (sem 404).
314
+
315
+ ---
316
+
317
+ ### `DELETE /v3/backup/{id}`
318
+
319
+ | Aspecto | Detalhe |
320
+ | ------------ | ------- |
321
+ | Finalidade | Remover a configuração **e todos os seus logs**. |
322
+ | Handler | `BackupRest.delete` → `BackupManager.delete` |
323
+ | Autenticação | `Authorization` / `api_key` |
324
+ | Status | `204 NO_CONTENT` |
325
+
326
+ **Path params:** `id` (String) — `_id` da config.
327
+
328
+ **Comportamento real (cascade):** além de remover o doc da coleção `backup`, executa `remove("{backup:#}", id)` na coleção `backup_log`, **apagando todo o histórico de execuções** desse backup. É idempotente: deletar um id inexistente também retorna `204`.
329
+
330
+ ---
331
+
332
+ ### `GET /v3/backup/{id}/execute`
333
+
334
+ | Aspecto | Detalhe |
335
+ | ------------ | ------- |
336
+ | Finalidade | **Disparar** uma execução de backup (assíncrona). |
337
+ | Handler | `BackupRest.execute` → `BackupManager.execute` |
338
+ | Autenticação | `Authorization` / `api_key` |
339
+ | Status | `200 OK` |
340
+
341
+ **Path params:** `id` (String) — `_id` da config.
342
+
343
+ **Comportamento real — esta é uma operação NÃO-RESTful:**
344
+
345
+ - **`GET` que muta estado:** apesar do verbo `GET`, este endpoint **cria um documento** em `backup_log` e enfileira trabalho. Não é seguro/idempotente no sentido HTTP, e qualquer cache/prefetch de GET pode disparar backups acidentais.
346
+ - Retorna **imediatamente** o `BackupLog` (status `"started"`) — não espera o backup terminar.
347
+ - Se já houver um log em andamento (sem `finish`), retorna esse log **sem disparar nova execução** (proteção contra concorrência).
348
+ - Se o `_id` não existir, `execute` retorna `null` → resposta `200 OK` com body nulo (sem erro).
349
+
350
+ **Resposta típica (200):**
351
+
352
+ ```json
353
+ {
354
+ "_id": "Lm7Qz2",
355
+ "backup": "Xa3Bc9",
356
+ "status": "started",
357
+ "start": "2026-05-20T12:05:00Z",
358
+ "total": { "original": 0, "backuped": 0 }
359
+ }
360
+ ```
361
+
362
+ > Não há endpoint para **listar** backups, nem para **consultar o `backup_log`**. Ver Seção 7 e Seção 9.
363
+
364
+ ---
365
+
366
+ ## 5. Regras de Negócio
367
+
368
+ Regras presentes no código que **não** estão no schema:
369
+
370
+ 1. **Obrigatoriedade efetiva (não declarada):** `collection`, `match` e `aggregate` são obrigatórios na prática — `save` no-opa silenciosamente sem eles (`BackupManager.save` linha 41). `title` e `backupCollectionName` são opcionais.
371
+
372
+ 2. **`aggregate` é um array de estágios, não um objeto:** em `asyncExecute` ele é desserializado como `List` (`JsonUtil.fromJson(backup.aggregate, List.class)`). O `$match` é montado **separadamente** a partir de `backup.match` e prefixado ao pipeline. O pipeline efetivo é `[{$match: <match>}, ...<aggregate>]`. Repetir o `$match` dentro de `aggregate` é redundante.
373
+
374
+ 3. **`incremental` = "não dropar o destino":** quando `false`, `bkp.drop()` apaga a coleção de destino inteira antes de inserir (full refresh). Quando `true`, o destino é preservado e os documentos são inseridos via `bkp.save(o)`.
375
+ - **Dedup por `_id`:** em modo incremental, `save` faz upsert pelo `_id` do documento. Se o pipeline `aggregate` **projetar o `_id` para fora** (ou gerar `_id` novo via `$group`), a deduplicação quebra e **cada execução insere duplicatas**.
376
+
377
+ 4. **Remoção da origem é por `match`, não pelos IDs copiados:** quando `deleteOriginalDataAfterBackup=true`, a remoção usa o **mesmo `match`** (`source.remove(match)`), não a lista de documentos efetivamente copiados. Se novos documentos passarem a casar com `match` entre a cópia e a remoção, eles serão **removidos sem terem sido copiados** (janela de inconsistência por não haver transação).
378
+
379
+ 5. **Full-replace apaga `created` em updates:** como o `POST` faz `col.save(obj)` (substituição total) e `created` só é setado no ramo de `_id` novo, **atualizar uma config sem reenviar `created` zera/remove o campo**. Sempre reenvie o documento completo (incluindo `created`) ao atualizar.
380
+
381
+ 6. **Lock lógico de execução única por backup:** a checagem `{finish:{$exists:false}}` garante no máximo **uma execução em andamento por config**. Um log preso sem `finish` bloqueia novas execuções indefinidamente (ver Seção 7).
382
+
383
+ 7. **Isolamento multi-tenant:** cada `apiKey` resolve para uma `ManagerFactory` própria (`FrontController.getInstance(apiKey)`), com seu próprio `BackupManager`, fila e thread (`Funifier-Backup-Async-<apiKey>`). As coleções `backup`/`backup_log` são por banco de tenant.
384
+
385
+ 8. **Consistência eventual / sem transação:** count, aggregate-insert e remove são passos independentes. Não há rollback. Falha parcial deixa estado intermediário (destino parcial + `status=error`).
386
+
387
+ ---
388
+
389
+ ## 6. Comportamentos Automáticos
390
+
391
+ | Comportamento | Trigger | Impacto | Persistência |
392
+ | ------------- | ------- | ------- | ------------ |
393
+ | Geração de `_id` | `save` com `_id` vazio | Cria `_id` time-based (`Guid.shortTimeMillis`) | coleção `backup` |
394
+ | `created`/`updated` | `save` | `created` só em novos; `updated` sempre | coleção `backup` |
395
+ | Default de destino | `save` com `backupCollectionName` vazio | Define `<collection>_bkp` | coleção `backup` |
396
+ | No-op silencioso | `save` sem `collection`/`match`/`aggregate` | Nada é salvo; resposta ainda 201 | nenhuma |
397
+ | Cascade de logs | `DELETE /{id}` | Remove todos os `backup_log` do backup | coleção `backup_log` |
398
+ | Drop do destino | `asyncExecute` com `incremental=false` | Apaga a coleção de destino inteira | coleção de destino |
399
+ | Remoção da origem | `asyncExecute` com `deleteOriginalDataAfterBackup=true` | `remove(match)` na origem | coleção de origem |
400
+ | Atualização de progresso | `asyncExecute`, a cada 50 docs | Reescreve `backup_log.status` | coleção `backup_log` |
401
+ | Trigger `after_finish` | `asyncExecute` ao concluir **com sucesso** | Dispara cadeia de triggers | conforme triggers |
402
+
403
+ ### Fluxo de behaviors automáticos no `save`
404
+
405
+ ```mermaid
406
+ flowchart TD
407
+ A["save(obj)"] --> B{"collection, match e aggregate\ntodos != null?"}
408
+ B -- não --> X["no-op silencioso\n(nada persiste)"]
409
+ B -- sim --> C{"_id vazio?"}
410
+ C -- sim --> D["_id = Guid.shortTimeMillis()\ncreated = now"]
411
+ C -- não --> E["mantém _id\n(created NÃO é redefinido)"]
412
+ D --> F["updated = now"]
413
+ E --> F
414
+ F --> G{"backupCollectionName vazio?"}
415
+ G -- sim --> H["backupCollectionName = collection + '_bkp'"]
416
+ G -- não --> I["mantém"]
417
+ H --> J["col.save(obj) — full replace"]
418
+ I --> J
419
+ ```
420
+
421
+ ### Trigger `after_finish`
422
+
423
+ Ao concluir com sucesso, `asyncExecute` monta um `TriggerContext` (com `manager` e `extra.backup = <config>`) e chama:
424
+
425
+ ```java
426
+ manager.getTriggerManager().execute(
427
+ log.backup, // id
428
+ log, // objeto (BackupLog)
429
+ Entity.BACKUP_LOG.collection, // "backup_log"
430
+ "after_finish", // evento
431
+ null, // player
432
+ context);
433
+ ```
434
+
435
+ > **Só dispara em sucesso.** No caminho de erro o trigger é pulado. Não existe trigger `before`/`antes` apesar do comentário no topo de `BackupManager.java` ("executar trigger em eventos antes e após").
436
+
437
+ ---
438
+
439
+ ## 7. Suportado vs NÃO Suportado
440
+
441
+ ### ✅ Suportado
442
+
443
+ - Criar/atualizar config de backup (`POST /v3/backup`, upsert/full-replace).
444
+ - Buscar config por id (`GET /v3/backup/{id}`).
445
+ - Deletar config + cascata de logs (`DELETE /v3/backup/{id}`).
446
+ - Disparar execução assíncrona (`GET /v3/backup/{id}/execute`).
447
+ - Filtro por `match` + transformação por pipeline `aggregate`.
448
+ - Modo full-refresh (`incremental=false`, dropa destino) e modo append/upsert (`incremental=true`).
449
+ - Remoção opcional dos originais (`deleteOriginalDataAfterBackup`).
450
+ - Lock lógico de uma execução em andamento por config.
451
+ - Trigger `after_finish` em sucesso.
452
+ - Isolamento multi-tenant por `apiKey`.
453
+
454
+ ### ❌ NÃO Suportado
455
+
456
+ - **Agendamento automático / cron** — campos `active` e `cron` estão **comentados** em `Backup.java` (linhas 28–29). Não há scheduler que execute backups automaticamente. Execução é sempre manual via `/execute`.
457
+ - **Listagem de backups** — não existe `GET /v3/backup` (a doc anterior afirmava existir; é falso). Só busca por id.
458
+ - **Consulta de log via REST** — `BackupManager.findLog(id)` existe mas **nenhum endpoint o expõe**. Módulos irmãos (`StaticRest`, `FolderRest`, `CsvRest`) expõem `findLog`; `BackupRest` não. A única forma de inspecionar um log é consultar a coleção `backup_log` diretamente no Mongo.
459
+ - **Campo `extra` do log** — declarado em `BackupLog` mas **nunca escrito** pelo código. Campo morto.
460
+ - **Trigger `before`/antes do backup** — declarado no comentário de `BackupManager`, mas **não implementado**. Apenas `after_finish` (sucesso) dispara.
461
+ - **Trigger em caso de erro** — não há evento de trigger no caminho de falha.
462
+ - **Recuperação de execuções perdidas** — a fila é **em memória, por tenant**. Reinício do servidor descarta os logs enfileirados e ainda **não finalizados**. Pior: um log que ficou sem `finish` (por reinício, ou por config deletada antes do `asyncExecute`) **bloqueia permanentemente** novas execuções daquele backup (a checagem `{finish:{$exists:false}}` continua encontrando-o e retornando-o sem re-enfileirar). Recuperação exige intervenção manual no Mongo.
463
+ - **Transação / atomicidade** — não há. Falha parcial não faz rollback.
464
+ - **Validação com feedback de erro** — body inválido no `POST` retorna 200/201 "sucesso" sem persistir.
465
+ - **404 para recursos inexistentes** — `GET`/`execute` em id inexistente retornam 200 com body nulo.
466
+
467
+ ---
468
+
469
+ ## 8. Segurança e Permissões
470
+
471
+ - **Autenticação:** todas as rotas recebem `@BeanParam AuthBean`, que extrai o `apiKey` do header `Authorization` (`Bearer`, `Basic` ou `Studio`) ou de `api_key`/`X-Api-Key`. A autenticação global é aplicada pelo `SecurityFilter` (`com.funifier.rest.v3.filter.SecurityFilter`).
472
+ - **Isolamento por tenant:** o `apiKey` extraído roteia para `FrontController.getInstance(apiKey)` → `ManagerFactory` do tenant. Operações só enxergam as coleções daquele banco.
473
+ - **Ausência de autorização fina:** `BackupRest` **não declara** checagens de role/permissão por método. A proteção é o nível de autenticação resolvido pelo `SecurityFilter`. Como o módulo é destrutivo (drop/remove), deve ser tratado como operação **administrativa** (token Basic/Studio com privilégio de admin), mas o código não força isso na própria classe REST.
474
+
475
+ **Superfícies de injeção / risco confirmadas no código:**
476
+
477
+ 1. **Execução de query/aggregate arbitrários:** `match` e `aggregate` são armazenados como **strings cruas** e executados diretamente contra o Mongo (`source.count(match)`, `source.aggregate(...)`, `source.remove(match)`). Quem cria uma config de backup pode rodar **qualquer filtro e qualquer pipeline** sobre **qualquer coleção** do tenant, incluindo coleções de sistema. É efetivamente uma superfície de execução de query arbitrária no banco do tenant.
478
+ 2. **Remoção arbitrária de dados:** com `deleteOriginalDataAfterBackup=true`, `source.remove(match)` apaga **qualquer conjunto de documentos** descrito por `match` em **qualquer coleção** (`collection`). Um `match` amplo (ex.: `{}`) remove a coleção inteira de origem.
479
+ 3. **Drop de coleção arbitrária:** com `incremental=false`, `bkp.drop()` apaga a coleção apontada por `backupCollectionName`. Se isso apontar para uma coleção viva (ex.: `player`, `achievement`), ela é **destruída**.
480
+ 4. **GET mutante:** `GET /{id}/execute` dispara trabalho destrutivo via um verbo idempotente — vulnerável a prefetch/cache/CSRF-via-GET se exposto a tokens com menos privilégio.
481
+
482
+ > Recomendação operacional (não imposta pelo código): restringir criação/execução de backups a credenciais administrativas e revisar `collection`/`backupCollectionName`/`match` antes de cada execução.
483
+
484
+ ---
485
+
486
+ ## 9. Observabilidade e Troubleshooting
487
+
488
+ ### Diagnóstico rápido
489
+
490
+ O módulo **não expõe logs via REST**. Toda investigação é feita direto no Mongo nas coleções `backup` e `backup_log`.
491
+
492
+ ```
493
+ # Configuração de backup
494
+ GET /v3/backup/<id>
495
+
496
+ # Disparar execução (retorna o log inicial)
497
+ GET /v3/backup/<id>/execute
498
+ ```
499
+
500
+ ### Queries úteis (Mongo direto)
501
+
502
+ ```javascript
503
+ // Último log de um backup
504
+ db.backup_log.find({ backup: "<id>" }).sort({ start: -1 }).limit(1)
505
+
506
+ // Logs "presos" (em andamento) que bloqueiam novas execuções
507
+ db.backup_log.find({ finish: { $exists: false } })
508
+
509
+ // Logs com erro e a mensagem
510
+ db.backup_log.find({ status: "error" }, { backup: 1, message: 1, start: 1 })
511
+
512
+ // Verificar contagens da última execução
513
+ db.backup_log.find({ backup: "<id>" }, { status: 1, total: 1, start: 1, finish: 1 }).sort({ start: -1 })
514
+ ```
515
+
516
+ ### Erros comuns e causas
517
+
518
+ | Sintoma | Causa provável |
519
+ | ------- | -------------- |
520
+ | `POST` retorna 201 mas a config não aparece | Faltou `collection`, `match` ou `aggregate` → `save` no-opou silenciosamente. |
521
+ | `/execute` retorna sempre o mesmo log e nada processa | Existe log sem `finish` (preso). Reinício de servidor ou config deletada antes do async. Limpe/finalize o log no Mongo. |
522
+ | Coleção de destino sumiu | `incremental=false` → `bkp.drop()`. Esperado. |
523
+ | Dados originais sumiram | `deleteOriginalDataAfterBackup=true` com `match` amplo. |
524
+ | Destino acumula duplicatas a cada run | `incremental=true` + pipeline que remove/recria `_id`. Dedup por `_id` quebrou. |
525
+ | `created` virou null após editar config | `POST` é full-replace e não reenviou `created`. |
526
+ | Backup "não termina" | Trabalho roda em thread assíncrona; releia `backup_log`. Se `status` não avança e não há `finish`, ver log preso. |
527
+
528
+ ### Sinais no log do servidor (stdout)
529
+
530
+ - `COMECOU EXECUCAO DA THREAD BACKUP ASYNC` — impresso quando a thread assíncrona inicia (log de debug legado em `BackupAsyncProcessor.run`).
531
+ - Stacktrace via `e.printStackTrace()` — toda exceção de `asyncExecute` é impressa no stdout (não em logger estruturado). É onde aparece a causa real de um `status="error"`.
532
+
533
+ ### Notas de performance
534
+
535
+ - `status` é reescrito em `backup_log` **a cada 50 documentos** copiados → write amplification proporcional ao volume. Em backups muito grandes isso gera muitos writes em `backup_log`.
536
+ - O aggregate usa `allowDiskUse(true)` — pipelines pesados podem usar disco no Mongo.
537
+ - Detalhe de implementação: no estágio de `aggregate`, o `match` passa por dupla serialização (`FunifierMarshaller.toString(FunifierMarshaller.toString(match))`, `BackupManager.java` linha 141) enquanto o `count` usa serialização simples (linha 134). É funcionalmente idempotente para BSON bem-formado, mas é um code smell.
538
+
539
+ ---
540
+
541
+ ## 10. Exemplos Práticos
542
+
543
+ ### 10.1 Exemplo mínimo funcional (full refresh, sem deletar origem)
544
+
545
+ ```json
546
+ POST /v3/backup
547
+ {
548
+ "collection": "action_log",
549
+ "match": { "time": { "$lt": { "$date": "2025-01-01T00:00:00Z" } } },
550
+ "aggregate": []
551
+ }
552
+ ```
553
+
554
+ - `aggregate: []` → pipeline efetivo é só `[{$match: ...}]` (cópia sem transformação).
555
+ - `backupCollectionName` ausente → destino vira `action_log_bkp`.
556
+ - `incremental` ausente (`false`) → `action_log_bkp` é dropada e recriada a cada execução.
557
+ - Disparar: `GET /v3/backup/<id>/execute`.
558
+
559
+ ### 10.2 Exemplo avançado (incremental, transformando, e removendo origem)
560
+
561
+ ```json
562
+ POST /v3/backup
563
+ {
564
+ "title": "Arquivar achievements de 2024 com projeção enxuta",
565
+ "collection": "achievement",
566
+ "match": { "time": { "$gte": { "$date": "2024-01-01T00:00:00Z" },
567
+ "$lt": { "$date": "2025-01-01T00:00:00Z" } } },
568
+ "aggregate": [
569
+ { "$project": { "_id": 1, "player": 1, "type": 1, "total": 1, "time": 1 } }
570
+ ],
571
+ "backupCollectionName": "achievement_2024_archive",
572
+ "incremental": true,
573
+ "deleteOriginalDataAfterBackup": true
574
+ }
575
+ ```
576
+
577
+ - `_id` mantido no `$project` → dedup por upsert funciona em re-execuções (modo incremental).
578
+ - Após copiar, remove da `achievement` os docs que casam com `match`.
579
+ - **Atenção:** a remoção usa `match`, não os IDs copiados — qualquer doc que passe a casar entre cópia e remoção será removido sem cópia.
580
+
581
+ ### 10.3 Anti-pattern — o que NÃO fazer
582
+
583
+ ```json
584
+ POST /v3/backup
585
+ {
586
+ "collection": "achievement",
587
+ "match": {},
588
+ "aggregate": [ { "$group": { "_id": "$player", "n": { "$sum": 1 } } } ],
589
+ "backupCollectionName": "player",
590
+ "incremental": false,
591
+ "deleteOriginalDataAfterBackup": true
592
+ }
593
+ ```
594
+
595
+ Por que é destrutivo:
596
+
597
+ 1. `incremental=false` + `backupCollectionName: "player"` → `bkp.drop()` **apaga a coleção `player` inteira**.
598
+ 2. `$group` redefine `_id` → mesmo em modo incremental haveria perda de identidade; aqui o destino já foi dropado.
599
+ 3. `match: {}` + `deleteOriginalDataAfterBackup=true` → **remove TODOS os achievements** da origem.
600
+ 4. Resultado: `player` destruída e `achievement` esvaziada, sem rollback.
601
+
602
+ **Regra:** nunca aponte `backupCollectionName` para uma coleção viva; nunca combine `match` amplo com `deleteOriginalDataAfterBackup`; valide o pipeline antes de executar.
603
+
604
+ ---
605
+
606
+ ## Checklist de Configuração
607
+
608
+ - [ ] `collection`, `match` e `aggregate` preenchidos (sem os três, o `POST` não persiste mas retorna "201").
609
+ - [ ] `aggregate` é um **array** de estágios (não objeto); o `$match` é montado automaticamente — não duplique.
610
+ - [ ] `backupCollectionName` aponta para uma coleção **dedicada de destino**, nunca para uma coleção viva (com `incremental=false` ela será **dropada**).
611
+ - [ ] Decidiu conscientemente entre `incremental=false` (full refresh, dropa destino) e `incremental=true` (append/upsert por `_id`).
612
+ - [ ] Se `incremental=true`, o pipeline **preserva `_id`** (senão duplica a cada run).
613
+ - [ ] `deleteOriginalDataAfterBackup` só `true` após confirmar que `match` é restrito (lembre: remove por `match`, não pelos IDs copiados).
614
+ - [ ] Ao **atualizar** uma config, reenvie o documento **completo** (incl. `created`) — `POST` é full-replace.
615
+ - [ ] Disparo é manual via `GET /{id}/execute`; não espere agendamento automático (não existe).
616
+ - [ ] Para acompanhar o resultado, consulte `backup_log` **direto no Mongo** (não há endpoint de log).
617
+ - [ ] Armadilha de log preso: se `/execute` parar de processar, verifique `backup_log` com `finish` ausente e finalize/remova manualmente.
618
+ - [ ] Operação tratada como **administrativa** (credencial com privilégio) — o código não impõe role, mas as operações são destrutivas.