funifier-mcp 0.2.25 → 0.2.27

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 (211) 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 +5 -2
  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 +1011 -77
  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/core/api-client.d.ts +21 -1
  66. package/dist/core/api-client.d.ts.map +1 -1
  67. package/dist/core/api-client.js +154 -1
  68. package/dist/core/api-client.js.map +1 -1
  69. package/dist/core/constants.d.ts +14 -0
  70. package/dist/core/constants.d.ts.map +1 -1
  71. package/dist/core/constants.js +14 -0
  72. package/dist/core/constants.js.map +1 -1
  73. package/dist/core/types/Folder.d.ts +16 -0
  74. package/dist/core/types/Folder.d.ts.map +1 -0
  75. package/dist/core/types/Folder.js +3 -0
  76. package/dist/core/types/Folder.js.map +1 -0
  77. package/dist/core/types/FolderContent.d.ts +10 -0
  78. package/dist/core/types/FolderContent.d.ts.map +1 -0
  79. package/dist/core/types/FolderContent.js +3 -0
  80. package/dist/core/types/FolderContent.js.map +1 -0
  81. package/dist/core/types/FolderContentType.d.ts +10 -0
  82. package/dist/core/types/FolderContentType.d.ts.map +1 -0
  83. package/dist/core/types/FolderContentType.js +3 -0
  84. package/dist/core/types/FolderContentType.js.map +1 -0
  85. package/dist/core/types/FolderLog.d.ts +11 -0
  86. package/dist/core/types/FolderLog.d.ts.map +1 -0
  87. package/dist/core/types/FolderLog.js +3 -0
  88. package/dist/core/types/FolderLog.js.map +1 -0
  89. package/dist/core/types/index.d.ts +4 -0
  90. package/dist/core/types/index.d.ts.map +1 -1
  91. package/dist/core/types/index.js +4 -0
  92. package/dist/core/types/index.js.map +1 -1
  93. package/dist/mcp/bundle.js +121 -87
  94. package/dist/mcp/check-update.d.ts +2 -0
  95. package/dist/mcp/check-update.d.ts.map +1 -0
  96. package/dist/mcp/check-update.js +44 -0
  97. package/dist/mcp/check-update.js.map +1 -0
  98. package/dist/mcp/index.js +5 -2
  99. package/dist/mcp/index.js.map +1 -1
  100. package/dist/mcp/resources/documentation.d.ts +1 -1
  101. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  102. package/dist/mcp/resources/documentation.js +39 -3
  103. package/dist/mcp/resources/documentation.js.map +1 -1
  104. package/dist/mcp/tools/_char-guard.js +1 -1
  105. package/dist/mcp/tools/_char-guard.js.map +1 -1
  106. package/dist/mcp/tools/_fetch-current.d.ts +1 -1
  107. package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
  108. package/dist/mcp/tools/_fetch-current.js +12 -0
  109. package/dist/mcp/tools/_fetch-current.js.map +1 -1
  110. package/dist/mcp/tools/connect.d.ts.map +1 -1
  111. package/dist/mcp/tools/connect.js +18 -8
  112. package/dist/mcp/tools/connect.js.map +1 -1
  113. package/dist/mcp/tools/database.d.ts.map +1 -1
  114. package/dist/mcp/tools/database.js +59 -47
  115. package/dist/mcp/tools/database.js.map +1 -1
  116. package/dist/mcp/tools/database.test.js +2 -2
  117. package/dist/mcp/tools/database.test.js.map +1 -1
  118. package/dist/mcp/tools/delete.d.ts.map +1 -1
  119. package/dist/mcp/tools/delete.js +33 -3
  120. package/dist/mcp/tools/delete.js.map +1 -1
  121. package/dist/mcp/tools/execute.d.ts.map +1 -1
  122. package/dist/mcp/tools/execute.js +20 -9
  123. package/dist/mcp/tools/execute.js.map +1 -1
  124. package/dist/mcp/tools/folder.d.ts +4 -0
  125. package/dist/mcp/tools/folder.d.ts.map +1 -0
  126. package/dist/mcp/tools/folder.js +68 -0
  127. package/dist/mcp/tools/folder.js.map +1 -0
  128. package/dist/mcp/tools/get.d.ts.map +1 -1
  129. package/dist/mcp/tools/get.js +16 -6
  130. package/dist/mcp/tools/get.js.map +1 -1
  131. package/dist/mcp/tools/index.d.ts +1 -1
  132. package/dist/mcp/tools/index.d.ts.map +1 -1
  133. package/dist/mcp/tools/index.js +5 -1
  134. package/dist/mcp/tools/index.js.map +1 -1
  135. package/dist/mcp/tools/list.d.ts.map +1 -1
  136. package/dist/mcp/tools/list.js +38 -14
  137. package/dist/mcp/tools/list.js.map +1 -1
  138. package/dist/mcp/tools/logs.d.ts.map +1 -1
  139. package/dist/mcp/tools/logs.js +15 -5
  140. package/dist/mcp/tools/logs.js.map +1 -1
  141. package/dist/mcp/tools/save.d.ts.map +1 -1
  142. package/dist/mcp/tools/save.js +26 -4
  143. package/dist/mcp/tools/save.js.map +1 -1
  144. package/dist/mcp/tools/save.test.js +192 -1
  145. package/dist/mcp/tools/save.test.js.map +1 -1
  146. package/dist/mcp/tools/search-docs.d.ts +3 -0
  147. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  148. package/dist/mcp/tools/search-docs.js +102 -0
  149. package/dist/mcp/tools/search-docs.js.map +1 -0
  150. package/package.json +6 -2
  151. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  152. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  153. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  154. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  155. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  156. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  157. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  158. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  159. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  160. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  161. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  162. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  163. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  164. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  165. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  166. package/skills/funifier/SKILL.md +88 -0
  167. package/skills/funifier/references/configure-security.md +96 -0
  168. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  169. package/skills/funifier/references/create-aggregate.md +144 -0
  170. package/skills/funifier/references/create-challenge.md +116 -0
  171. package/skills/funifier/references/create-competition.md +98 -0
  172. package/skills/funifier/references/create-crossword.md +574 -0
  173. package/skills/funifier/references/create-custom-object.md +91 -0
  174. package/skills/funifier/references/create-custom-page.md +135 -0
  175. package/skills/funifier/references/create-folder.md +104 -0
  176. package/skills/funifier/references/create-lastmile.md +643 -0
  177. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  178. package/skills/funifier/references/create-level.md +94 -0
  179. package/skills/funifier/references/create-lottery.md +913 -0
  180. package/skills/funifier/references/create-mystery.md +769 -0
  181. package/skills/funifier/references/create-notification.md +75 -0
  182. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  183. package/skills/funifier/references/create-quiz.md +98 -0
  184. package/skills/funifier/references/create-scheduler.md +141 -0
  185. package/skills/funifier/references/create-story.md +636 -0
  186. package/skills/funifier/references/create-swap.md +95 -0
  187. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  188. package/skills/funifier/references/create-virtual-good.md +96 -0
  189. package/skills/funifier/references/create-webhook.md +72 -0
  190. package/skills/funifier/references/create-websocket.md +71 -0
  191. package/skills/funifier/references/create-widget.md +76 -0
  192. package/skills/funifier/references/debug.md +87 -0
  193. package/skills/funifier/references/help.md +81 -0
  194. package/skills/funifier/references/implement-frontend.md +106 -0
  195. package/skills/funifier/references/import-csv.md +75 -0
  196. package/skills/funifier/references/manage-player.md +82 -0
  197. package/skills/funifier/references/manage-team.md +76 -0
  198. package/skills/funifier/references/upload-file.md +91 -0
  199. package/datasource-funifier-docs/.search-index.json +0 -17318
  200. package/datasource-funifier-docs/.skills-map.json +0 -73
  201. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  202. package/skills/funifier-create-challenge/SKILL.md +0 -88
  203. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  204. package/skills/funifier-create-level/SKILL.md +0 -87
  205. package/skills/funifier-create-quiz/SKILL.md +0 -87
  206. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  207. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  208. package/skills/funifier-debug/SKILL.md +0 -92
  209. package/skills/funifier-help/SKILL.md +0 -86
  210. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  211. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,41 +1,388 @@
1
- # WebHook (WebHook)
1
+ # `webhook`
2
2
 
3
- **API Endpoint:** `/v3/webhook`
3
+ **Acesso Studio:** nenhuma tela de Studio localizada no `funifier-service` — o módulo é **API-only** (não há referência a `/studio/webhook` ou UI equivalente no código).
4
+ **API Endpoint:** `/v3/hook` (⚠️ **não** `/v3/webhook` — ver seção 4)
5
+ **Coleção MongoDB:** `webhook` (definida em `Entity.WEBHOOK("webhook", Webhook.class)`)
4
6
 
5
- ## O que é
7
+ ---
6
8
 
7
- Notificações automáticas para endpoints externos em tempo real. Permite que a Funifier envie notificações automáticas (payloads) para sistemas externos sempre que eventos importantes ocorrem na gamificação, sem necessidade de polling.
9
+ ## 1. Visão Geral
8
10
 
9
- ## Quando usar
11
+ O módulo `webhook` implementa um mecanismo de **subscription/notificação push**: um sistema externo registra uma `target_url` associada a um `event`, e a Funifier faz `HTTP POST` nessa URL quando o evento ocorre — evitando que o consumidor faça polling.
10
12
 
11
- - Para enviar dados para sistemas de BI quando um desafio é completado
12
- - Para notificar CRM quando jogador atinge uma meta
13
- - Para integrar com ferramentas externas (Slack, Teams, etc.)
14
- - Para sincronizar dados em tempo real com outros sistemas
13
+ Papel arquitetural e fatos confirmados no código:
15
14
 
16
- ## Checklist de Configuração
15
+ - A entidade (`Webhook.java`) tem **apenas 3 campos**: `_id`, `target_url`, `event`.
16
+ - Existe **um único evento implementado**: `achievement_created` (constante `Webhook.EVENT_ACHIEVEMENT_CREATED`). Nenhum outro valor de `event` é disparado em qualquer ponto do código.
17
+ - O **único ponto de disparo** em todo o `funifier-service` é `AchievementManager.fireAction(...)`, linha 827. Não há scheduler, job, fila ou outro produtor.
18
+ - O módulo **depende do módulo Achievement** (único produtor de eventos) e é **consumido por sistemas externos**. Não há acoplamento com Trigger, mas a recomendação interna da própria plataforma (texto de prompt em `AIRest.buildZapier`) é usar **Triggers Groovy/Java com `Unirest.post`** para qualquer evento que não seja `achievement_created` — porque o módulo webhook nativo não os suporta.
19
+
20
+ > O módulo resolve o caso "notifique-me quando um jogador ganhar uma conquista". Para qualquer outra necessidade de integração em tempo real, o código não oferece cobertura nativa (ver seção 7).
21
+
22
+ ---
23
+
24
+ ## 2. Arquitetura e Fluxos
25
+
26
+ São dois fluxos independentes: **(A) gestão da inscrição** (REST, síncrono) e **(B) entrega do evento** (disparada pelo Achievement, síncrona).
27
+
28
+ ### 2.1 Pipeline A — Gestão da inscrição (REST)
29
+
30
+ ```
31
+ [POST /v3/hook] → WebhookRest.insert() → WebhookManager.insert(hook)
32
+ [POST /v3/hook/unsub.] → WebhookRest.unsubscribe() → WebhookManager.deleteByURL(target_url)
33
+ [GET /v3/hook] → WebhookRest.findAll() → WebhookManager.findAll()
34
+ [GET /v3/hook/{id}] → WebhookRest.find() → WebhookManager.find(id)
35
+ [DELETE /v3/hook/{id}] → WebhookRest.delete() → WebhookManager.delete(id)
36
+ ```
37
+
38
+ Detalhe de `WebhookManager.insert(hook)`:
39
+
40
+ ```
41
+ se hook != null e hook.id == null:
42
+ hook.id = Guid.newShortGuid() // = new ObjectId().toString() → hex de 24 chars
43
+ c = getJongoConnection().getCollection("webhook")
44
+ c.save(hook) // Jongo save = UPSERT por _id
45
+ ```
46
+
47
+ Consequência: se o `_id` for informado no corpo e já existir, `c.save()` **substitui** o documento (full replace) — POST funciona como upsert, não como create estrito (ver seção 4).
48
+
49
+ ### 2.2 Pipeline B — Entrega do evento (`achievement_created`)
50
+
51
+ Sequência real, citando métodos:
52
+
53
+ 1. Uma `ActionLog` é processada → `AchievementManager.fireAction(ActionLog trigger)` (linha 117) avalia desafios/pontos/níveis e acumula `List<Achievement> result`.
54
+ 2. Etapa 5–6: `updatePlayerStatus(...)` adiciona status do jogador a `result`; conquistas de time são reavaliadas.
55
+ 3. Etapa 7 (linha 825–828): **somente se `result.size() > 0`** →
56
+ `manager.getWebhookManager().execute(Webhook.EVENT_ACHIEVEMENT_CREATED, result)`.
57
+ 4. `WebhookManager.execute(event, content)`:
58
+
59
+ ```
60
+ hooks = getCollection("webhook").find("{event:#}", "achievement_created")
61
+ para cada webhook em hooks:
62
+ tente:
63
+ Unirest.post(webhook.target_url)
64
+ .header("Content-Type", "application/json")
65
+ .body(JsonUtil.toJsonRemoveNullFields(content)) // content = List<Achievement>
66
+ .asString()
67
+ capture UnirestException:
68
+ System.out.println("WEBHOOK ERROR (id - event) : URL " + target_url)
69
+ e.printStackTrace()
70
+ // NÃO interrompe o loop; NÃO repropaga
71
+ ```
72
+
73
+ Características confirmadas da entrega:
74
+
75
+ - **Síncrona e sequencial** — cada hook é chamado em sequência, dentro da thread de `fireAction`. Um `target_url` lento atrasa a etapa pós-avaliação.
76
+ - **Sem timeout configurado** no `Unirest.post` (usa o default da lib).
77
+ - **Sem retry, sem fila, sem DLQ, sem confirmação de entrega.**
78
+ - **Sem persistência de status** de envio.
79
+ - Erros são **engolidos por hook** (impressos em `stdout`/`stderr`, não em logger) e não afetam o resultado da ação.
80
+ - O corpo é `JsonUtil.toJsonRemoveNullFields(result)`: JSON indentado, campos `null` removidos. Esse helper **retorna `null`** se o Jackson lançar exceção — nesse caso o corpo do POST seria literalmente vazio/`null`.
81
+
82
+ #### Fluxo de entrega — `achievement_created`
83
+
84
+ ```mermaid
85
+ sequenceDiagram
86
+ participant Ext as Cliente (ActionLog)
87
+ participant AM as AchievementManager.fireAction
88
+ participant WM as WebhookManager.execute
89
+ participant DB as MongoDB (webhook)
90
+ participant URL as target_url externa
91
+
92
+ Ext->>AM: processa ação do jogador
93
+ AM->>AM: avalia desafios/pontos/níveis → List<Achievement> result
94
+ alt result.size() > 0
95
+ AM->>WM: execute("achievement_created", result)
96
+ WM->>DB: find({event:"achievement_created"})
97
+ DB-->>WM: hooks correspondentes
98
+ loop para cada hook (sequencial, bloqueante)
99
+ WM->>URL: POST target_url (body = toJsonRemoveNullFields(result))
100
+ alt sucesso
101
+ URL-->>WM: 2xx (ignorado)
102
+ else UnirestException
103
+ URL-->>WM: erro → stdout + stackTrace, continua
104
+ end
105
+ end
106
+ else result vazio
107
+ AM->>AM: nenhum webhook é disparado
108
+ end
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 3. Estrutura dos Objetos
114
+
115
+ ### 3.1 `Webhook` — documento raiz
116
+
117
+ Arquivo: `com/funifier/engine/webhook/Webhook.java`. Anotação de classe: `@JsonIgnoreProperties(ignoreUnknown=true)`.
118
+
119
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
120
+ | ------------ | ------ | ------------------------------- | ------------- | --------- |
121
+ | `_id` | String | `ObjectId` hex se omitido | não | Identificador. No Java é o campo `id` mapeado via `@JsonProperty("_id")`. Se não enviado, `insert()` gera `Guid.newShortGuid()` = `new ObjectId().toString()`. |
122
+ | `target_url` | String | — | de fato sim¹ | URL externa que recebe o `POST`. Não há validação de formato no código. |
123
+ | `event` | String | — | de fato sim¹ | Tipo de evento. **Único valor com efeito:** `achievement_created`. |
124
+
125
+ ¹ **Não há `@NotNull` nem validação explícita.** A obrigatoriedade é de fato, derivada do comportamento: um hook sem `event` jamais casa o filtro `find({event:#})` e nunca dispara; um hook com `target_url` nula faria `Unirest.post(null)` → `UnirestException` capturada e ignorada.
126
+
127
+ #### Campos computados (não persistem)
128
+ Nenhum. A entidade é plana e totalmente persistida.
129
+
130
+ #### Campos removidos silenciosamente na resposta
131
+ A serialização de saída usa `JsonUtil.toJsonRemoveNullFields` (Jackson com `Include.NON_NULL`). Qualquer um dos 3 campos que esteja `null` é **omitido** do corpo de resposta do POST.
132
+
133
+ #### Campos aceitos e silenciosamente ignorados (entrada)
134
+ Por causa de `@JsonIgnoreProperties(ignoreUnknown=true)`, **qualquer campo extra no corpo é descartado sem erro**. Tentativas comuns (baseadas em outras plataformas de webhook) que **não existem** e são ignoradas:
135
+
136
+ - `headers`, `secret` / `signing_secret`, `description`, `enabled` / `active`, `method`, `retries`, `timeout`, `filter` / `criteria`, `player`, `item`.
137
+
138
+ Enviar qualquer um deles retorna 201 com sucesso, mas o campo **não é persistido nem tem efeito**.
139
+
140
+ #### Enum de eventos
141
+ Não existe um `enum` — `event` é `String` livre. A única constante declarada é:
142
+
143
+ | Constante | Valor | Significado operacional |
144
+ | --------- | ----- | ----------------------- |
145
+ | `Webhook.EVENT_ACHIEVEMENT_CREATED` | `"achievement_created"` | Disparado quando `fireAction` registra ≥1 nova conquista (progresso de desafio, level up, status do jogador). |
146
+
147
+ > Qualquer outro valor de `event` pode ser salvo, mas **nunca será disparado** (não há outro `execute(...)` no código).
148
+
149
+ ### 3.2 Subentidades
150
+ **Não aplicável** — entidade plana, sem objetos aninhados.
151
+
152
+ ### 3.3 Técnicas de jogo (`techniques`)
153
+ **Não aplicável** — o módulo não possui códigos GT nem qualquer relação com técnicas de jogo.
154
+
155
+ > **Ciclo de vida / `stateDiagram`:** não aplicável — a entidade não tem campo de status/estado. Um hook só existe ou não existe.
156
+
157
+ ---
158
+
159
+ ## 4. Endpoints
160
+
161
+ Classe: `WebhookRest` — `@Path("v3/hook")`. **Esta é a única classe JAX-RS que registra o caminho** (confirmado: não existe `@Path` para `webhook` em nenhum outro lugar). O caminho documentado anteriormente como `/v3/webhook` está **incorreto**.
162
+
163
+ ```mermaid
164
+ flowchart LR
165
+ A[POST /v3/hook] -->|insert/upsert| W[(coleção webhook)]
166
+ B[POST /v3/hook/unsubscribe] -->|deleteByURL| W
167
+ C[GET /v3/hook] -->|findAll| W
168
+ D[GET /v3/hook/:id] -->|find| W
169
+ E[DELETE /v3/hook/:id] -->|delete| W
170
+ ```
17
171
 
18
- - [ ] Definir URL do endpoint externo
19
- - [ ] Definir eventos que disparam o webhook
20
- - [ ] Definir formato do payload
21
- - [ ] Testar conectividade com endpoint
172
+ ### `POST /v3/hook` Inscrever (subscribe)
22
173
 
23
- ## API Endpoints
174
+ | Aspecto | Detalhe |
175
+ | --------------------- | ------- |
176
+ | Finalidade | Registrar um webhook. |
177
+ | Autenticação | Credenciais via `AuthBean` (Bearer/Basic/Studio token, ou `api_key`/`X-Api-Key`). |
178
+ | Full replace ou patch | **Upsert (full replace por `_id`)** via `c.save(hook)`. Sem `_id` → cria com id gerado. Com `_id` existente → substitui o documento inteiro. |
179
+ | Status de retorno | `201 CREATED` com `toJsonRemoveNullFields(hook)`. |
24
180
 
25
- ### Listar Webhooks
26
- **Método:** GET
27
- **Endpoint:** `/v3/webhook`
181
+ **Comportamento real:** difere do REST convencional — POST não é create estrito; é upsert. O id gerado é um `ObjectId` hex.
28
182
 
29
- ### Criar Webhook
30
- **Método:** POST
31
- **Endpoint:** `/v3/webhook`
183
+ **Exemplo request/response:**
184
+ ```json
185
+ // POST /v3/hook
186
+ { "target_url": "https://meusistema.com/funifier-hook", "event": "achievement_created" }
187
+ ```
188
+ ```json
189
+ // 201 CREATED
190
+ { "_id": "665f0b2c9a1e4d0012ab34cd", "target_url": "https://meusistema.com/funifier-hook", "event": "achievement_created" }
191
+ ```
32
192
 
33
- ### Deletar Webhook
34
- **Método:** DELETE
35
- **Endpoint:** `/v3/webhook/:id`
193
+ ### `POST /v3/hook/unsubscribe` — Cancelar por URL
36
194
 
37
- ## Validações e Testes
195
+ | Aspecto | Detalhe |
196
+ | --------------------- | ------- |
197
+ | Finalidade | Remover inscrição(ões) pela `target_url`. |
198
+ | Autenticação | `AuthBean`. |
199
+ | Corpo | `{ "target_url": "..." }` (só `target_url` é usado). |
200
+ | Status de retorno | `201 CREATED` (⚠️ retorna **CREATED** para uma operação de exclusão). |
201
+
202
+ **Comportamento real:** `WebhookManager.deleteByURL(target_url)` executa `remove("{target_url:#}", url)` — **remove TODOS os documentos** cuja `target_url` casa, não apenas um. **Nenhuma contagem é retornada**; o corpo de resposta é o próprio hook enviado, ecoado.
203
+
204
+ ### `GET /v3/hook` — Listar todos
205
+
206
+ | Aspecto | Detalhe |
207
+ | ------- | ------- |
208
+ | Finalidade | Lista todos os webhooks da organização. |
209
+ | Autenticação | `AuthBean`. apidoc: OAuth 2.0 (client_credentials). |
210
+ | Retorno | `200 OK`, array JSON. |
211
+
212
+ ### `GET /v3/hook/{id}` — Buscar por id
213
+
214
+ | Aspecto | Detalhe |
215
+ | ------- | ------- |
216
+ | Finalidade | Retorna um webhook pelo `_id`. |
217
+ | Autenticação | `AuthBean`. apidoc: OAuth 2.0 (client_credentials). |
218
+ | Retorno | `200 OK`. Se não encontrado, `find` retorna `null` → corpo `null`. |
219
+
220
+ ### `DELETE /v3/hook/{id}` — Remover por id
221
+
222
+ | Aspecto | Detalhe |
223
+ | ------- | ------- |
224
+ | Finalidade | Remove um webhook pelo `_id` (unsubscribe). |
225
+ | Autenticação | `AuthBean`. apidoc: OAuth 2.0 (client_credentials). |
226
+ | Retorno | `200 OK` com corpo `null`. Idempotente: remover id inexistente também retorna 200. |
227
+
228
+ ---
229
+
230
+ ## 5. Regras de Negócio
231
+
232
+ Regras presentes no código e ausentes de qualquer schema:
233
+
234
+ - **`event` é o único filtro de entrega.** Não é possível filtrar por jogador, item, desafio ou critério. Todos os hooks de `achievement_created` recebem o mesmo payload completo.
235
+ - **POST é upsert.** Reenviar com `_id` existente substitui o documento.
236
+ - **`unsubscribe` é "delete-all-by-url".** Uma chamada remove todos os hooks com aquela `target_url`.
237
+ - **Campos obrigatórios são de fato, não validados.** O sistema aceita hooks malformados sem erro; eles simplesmente não funcionam.
238
+ - **Inscrição em evento desconhecido é no-op silencioso.** O documento é salvo, mas nunca dispara.
239
+ - **Multi-tenant por banco de dados.** `FrontController.getInstance(apiKey)` resolve a conexão Jongo da organização; a coleção `webhook` é isolada por org. Não há `organization`/`tenant` no documento — o isolamento vem do banco.
240
+ - **Entrega só ocorre quando `result.size() > 0`** em `fireAction` — ou seja, somente quando a ação efetivamente gerou conquistas.
241
+
242
+ ---
243
+
244
+ ## 6. Comportamentos Automáticos
245
+
246
+ | Comportamento | Trigger | Impacto | Persistência |
247
+ | ------------- | ------- | ------- | ------------ |
248
+ | Geração de `_id` | `insert()` com `id == null` | Atribui `ObjectId` hex ao hook | Persistido na coleção `webhook` |
249
+ | Entrega `achievement_created` | `fireAction` com `result.size() > 0` | POST síncrono em cada `target_url` inscrita | **Não persiste** resultado de envio |
250
+ | Log de erro de entrega | `UnirestException` durante o POST | `System.out.println` + `printStackTrace` | Apenas `stdout`/`stderr` |
251
+ | Remoção em massa por URL | `POST /v3/hook/unsubscribe` | Remove N documentos com a mesma URL | Persistido (remoção) |
252
+
253
+ ```mermaid
254
+ flowchart LR
255
+ A[ActionLog processada] --> B[fireAction avalia conquistas]
256
+ B --> C{result.size > 0?}
257
+ C -- não --> Z[fim, sem webhook]
258
+ C -- sim --> D["execute('achievement_created', result)"]
259
+ D --> E[find hooks por event]
260
+ E --> F{para cada hook}
261
+ F --> G[POST target_url síncrono]
262
+ G -- erro --> H[stdout + stackTrace, continua]
263
+ G -- ok --> I[resposta ignorada]
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 7. Suportado vs NÃO Suportado
269
+
270
+ ### ✅ Suportado
271
+ - Inscrever webhook (`POST /v3/hook`) com upsert por `_id`.
272
+ - Listar (`GET /v3/hook`) e buscar por id (`GET /v3/hook/{id}`).
273
+ - Cancelar por id (`DELETE /v3/hook/{id}`) e por URL (`POST /v3/hook/unsubscribe`).
274
+ - Entrega push do evento `achievement_created` para todas as URLs inscritas.
275
+ - Isolamento por organização (banco de dados por `apiKey`).
276
+
277
+ ### ❌ NÃO Suportado
278
+ - **Qualquer evento além de `achievement_created`.** A documentação anterior citava "desafio completado", "jogador atinge meta", "Slack/Teams" — **nenhum desses é um evento separado**. Não existe taxonomia de eventos no código.
279
+ - **Filtro/seleção de payload.** Sem filtro por jogador/item/critério; payload é sempre a `List<Achievement>` completa.
280
+ - **Customização do payload** (sem template, sem mapeamento de campos).
281
+ - **Retry, fila assíncrona, DLQ, confirmação de entrega ou histórico de envios.** Tudo síncrono e fire-and-forget.
282
+ - **Timeout configurável** no POST de saída.
283
+ - **Assinatura/HMAC, `secret` ou headers customizados** no POST de saída — o receptor não tem como verificar autenticidade.
284
+ - **Headers de autenticação no POST de saída** — só `Content-Type: application/json` é enviado.
285
+ - **Campos extras** no corpo de inscrição (`headers`, `secret`, `enabled`, etc.) — aceitos e silenciosamente descartados (`@JsonIgnoreProperties`).
286
+ - **UI de Studio** — nenhuma tela localizada no `funifier-service`.
287
+ - **Cobertura de testes** — nenhum arquivo de teste de webhook localizado em `src/test`.
288
+ - **Logging estruturado de entrega** — apenas `stdout` em caso de erro.
289
+
290
+ ---
291
+
292
+ ## 8. Segurança e Permissões
293
+
294
+ - **Autenticação de entrada:** todos os endpoints passam por `AuthBean`, que resolve a `apiKey` a partir de `Authorization` (Bearer/Basic/Studio/Account), `X-Api-Key` ou query `api_key`. Sem credencial válida não há `apiKey` → sem conexão de organização. A apidoc declara OAuth 2.0 (client_credentials) para GET/DELETE.
295
+ - **Isolamento multi-tenant:** por banco de dados da organização (via `FrontController.getInstance(apiKey)`). Um cliente só enxerga/altera webhooks da própria org.
296
+ - **SSRF (superfície real):** `target_url` é uma string arbitrária fornecida pelo cliente, e o servidor faz `Unirest.post(target_url)` diretamente. Um cliente autenticado pode apontar a URL para endereços internos/metadados da infraestrutura. **Não há allowlist nem validação de destino no código.**
297
+ - **Ausência de autenticidade na saída:** o POST de saída não é assinado e não envia segredo/HMAC. O receptor não consegue distinguir um payload legítimo da Funifier de um forjado. Recomenda-se ao consumidor validar por outro meio (ex.: URL secreta longa).
298
+ - **Falha de serialização silenciosa:** `toJsonRemoveNullFields` retorna `null` em exceção do Jackson — nesse caso o corpo do POST seria `null`/vazio, sem erro registrado.
299
+
300
+ ---
301
+
302
+ ## 9. Observabilidade e Troubleshooting
303
+
304
+ **Diagnóstico — o módulo está funcionando?**
305
+ - Liste as inscrições: `GET /v3/hook`.
306
+ - Confirme que o `event` é exatamente `achievement_created` (qualquer outro valor nunca dispara).
307
+ - Confirme que a ação realmente gera conquistas — webhooks só disparam quando `fireAction` produz `result.size() > 0`.
308
+
309
+ **Sinais no log (apenas em erro):**
310
+ ```
311
+ WEBHOOK ERROR (<id> - <event>) : URL <target_url>
312
+ <stack trace da UnirestException>
313
+ ```
314
+ Não há log de sucesso. Ausência de log não significa que a entrega ocorreu.
315
+
316
+ **Queries úteis (Mongo, banco da organização):**
317
+ ```js
318
+ db.webhook.find({}) // todas as inscrições
319
+ db.webhook.find({ event: "achievement_created" })// as que efetivamente disparam
320
+ db.webhook.find({ target_url: "https://..." }) // localizar por destino antes de unsubscribe
321
+ ```
322
+
323
+ **Comandos REST de investigação:**
324
+ ```
325
+ GET /v3/hook
326
+ GET /v3/hook/<id>
327
+ DELETE /v3/hook/<id>
328
+ POST /v3/hook/unsubscribe { "target_url": "https://..." }
329
+ ```
330
+
331
+ **Erros comuns e causas:**
332
+ - *"Cadastrei o webhook mas nada chega"* → `event` diferente de `achievement_created`; ou a ação não gerou conquista; ou `target_url` inacessível (erro foi só para stdout).
333
+ - *"Cadastrei `secret`/`headers` e não funcionam"* → campos descartados por `@JsonIgnoreProperties`.
334
+ - *"Processamento de ações ficou lento"* → `target_url` lenta; entrega é síncrona, sequencial e sem timeout.
335
+ - *"Deletei por URL e sumiram vários"* → `unsubscribe` remove todos com a mesma `target_url`.
336
+
337
+ ---
338
+
339
+ ## 10. Exemplos Práticos
340
+
341
+ ### Exemplo mínimo funcional
342
+ ```json
343
+ // POST /v3/hook
344
+ { "target_url": "https://meusistema.com/funifier", "event": "achievement_created" }
345
+ ```
346
+
347
+ ### Exemplo com `_id` controlado pelo cliente (upsert determinístico)
348
+ ```json
349
+ // POST /v3/hook → cria/atualiza sempre o mesmo documento
350
+ { "_id": "crm-prod-hook", "target_url": "https://crm.exemplo.com/funifier", "event": "achievement_created" }
351
+ ```
352
+ Reenviar com o mesmo `_id` substitui a URL — útil para reconfiguração idempotente.
353
+
354
+ ### Cancelamento
355
+ ```json
356
+ // POST /v3/hook/unsubscribe → remove TODOS os hooks com esta URL
357
+ { "target_url": "https://crm.exemplo.com/funifier" }
358
+ ```
359
+ ```
360
+ // ou, por id específico:
361
+ DELETE /v3/hook/crm-prod-hook
362
+ ```
363
+
364
+ ### Anti-pattern — o que NÃO fazer
365
+ ```json
366
+ // ❌ Evento inexistente: salva com 201, mas NUNCA dispara
367
+ { "target_url": "https://x.com/hook", "event": "challenge_completed" }
368
+
369
+ // ❌ Campos que não existem: aceitos e silenciosamente ignorados
370
+ { "target_url": "https://x.com/hook", "event": "achievement_created",
371
+ "secret": "abc", "headers": { "X-Token": "..." }, "enabled": true }
372
+
373
+ // ❌ target_url lenta/instável: bloqueia a etapa pós-avaliação de fireAction (entrega síncrona, sem timeout)
374
+ ```
375
+ Para eventos diferentes de `achievement_created`, **não use este módulo** — implemente um **Trigger** (Groovy/Java) com `Unirest.post(...)`, conforme a própria orientação interna da plataforma.
376
+
377
+ ---
378
+
379
+ ## Checklist de Configuração
38
380
 
39
- - [ ] Webhook aparece na lista
40
- - [ ] Payload é enviado quando evento ocorre
41
- - [ ] Endpoint externo recebe dados corretamente
381
+ - [ ] `target_url` acessível **e rápida** — a entrega é síncrona, sequencial e sem timeout configurado.
382
+ - [ ] `event` é exatamente `achievement_created` qualquer outro valor nunca dispara.
383
+ - [ ] O módulo Achievement está gerando conquistas para a ação esperada (`result.size() > 0`).
384
+ - [ ] Não confie em `secret`/`headers`/`enabled` no corpo — são silenciosamente ignorados.
385
+ - [ ] Ciente de que `POST /v3/hook` com `_id` existente **substitui** o documento (upsert).
386
+ - [ ] Ciente de que `unsubscribe` remove **todos** os hooks com a mesma `target_url`.
387
+ - [ ] O consumidor valida a autenticidade por conta própria — o POST de saída não é assinado.
388
+ - [ ] Caminho correto é `/v3/hook` (não `/v3/webhook`).