funifier-mcp 0.2.26 → 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 (170) 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/index.js +2 -2
  67. package/dist/mcp/index.js.map +1 -1
  68. package/dist/mcp/resources/documentation.d.ts +1 -1
  69. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  70. package/dist/mcp/resources/documentation.js +39 -3
  71. package/dist/mcp/resources/documentation.js.map +1 -1
  72. package/dist/mcp/tools/connect.d.ts.map +1 -1
  73. package/dist/mcp/tools/connect.js +18 -8
  74. package/dist/mcp/tools/connect.js.map +1 -1
  75. package/dist/mcp/tools/database.d.ts.map +1 -1
  76. package/dist/mcp/tools/database.js +59 -47
  77. package/dist/mcp/tools/database.js.map +1 -1
  78. package/dist/mcp/tools/database.test.js +2 -2
  79. package/dist/mcp/tools/database.test.js.map +1 -1
  80. package/dist/mcp/tools/delete.d.ts.map +1 -1
  81. package/dist/mcp/tools/delete.js +13 -3
  82. package/dist/mcp/tools/delete.js.map +1 -1
  83. package/dist/mcp/tools/execute.d.ts.map +1 -1
  84. package/dist/mcp/tools/execute.js +20 -9
  85. package/dist/mcp/tools/execute.js.map +1 -1
  86. package/dist/mcp/tools/folder.d.ts.map +1 -1
  87. package/dist/mcp/tools/folder.js +22 -12
  88. package/dist/mcp/tools/folder.js.map +1 -1
  89. package/dist/mcp/tools/get.d.ts.map +1 -1
  90. package/dist/mcp/tools/get.js +16 -6
  91. package/dist/mcp/tools/get.js.map +1 -1
  92. package/dist/mcp/tools/index.d.ts +1 -1
  93. package/dist/mcp/tools/index.d.ts.map +1 -1
  94. package/dist/mcp/tools/index.js +3 -1
  95. package/dist/mcp/tools/index.js.map +1 -1
  96. package/dist/mcp/tools/list.d.ts.map +1 -1
  97. package/dist/mcp/tools/list.js +38 -14
  98. package/dist/mcp/tools/list.js.map +1 -1
  99. package/dist/mcp/tools/logs.d.ts.map +1 -1
  100. package/dist/mcp/tools/logs.js +15 -5
  101. package/dist/mcp/tools/logs.js.map +1 -1
  102. package/dist/mcp/tools/save.d.ts.map +1 -1
  103. package/dist/mcp/tools/save.js +14 -4
  104. package/dist/mcp/tools/save.js.map +1 -1
  105. package/dist/mcp/tools/save.test.js +3 -3
  106. package/dist/mcp/tools/save.test.js.map +1 -1
  107. package/dist/mcp/tools/search-docs.d.ts +3 -0
  108. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  109. package/dist/mcp/tools/search-docs.js +102 -0
  110. package/dist/mcp/tools/search-docs.js.map +1 -0
  111. package/package.json +6 -2
  112. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  113. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  114. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  115. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  116. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  117. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  118. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  119. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  120. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  121. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  122. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  123. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  124. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  125. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  126. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  127. package/skills/funifier/SKILL.md +88 -0
  128. package/skills/funifier/references/configure-security.md +96 -0
  129. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  130. package/skills/funifier/references/create-aggregate.md +144 -0
  131. package/skills/funifier/references/create-challenge.md +116 -0
  132. package/skills/funifier/references/create-competition.md +98 -0
  133. package/skills/funifier/references/create-crossword.md +574 -0
  134. package/skills/funifier/references/create-custom-object.md +91 -0
  135. package/skills/funifier/references/create-custom-page.md +135 -0
  136. package/skills/funifier/references/create-folder.md +104 -0
  137. package/skills/funifier/references/create-lastmile.md +643 -0
  138. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  139. package/skills/funifier/references/create-level.md +94 -0
  140. package/skills/funifier/references/create-lottery.md +913 -0
  141. package/skills/funifier/references/create-mystery.md +769 -0
  142. package/skills/funifier/references/create-notification.md +75 -0
  143. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  144. package/skills/funifier/references/create-quiz.md +98 -0
  145. package/skills/funifier/references/create-scheduler.md +141 -0
  146. package/skills/funifier/references/create-story.md +636 -0
  147. package/skills/funifier/references/create-swap.md +95 -0
  148. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  149. package/skills/funifier/references/create-virtual-good.md +96 -0
  150. package/skills/funifier/references/create-webhook.md +72 -0
  151. package/skills/funifier/references/create-websocket.md +71 -0
  152. package/skills/funifier/references/create-widget.md +76 -0
  153. package/skills/funifier/references/debug.md +87 -0
  154. package/skills/funifier/references/help.md +81 -0
  155. package/skills/funifier/references/implement-frontend.md +106 -0
  156. package/skills/funifier/references/import-csv.md +75 -0
  157. package/skills/funifier/references/manage-player.md +82 -0
  158. package/skills/funifier/references/manage-team.md +76 -0
  159. package/skills/funifier/references/upload-file.md +91 -0
  160. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  161. package/skills/funifier-create-challenge/SKILL.md +0 -88
  162. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  163. package/skills/funifier-create-level/SKILL.md +0 -87
  164. package/skills/funifier-create-quiz/SKILL.md +0 -87
  165. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  166. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  167. package/skills/funifier-debug/SKILL.md +0 -92
  168. package/skills/funifier-help/SKILL.md +0 -86
  169. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  170. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,28 +1,493 @@
1
- # Avatar (Avatar)
1
+ # `avatar`
2
2
 
3
- ## O que é
3
+ **Acesso Studio:** não identificado neste repositório (`funifier-service` é o backend; não há UI de Studio para avatar aqui)
4
+ **Endpoint base API:** `/v3/avatar`
5
+ **Coleção MongoDB:** `avatar` (`Entity.AVATAR`, mapeada para `HashMap.class` — sem classe de entidade dedicada)
4
6
 
5
- Criação e customização de avatares para os jogadores. Permite aos jogadores criarem avatares personalizados, desbloqueando acessórios e itens de acordo com o desempenho na gamificação ou por compras na loja. Pode ser usado em ambientes 2D ou 3D.
7
+ > **Aviso de engenharia reversa.** Esta documentação foi reescrita a partir do código-fonte real de `funifier-service` (`AvatarRest.java`, `AvatarManager.java`, `Entity.java`, `PlayerManager.java`). A documentação anterior descrevia recursos (acessórios desbloqueáveis, integração com Virtual Good/loja, ambientes 2D/3D, avatar padrão para novos jogadores) que **não têm nenhuma sustentação no código** ver Seção 7.
6
8
 
7
- ## Quando usar
9
+ ---
8
10
 
9
- - Para personalização visual dos jogadores
10
- - Para criar senso de identidade e pertencimento
11
- - Para vincular recompensas a itens visuais
12
- - Para ambientes virtuais com representação do jogador
11
+ ## 1. Visão Geral
13
12
 
14
- ## Dependências
13
+ O módulo `avatar` é um **armazenamento de imagem de perfil por jogador** com um documento de chave‑valor livre por jogador. Ele faz duas coisas concretas:
15
14
 
16
- - **Virtual Good** (opcional): acessórios podem ser vendidos na loja
15
+ 1. **Persiste um documento arbitrário** (`HashMap`) na coleção `avatar`, cujo `_id` é o id do jogador. O schema é totalmente livre — qualquer campo enviado pelo cliente é gravado verbatim.
16
+ 2. **Recebe uma imagem PNG em base64**, faz upload para o storage de nuvem (S3, Azure ou Google), e grava a URL resultante **tanto** no documento `avatar` (`png_url`) **quanto** no campo `extra.avatar` do jogador.
17
17
 
18
- ## Checklist de Configuração
18
+ Arquiteturalmente o módulo é mínimo:
19
+
20
+ - `AvatarRest` (`com.funifier.rest.v3.rest.AvatarRest`) — recurso JAX‑RS em `@Path("v3/avatar")`.
21
+ - `AvatarManager` (`com.funifier.engine.avatar.AvatarManager`) — manager fino sobre Jongo/MongoDB. CRUD direto na coleção, sem regras de negócio.
22
+ - Sem entidade, sem DTO, sem scheduler, sem aggregate dedicado, sem enums.
23
+
24
+ **Relação com outros módulos:** praticamente nenhuma. `Entity.AVATAR` é referenciada **exclusivamente** dentro de `AvatarManager.java`; `getAvatarManager()` é chamado apenas por `ManagerFactory` (definição) e `AvatarRest`. O único acoplamento real é com o módulo **Player**: o upload de imagem dispara `PlayerManager.insert(player)` (ver Seção 6), com todos os efeitos colaterais de uma atualização de jogador.
25
+
26
+ **Problema que resolve:** guardar a imagem/representação visual de um jogador e disponibilizá‑la via URL pública. Nada além disso está implementado.
27
+
28
+ ---
29
+
30
+ ## 2. Arquitetura e Fluxos
31
+
32
+ ### 2.1 Pipeline principal — `POST /v3/avatar` → `AvatarRest.save()`
33
+
34
+ Sequência exata (`AvatarRest.save`, linhas 191‑282):
35
+
36
+ ```
37
+ [Trigger] POST /v3/avatar com Map<String,Object> avatar (JSON)
38
+ [Resolve player] player = avatar.get("_id")
39
+ se "me" → player = authBean.getPlayerFromTokenIfExist(); avatar.put("_id", player)
40
+ [Carrega player] p = PlayerManager.findById(player)
41
+ [Normaliza nome] name = Normalizer NFD + remove acentos/não‑alfanuméricos + lowercase + trim
42
+ [Lê imagem] png = (String) avatar.get("png") // svg NÃO é lido (linha comentada)
43
+ [Cloud] cloud = Configuration.getCurrentConfiguration().getCloud() // global, não por‑tenant
44
+ [Limpa antiga] current = AvatarManager.findById(player)
45
+ se current != null e current.png_url tem >5 chars → delete na nuvem (azure/google/s3)
46
+ [Processa png] se png != null E png.length() > 20:
47
+ valida prefixo "data:image/png" + "base64"
48
+ se válido: bytes = Base64.decode(...)
49
+ url = <Cloud>Service.put(bytes, name+".png", apiKey, "avatar")
50
+ p.extra.put("avatar", url)
51
+ PlayerManager.insert(p) // ← efeito colateral pesado (ver 2.2)
52
+ avatar.put("png_url", url)
53
+ avatar.put("status", "saved")
54
+ senão: avatar.put("status", "error")
55
+ [Persiste avatar] AvatarManager.save(avatar) → Jongo c.save(avatar) // upsert/replace por _id
56
+ [Resposta] 201 CREATED + JSON do avatar (campos null removidos)
57
+ ```
58
+
59
+ Pseudocódigo do gate de processamento de imagem (note os silêncios):
60
+
61
+ ```
62
+ se png == null OU png.length() <= 20:
63
+ não processa imagem (status NÃO é setado, png_url NÃO é setado)
64
+ senão se png não casa "data:image/png" + "base64":
65
+ avatar.status = "error" (nenhuma imagem é enviada)
66
+ senão:
67
+ faz upload, seta player.extra.avatar, insert(player), avatar.png_url, avatar.status = "saved"
68
+ ```
69
+
70
+ #### Fluxo de avaliação — `POST /v3/avatar`
71
+
72
+ ```mermaid
73
+ flowchart TD
74
+ A[POST /v3/avatar - JSON] --> B{_id == 'me'?}
75
+ B -- sim --> C[_id = player do token]
76
+ B -- nao --> D[usa _id do payload]
77
+ C --> E[PlayerManager.findById _id]
78
+ D --> E
79
+ E --> F[cloud = Configuration.getCloud - global]
80
+ F --> G{avatar atual tem png_url > 5 chars?}
81
+ G -- sim --> H[deleteURL imagem antiga - azure/google/s3]
82
+ G -- nao --> I{png != null E length > 20?}
83
+ H --> I
84
+ I -- nao --> M[AvatarManager.save - documento]
85
+ I -- sim --> J{"prefixo data:image/png;base64 valido?"}
86
+ J -- nao --> K[avatar.status = error]
87
+ J -- sim --> L[upload name.png -> png_url<br/>player.extra.avatar = url<br/>PlayerManager.insert p<br/>avatar.status = saved]
88
+ K --> M
89
+ L --> M
90
+ M --> N[201 CREATED + JSON sem campos null]
91
+ ```
92
+
93
+ > **Síncrono.** Todo o fluxo é síncrono, sem transação. Se `AvatarManager.save` falhar **depois** de `PlayerManager.insert(p)`, o jogador já terá sido atualizado e a imagem já estará na nuvem — não há rollback.
94
+
95
+ ### 2.2 Efeito colateral oculto — `PlayerManager.insert(p)`
96
+
97
+ Esta é a parte **não óbvia**. Salvar uma imagem de avatar **não** apenas grava uma URL: ela re‑persiste o jogador inteiro através de `PlayerManager.insert(player)` (linhas 244‑313 de `PlayerManager.java`), que dispara triggers, auditoria e sincronização de principal/times. Importante: `insert` é chamado **sem `AuthBean`**, então o log de auditoria fica com contexto de usuário nulo.
98
+
99
+ #### Cascata de efeitos — `PlayerManager.insert(p)`
100
+
101
+ ```mermaid
102
+ sequenceDiagram
103
+ participant R as AvatarRest.save
104
+ participant PM as PlayerManager.insert
105
+ participant TM as TriggerManager
106
+ participant DB as MongoDB
107
+ participant AM as AuditManager
108
+ R->>PM: insert(player) [authBean = null]
109
+ PM->>DB: findOne principal {userId}
110
+ alt principal existe (update)
111
+ PM->>TM: execute(PLAYER, before_update)
112
+ PM->>DB: save(player) na colecao player
113
+ PM->>DB: save(principal)
114
+ PM->>TM: execute(PLAYER, after_update)
115
+ PM->>AM: log(player, EVENT_UPDATE, authBean=null)
116
+ else principal inexistente (create)
117
+ PM->>TM: execute(PLAYER, before_create)
118
+ PM->>DB: save(player)
119
+ PM->>DB: save(principal novo)
120
+ PM->>TM: execute(PLAYER, after_create)
121
+ PM->>AM: log(player, EVENT_CREATE, authBean=null)
122
+ end
123
+ PM->>DB: recalcula links de time do jogador
124
+ PM->>DB: player.updated = now
125
+ ```
126
+
127
+ ### 2.3 Endpoints legados (form‑urlencoded e download)
128
+
129
+ `save_temp_image`, `save_ready_image` e `download_temp_image` são endpoints antigos que **não tocam a coleção `avatar`** — eles operam apenas sobre `player.extra.avatar`. Ver Seções 4 e 7. Há ainda três métodos **totalmente comentados** no fim de `AvatarRest.java` (linhas 409‑588): uma versão antiga de `saveTempImage`/`saveReadyImage` que escrevia no filesystem local e um `downloadReadyImage`. Não estão ativos.
130
+
131
+ ---
132
+
133
+ ## 3. Estrutura dos Objetos
134
+
135
+ ### 3.1 Documento `avatar` — coleção `avatar`
136
+
137
+ **Não existe classe de entidade.** `Entity.AVATAR` é mapeada para `HashMap.class`; o documento é um mapa livre. A tabela abaixo lista apenas os campos que o **código** conhece explicitamente. Qualquer outro campo enviado pelo cliente é persistido verbatim, sem validação.
138
+
139
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
140
+ | --------- | ------ | ------ | ----------- | --------- |
141
+ | `_id` | String | — | **Sim** | Id do jogador. É a chave do documento (1 avatar por jogador). Se ausente, `AvatarManager.save` **não faz nada silenciosamente** (sem erro, sem log). |
142
+ | `png` | String | — | Não | Imagem em data‑URI base64. Só é processada se `length > 20` e casar `data:image/png;base64,...`. **Não é removida antes de persistir** — fica gravada no documento (ver abaixo). |
143
+ | `png_url` | String | — | Não (server) | URL pública gerada após o upload. **Adicionada pelo servidor** durante o `save` quando a imagem é processada com sucesso. |
144
+ | `status` | String | — | Não (server) | `"saved"` (upload OK) ou `"error"` (data‑URI inválido). **Adicionada pelo servidor**. Não é setada quando `png` é nulo/curto. |
145
+ | `svg` | String | — | Não | **Aceito no payload mas IGNORADO** no endpoint JSON (`String svg = avatar.get("svg")` está comentado, linha 211). Persiste verbatim se enviado, mas nunca é processado. |
146
+ | *(livre)* | qualquer | — | Não | Qualquer chave adicional é gravada como veio. Sem schema, sem coerção de tipo. |
147
+
148
+ **Campos adicionados pelo servidor (não enviados pelo cliente):** `png_url`, `status`.
149
+
150
+ **Campo gravado de forma indesejada (legado):** `png`. O `avatar.remove("png")` está **comentado** (linhas 267‑268), então o **data‑URI base64 inteiro fica armazenado no MongoDB** ao lado de `png_url`. Isso infla o documento e duplica o conteúdo da imagem dentro do banco.
151
+
152
+ **Sem enums, sem subentidades, sem técnicas de jogo (GT).** O módulo não possui nenhuma estrutura aninhada nem catálogo de itens.
153
+
154
+ ### 3.2 Campo relacionado em `Player` — `extra.avatar`
155
+
156
+ | Campo (em `player`) | Tipo | Origem | Descrição |
157
+ | ------------------- | ---- | ------ | --------- |
158
+ | `extra.avatar` | String (URL) | gravado por todos os endpoints de upload | URL da última imagem de avatar enviada. **Escrito mas nunca lido** pelo backend — nenhum outro código em `funifier-service` lê `player.extra.get("avatar")`. |
159
+
160
+ ---
161
+
162
+ ## 4. Endpoints
163
+
164
+ ### `POST /v3/avatar`
165
+
166
+ | Aspecto | Detalhe |
167
+ | --------------------- | ------- |
168
+ | Método handler | `AvatarRest.save` |
169
+ | Consumes | `application/json` |
170
+ | Finalidade | Cria/atualiza o documento de avatar do jogador e, opcionalmente, faz upload da imagem PNG. |
171
+ | Autenticação | Bearer token (ou Basic/Studio/Account, ou `api_key`+`access_token`) |
172
+ | Full replace ou patch | **Full replace.** Jongo `c.save(map)` substitui o documento inteiro pelo `_id`. Campos omitidos no payload **somem** do documento. |
173
+ | Resolução `_id` | `"me"` → id do player extraído do token |
174
+
175
+ **Comportamento real:**
176
+
177
+ - O `_id` define o jogador; não há geração automática de id.
178
+ - Quando `png` é processado, há um **efeito colateral em `player`** (Seção 2.2) e um **delete da imagem anterior** na nuvem.
179
+ - O servidor injeta `png_url` e `status` no documento retornado.
180
+ - A resposta passa por `JsonUtil.toJsonRemoveNullFields` — campos com valor `null` **não aparecem** no JSON de retorno.
181
+ - Se o jogador não existir (`PlayerManager.findById` retorna `null`) **e** `png` for válido, ocorre **NullPointerException** em `p.extra.put(...)` (não há null‑check). Sem `png`, o jogador nunca é desreferenciado.
182
+
183
+ **Exemplo de request:**
184
+
185
+ ```json
186
+ {
187
+ "_id": "player123",
188
+ "png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ..."
189
+ }
190
+ ```
191
+
192
+ **Exemplo de response (201):**
193
+
194
+ ```json
195
+ {
196
+ "_id": "player123",
197
+ "png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ...",
198
+ "png_url": "https://<storage-do-tenant>/avatar/player123.png",
199
+ "status": "saved"
200
+ }
201
+ ```
202
+
203
+ > Note que `png` (o data‑URI inteiro) é devolvido **e** persistido, ao lado de `png_url`.
204
+
205
+ ---
206
+
207
+ ### `GET /v3/avatar/{id}`
208
+
209
+ | Aspecto | Detalhe |
210
+ | ------------ | ------- |
211
+ | Método handler | `AvatarRest.find` → `AvatarManager.findById` |
212
+ | Produces | `application/json` |
213
+ | Finalidade | Retorna o documento de avatar de um jogador. |
214
+ | Autenticação | Bearer token |
215
+
216
+ **Path params:**
217
+
218
+ | Param | Tipo | Descrição |
219
+ | ----- | ---- | --------- |
220
+ | `id` | String | Id do jogador. `"me"` → id do token. |
221
+
222
+ **Comportamento real:** `c.findOne("{_id:#}", id)`. Retorna o documento (com campos null removidos no JSON). Se não existir, retorna `null` serializado.
223
+
224
+ ---
225
+
226
+ ### `GET /v3/avatar`
227
+
228
+ | Aspecto | Detalhe |
229
+ | ------------ | ------- |
230
+ | Método handler | `AvatarRest.findAll` → `AvatarManager.findAll` |
231
+ | Produces | `application/json` |
232
+ | Finalidade | Lista documentos de avatar via pipeline de agregação MongoDB. |
233
+ | Autenticação | Bearer token |
234
+
235
+ **Query params:**
236
+
237
+ | Param | Tipo | Padrão | Descrição |
238
+ | ------------- | ------- | ------ | --------- |
239
+ | `id` | String | — | Filtra por `_id` exato. |
240
+ | `q` | String | — | **Fragmento de filtro Mongo concatenado cru** dentro do `$match` (ver Seção 8 — superfície de injeção). |
241
+ | `fields` | String | — | Lista separada por vírgula; vira `$project { campo:1, ... }`. |
242
+ | `orderby` | String | — | Campo de ordenação; vira `$sort { orderby: ±1 }`. |
243
+ | `reverse` | boolean | false | `true` → ordem decrescente (`-1`); senão `1`. Parse tolerante (exceção engolida). |
244
+ | `max_results` | int | 100 | `$limit`. `<= 0` ou não numérico → cai para **100**. |
245
+
246
+ **Query MongoDB real construída:**
247
+
248
+ ```
249
+ { $match : { _id: {$exists:true} [, _id: <id>] [, <q literal>] } }
250
+ [ { $project : { <campo>:1, ... } } ]
251
+ [ { $sort : { <orderby> : 1|-1 } } ]
252
+ { $limit : <max_results | 100> }
253
+ ```
254
+
255
+ ---
256
+
257
+ ### `DELETE /v3/avatar/{id}`
258
+
259
+ | Aspecto | Detalhe |
260
+ | ------------ | ------- |
261
+ | Método handler | `AvatarRest.delete` → `AvatarManager.delete` |
262
+ | Finalidade | Remove o documento de avatar e, se houver, a imagem na nuvem. |
263
+ | Autenticação | Bearer token |
264
+ | Resposta | `204 NO_CONTENT` |
265
+
266
+ **Path params:** `id` (String) — `"me"` → id do token.
267
+
268
+ **Comportamento real (inconsistência confirmada):** se o avatar tiver `png_url`, o delete da imagem é feito **apenas via `S3Service.deleteURL`** — **não é cloud‑aware**, diferente do `save` (que escolhe azure/google/s3). Em tenants Azure/Google a imagem **não é removida** da nuvem. Em seguida `c.remove("{_id:#}", id)` apaga o documento.
269
+
270
+ ---
271
+
272
+ ### `POST /v3/avatar/save_temp_image` *(legado)*
19
273
 
20
- - [ ] Definir tipos de avatar disponíveis
21
- - [ ] Criar acessórios e itens desbloqueáveis
22
- - [ ] Vincular itens a desafios ou loja virtual
274
+ | Aspecto | Detalhe |
275
+ | ------------ | ------- |
276
+ | Método handler | `AvatarRest.saveTempImage` |
277
+ | Consumes | `application/x-www-form-urlencoded` |
278
+ | Produces | `text/plain` |
279
+ | Finalidade | Upload de imagem (PNG ou SVG) gravando a URL **apenas** em `player.extra.avatar`. |
23
280
 
24
- ## Validações e Testes
281
+ **Form params:** `filename`, `imgdata`, `player` (`"me"` suportado).
282
+
283
+ **Comportamento real:** decodifica PNG base64 ou valida SVG, faz upload (cloud‑aware: azure/google/s3), `p.extra.put("avatar", url)`, `PlayerManager.insert(p)`. **Não grava nada na coleção `avatar`.** Retorna o texto `"saved"` ou `"error"`. O nome do arquivo enviado é usado direto (sem normalização do `save` JSON).
284
+
285
+ ---
286
+
287
+ ### `POST /v3/avatar/save_ready_image` *(legado)*
288
+
289
+ | Aspecto | Detalhe |
290
+ | ------------ | ------- |
291
+ | Método handler | `AvatarRest.saveReadyImage` |
292
+ | Consumes | `application/x-www-form-urlencoded` |
293
+ | Produces | `text/plain` |
294
+ | Finalidade | Igual ao `save_temp_image`, porém **somente S3**. |
295
+
296
+ **Comportamento real:** idêntico ao `save_temp_image`, mas o upload usa **`S3Service.put` fixo** (não cloud‑aware). Grava só em `player.extra.avatar` + `PlayerManager.insert(p)`. Não toca a coleção `avatar`.
297
+
298
+ ---
299
+
300
+ ### `GET /v3/avatar/download_temp_image` *(legado / quebrado)*
301
+
302
+ | Aspecto | Detalhe |
303
+ | ------------ | ------- |
304
+ | Método handler | `AvatarRest.downloadTempImage` |
305
+ | Produces | `image/png` |
306
+ | Finalidade | Lê e devolve um arquivo de imagem do disco local. |
307
+
308
+ **Query params:** `filename`, `filetype` (`png`/`svg`).
309
+
310
+ **Comportamento real:** lê `Files.readAllBytes(Paths.get("/home/funifier/xti/projetos/cds/WebContent/avatar/temp-avatars/" + filename))`. **Caminho local fixo** — não funciona em deploys de nuvem (o storage é S3/Azure/Google). Além disso, `filename` é concatenado sem sanitização → **superfície de path traversal** (Seção 8). Em erro de I/O retorna `200 OK` com corpo `"error"`.
311
+
312
+ ---
313
+
314
+ ## 5. Regras de Negócio
315
+
316
+ Regras que **existem no código** mas não em nenhum schema:
317
+
318
+ - **Um avatar por jogador.** O `_id` do documento é o id do jogador; não há coleção de itens nem múltiplos avatares.
319
+ - **`_id` obrigatório no `save`.** `AvatarManager.save` faz no‑op silencioso se `_id` estiver ausente — a chamada retorna sucesso aparente mas nada é gravado.
320
+ - **Gate de tamanho da imagem:** `png` só é processado se `length > 20`. Valores menores são ignorados sem erro.
321
+ - **Formato aceito no `save` JSON:** somente `data:image/png;base64,...`. SVG é ignorado neste endpoint.
322
+ - **Limpeza da imagem anterior:** no `save`, se já existe `png_url`, a imagem antiga é deletada da nuvem antes do novo upload (cloud‑aware).
323
+ - **Provedor de nuvem é global, não por tenant:** `Configuration.getCurrentConfiguration().getCloud()` decide entre `azure`/`google`/default(S3) para toda a instância.
324
+ - **Multi‑tenant por API key:** o tenant é resolvido por `FrontController.getInstance(apiKey)`; cada tenant tem sua própria conexão/coleção `avatar`.
325
+ - **Sem consistência transacional:** upload na nuvem, `insert(player)` e `save(avatar)` são passos independentes; falha parcial deixa estado inconsistente.
326
+
327
+ ---
328
+
329
+ ## 6. Comportamentos Automáticos
330
+
331
+ | Comportamento | Trigger | Impacto | Persistência |
332
+ | ------------- | ------- | ------- | ------------ |
333
+ | Atualização de `player.extra.avatar` | Upload bem‑sucedido em `save`, `save_temp_image`, `save_ready_image` | URL da imagem gravada no jogador | Coleção `player` |
334
+ | `PlayerManager.insert(player)` | Mesmo upload | Re‑persiste o jogador inteiro | Coleção `player` + `principal` + `team_player` |
335
+ | Triggers de Player | Dentro de `insert` | Dispara `before_update`/`after_update` (ou `before_create`/`after_create`) para `ENTITY_PLAYER` | Conforme triggers configurados |
336
+ | Log de auditoria | Dentro de `insert` | `AuditManager.log(player, EVENT_UPDATE/CREATE, authBean=null)` — **sem contexto de usuário** | Coleção `audit_log` |
337
+ | Sincronização de Principal | Dentro de `insert` | Cria/atualiza `principal` do jogador | Coleção `principal` |
338
+ | Recalcula `player.updated` | Dentro de `insert` | Timestamp atualizado | Coleção `player` |
339
+ | Delete da imagem antiga | `save` quando já existe `png_url` | Remove arquivo anterior na nuvem (cloud‑aware) | Storage de nuvem |
340
+ | Inclusão de `png_url` + `status` | `save` | Campos adicionados ao documento | Coleção `avatar` |
341
+
342
+ > **Atenção:** trocar a foto de avatar de um jogador dispara silenciosamente a cadeia de atualização de Player (triggers + auditoria). Triggers de `after_update` em `ENTITY_PLAYER` serão executados a cada upload de avatar.
343
+
344
+ ---
345
+
346
+ ## 7. Suportado vs NÃO Suportado
347
+
348
+ ### ✅ Suportado (confirmado no código)
349
+
350
+ - CRUD de um documento livre por jogador na coleção `avatar` (`save`/`find`/`findAll`/`delete`).
351
+ - Upload de imagem **PNG base64** via `POST /v3/avatar` (JSON), com armazenamento em S3/Azure/Google.
352
+ - Upload via endpoints legados form‑urlencoded (`save_temp_image`, `save_ready_image`) gravando em `player.extra.avatar`.
353
+ - Resolução de `"me"` para o jogador do token em todos os endpoints.
354
+ - Listagem com filtro (`q`), projeção (`fields`), ordenação (`orderby`/`reverse`) e limite (`max_results`).
355
+ - Limpeza da imagem anterior na nuvem ao re‑salvar (no `save` JSON).
356
+
357
+ ### ❌ NÃO Suportado (declarado em documentação antiga ou no payload, mas sem implementação)
358
+
359
+ - **Acessórios / itens desbloqueáveis** — inexistentes no código. Não há catálogo, item, nem desbloqueio.
360
+ - **Integração com Virtual Good / loja** — inexistente. `Entity.AVATAR` é usada **apenas** em `AvatarManager`; nenhum outro manager lê/escreve a coleção `avatar`.
361
+ - **Ambientes 2D/3D** — nenhum conceito de dimensão/cena/modelo no código.
362
+ - **Avatar padrão para novos jogadores** — não há provisionamento automático; um jogador sem documento simplesmente não tem avatar.
363
+ - **Tipos de avatar configuráveis** — não existe enum nem configuração de tipos.
364
+ - **Campo `svg` no endpoint JSON** — aceito no payload mas **ignorado** (linha de leitura comentada). Persiste verbatim sem processamento.
365
+ - **`download_temp_image` em produção/nuvem** — lê de caminho de filesystem local fixo; quebrado em qualquer deploy de nuvem.
366
+ - **DELETE cloud‑aware** — o delete da imagem usa só S3; em Azure/Google a imagem fica órfã na nuvem.
367
+ - **Remoção do `png` antes de persistir** — código comentado; o data‑URI base64 acaba gravado no MongoDB.
368
+ - **Métodos comentados** (`saveTempImage`/`saveReadyImage`/`downloadReadyImage` baseados em filesystem) — código morto, não roteado.
369
+
370
+ ---
371
+
372
+ ## 8. Segurança e Permissões
373
+
374
+ - **Autenticação:** via `AuthBean` — header `Authorization` (`Bearer`, `Basic`, `Studio`, `Account`) ou `api_key`/`access_token` (query ou headers `X-Api-Key`/`X-Access-Token`).
375
+ - **Validação de token não explícita neste controller:** `AvatarRest` resolve o tenant com `FrontController.getInstance(authBean.getApiKey()).getManagerFactory()` — **não** invoca o caminho `authBean.getManagerFactory()` que chama `.check(accessToken)`. Ou seja, o `apiKey` (extraído/derivado do token) determina o tenant, mas o passo explícito de validação de `access_token` deste recurso não é executado. Não significa "sem autenticação" (o `apiKey` ainda vem do token), mas é uma diferença de rigor frente a recursos que chamam `.check(...)`.
376
+ - **Isolamento multi‑tenant:** garantido pela conexão por `apiKey` (`FrontController.getInstance(apiKey)`); cada tenant acessa sua própria coleção `avatar`.
377
+ - **Injeção em pipeline MongoDB (`findAll`):** os parâmetros `q`, `fields` e `orderby` são **concatenados crus** na string do pipeline de agregação:
378
+ - `q`: `query.append(", " + q)` dentro do `$match` — permite injetar operadores/condições arbitrárias.
379
+ - `fields`: split por vírgula e concatenado no `$project`.
380
+ - `orderby`: concatenado no `$sort`.
381
+ - Não há allow‑list nem escaping. Trate como **superfície de injeção NoSQL**.
382
+ - **Path traversal em `download_temp_image`:** `filename` é concatenado direto em `Paths.get(".../temp-avatars/" + filename)` sem sanitização → possível leitura de arquivos fora do diretório (`../`). Endpoint legado/quebrado, mas presente.
383
+ - **Vazamento em logs:** vários `System.out.println` imprimem `apiKey`, `player` e um trecho base64 da imagem (`AvatarRest.downloadTempImage`, `saveTempImage`, `save`). Dados sensíveis em log de aplicação.
384
+ - **Auditoria com usuário nulo:** o upload chama `PlayerManager.insert(p)` sem `AuthBean`, então o `audit_log` da atualização do jogador não registra qual usuário disparou a ação.
385
+
386
+ ---
387
+
388
+ ## 9. Observabilidade e Troubleshooting
389
+
390
+ **Como verificar se o módulo está funcionando**
391
+
392
+ ```
393
+ # Recupera o avatar de um jogador
394
+ GET /v3/avatar/player123
395
+
396
+ # Recupera o avatar do jogador autenticado
397
+ GET /v3/avatar/me
398
+
399
+ # Lista avatares com imagem (filtro via q — sintaxe Mongo crua)
400
+ GET /v3/avatar?q={ "png_url" : { "$exists" : true } }&max_results=20
401
+
402
+ # Projeta apenas campos relevantes
403
+ GET /v3/avatar?fields=_id,png_url,status
404
+ ```
405
+
406
+ **Queries Mongo úteis (investigação direta no banco do tenant):**
407
+
408
+ ```js
409
+ // Avatar de um jogador
410
+ db.avatar.findOne({ _id: "player123" })
411
+
412
+ // Documentos que ainda guardam o base64 inflado (png presente)
413
+ db.avatar.find({ png: { $exists: true } }, { _id: 1, png_url: 1, status: 1 })
414
+
415
+ // Avatares com status de erro de upload
416
+ db.avatar.find({ status: "error" })
417
+
418
+ // Conferir a URL gravada no jogador
419
+ db.player.findOne({ _id: "player123" }, { "extra.avatar": 1 })
420
+ ```
421
+
422
+ **Erros comuns e causas**
423
+
424
+ | Sintoma | Causa provável |
425
+ | ------- | -------------- |
426
+ | `save` retorna 500 / NPE | `png` presente mas o jogador (`_id`) não existe — `findById` retorna null e `p.extra.put` falha. Crie o jogador antes. |
427
+ | `status: "error"` no retorno | `png` não casa `data:image/png;base64,...` (formato/prefixo inválido). |
428
+ | Documento salvo mas sem `png_url`/`status` | `png` ausente ou `length <= 20` — imagem não foi processada. |
429
+ | Nada é gravado, mas a chamada "passa" | `_id` ausente no payload — `AvatarManager.save` faz no‑op silencioso. |
430
+ | Imagem antiga continua na nuvem após DELETE | Tenant em Azure/Google — DELETE só remove via S3 (não cloud‑aware). |
431
+ | `download_temp_image` sempre retorna "error" | Caminho local fixo inexistente no deploy de nuvem. |
432
+ | Triggers/auditoria de Player disparando "do nada" | Upload de avatar chama `PlayerManager.insert` e dispara o ciclo de atualização do jogador. |
433
+
434
+ ---
435
+
436
+ ## 10. Exemplos Práticos
437
+
438
+ ### 10.1 Exemplo mínimo funcional (documento sem imagem)
439
+
440
+ Grava metadados de avatar sem upload de imagem. Útil para guardar configuração de avatar gerado no cliente.
441
+
442
+ ```http
443
+ POST /v3/avatar
444
+ Authorization: Bearer <token>
445
+ Content-Type: application/json
446
+
447
+ { "_id": "player123", "skin": "default", "hat": "none" }
448
+ ```
449
+
450
+ → documento `{ _id, skin, hat }` salvo na coleção `avatar`. Sem efeito colateral em `player`.
451
+
452
+ ### 10.2 Exemplo avançado (upload de imagem PNG)
453
+
454
+ ```http
455
+ POST /v3/avatar
456
+ Authorization: Bearer <token>
457
+ Content-Type: application/json
458
+
459
+ {
460
+ "_id": "me",
461
+ "png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
462
+ }
463
+ ```
464
+
465
+ → resolve `me` para o player do token, faz upload do PNG, grava `png_url` + `status:"saved"` no documento, escreve `player.extra.avatar` e **dispara `PlayerManager.insert`** (triggers + auditoria do jogador).
466
+
467
+ ### 10.3 Anti‑pattern (o que NÃO fazer)
468
+
469
+ ❌ **Confiar no `save_ready_image`/`save_temp_image` para popular a coleção `avatar`.**
470
+ Esses endpoints gravam **somente** `player.extra.avatar` — `GET /v3/avatar/{id}` retornará vazio. Use `POST /v3/avatar` (JSON) se você precisa do documento na coleção.
471
+
472
+ ❌ **Reenviar o `png` (data‑URI) a cada `GET`/`PUT`.**
473
+ O `png` não é removido antes de persistir; reenviá‑lo incha o documento com a imagem base64 duplicada (já existe `png_url`). Envie `png` apenas quando a imagem mudar; para metadados, omita `png`.
474
+
475
+ ❌ **Esperar atomicidade.**
476
+ Não há transação. Se o `save` do documento falhar após o upload, a imagem na nuvem e a atualização do jogador permanecem. Trate a operação como eventualmente consistente.
477
+
478
+ ❌ **Usar `q`/`orderby`/`fields` com entrada de usuário não confiável.**
479
+ São concatenados crus no pipeline — injeção NoSQL. Valide/allow‑liste no seu backend antes de repassar.
480
+
481
+ ---
482
+
483
+ ## Checklist de Configuração
25
484
 
26
- - [ ] Avatar padrão é exibido para novos jogadores
27
- - [ ] Acessórios desbloqueados aparecem disponíveis
28
- - [ ] Personalização do avatar é salva corretamente
485
+ - [ ] **`_id` preenchido** no payload do `POST /v3/avatar` — sem ele o save é um no‑op silencioso.
486
+ - [ ] **Jogador existe** antes de enviar `png` — `png` + jogador inexistente = NPE.
487
+ - [ ] **Formato da imagem** é exatamente `data:image/png;base64,...` (SVG não é processado no endpoint JSON).
488
+ - [ ] **`png` com mais de 20 caracteres** — abaixo disso é ignorado sem erro.
489
+ - [ ] **Ciente do efeito colateral:** salvar imagem dispara `PlayerManager.insert` (triggers + auditoria do Player).
490
+ - [ ] **Provedor de nuvem** (`getCloud()`) configurado corretamente — DELETE só é cloud‑aware para S3.
491
+ - [ ] **Armadilha:** omitir campos no `save` os remove do documento (full replace, não patch).
492
+ - [ ] **Armadilha:** reenviar `png` grava o base64 inteiro no MongoDB ao lado de `png_url`.
493
+ - [ ] **Segurança:** sanitizar/allow‑listar `q`, `fields`, `orderby` antes de expor o `findAll` a clientes não confiáveis.