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.
- package/.cursor/rules/funifier.mdc +38 -41
- package/.github/copilot-instructions.md +38 -41
- package/AGENTS.md +56 -49
- package/README.md +40 -22
- package/datasource-funifier-docs/.coverage.json +326 -0
- package/datasource-funifier-docs/.validation.json +593 -0
- package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
- package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
- package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
- package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
- package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
- package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
- package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
- package/datasource-funifier-docs/knowledge/index.md +4 -1
- package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
- package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
- package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
- package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
- package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
- package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
- package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
- package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
- package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
- package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
- package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
- package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
- package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
- package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
- package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
- package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
- package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
- package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
- package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
- package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
- package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
- package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
- package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
- package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
- package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
- package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
- package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
- package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
- package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
- package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
- package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
- package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
- package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
- package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
- package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
- package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
- package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
- package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
- package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
- package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +42 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.js +74 -3
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/persona.d.ts +3 -0
- package/dist/cli/persona.d.ts.map +1 -0
- package/dist/cli/persona.js +25 -0
- package/dist/cli/persona.js.map +1 -0
- package/dist/mcp/bundle.js +119 -93
- package/dist/mcp/check-update.d.ts +5 -0
- package/dist/mcp/check-update.d.ts.map +1 -1
- package/dist/mcp/check-update.js +21 -10
- package/dist/mcp/check-update.js.map +1 -1
- package/dist/mcp/check-update.test.d.ts +2 -0
- package/dist/mcp/check-update.test.d.ts.map +1 -0
- package/dist/mcp/check-update.test.js +33 -0
- package/dist/mcp/check-update.test.js.map +1 -0
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/prompts/templates.d.ts.map +1 -1
- package/dist/mcp/prompts/templates.js +35 -0
- package/dist/mcp/prompts/templates.js.map +1 -1
- package/dist/mcp/resources/documentation.d.ts +1 -1
- package/dist/mcp/resources/documentation.d.ts.map +1 -1
- package/dist/mcp/resources/documentation.js +39 -3
- package/dist/mcp/resources/documentation.js.map +1 -1
- package/dist/mcp/tools/connect.d.ts.map +1 -1
- package/dist/mcp/tools/connect.js +18 -8
- package/dist/mcp/tools/connect.js.map +1 -1
- package/dist/mcp/tools/database.d.ts.map +1 -1
- package/dist/mcp/tools/database.js +59 -47
- package/dist/mcp/tools/database.js.map +1 -1
- package/dist/mcp/tools/database.test.js +2 -2
- package/dist/mcp/tools/database.test.js.map +1 -1
- package/dist/mcp/tools/delete.d.ts.map +1 -1
- package/dist/mcp/tools/delete.js +13 -3
- package/dist/mcp/tools/delete.js.map +1 -1
- package/dist/mcp/tools/execute.d.ts.map +1 -1
- package/dist/mcp/tools/execute.js +20 -9
- package/dist/mcp/tools/execute.js.map +1 -1
- package/dist/mcp/tools/folder.d.ts.map +1 -1
- package/dist/mcp/tools/folder.js +22 -12
- package/dist/mcp/tools/folder.js.map +1 -1
- package/dist/mcp/tools/get.d.ts.map +1 -1
- package/dist/mcp/tools/get.js +16 -6
- package/dist/mcp/tools/get.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +28 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/list.d.ts.map +1 -1
- package/dist/mcp/tools/list.js +38 -14
- package/dist/mcp/tools/list.js.map +1 -1
- package/dist/mcp/tools/logs.d.ts.map +1 -1
- package/dist/mcp/tools/logs.js +15 -5
- package/dist/mcp/tools/logs.js.map +1 -1
- package/dist/mcp/tools/save.d.ts.map +1 -1
- package/dist/mcp/tools/save.js +14 -4
- package/dist/mcp/tools/save.js.map +1 -1
- package/dist/mcp/tools/save.test.js +3 -3
- package/dist/mcp/tools/save.test.js.map +1 -1
- package/dist/mcp/tools/search-docs.d.ts +3 -0
- package/dist/mcp/tools/search-docs.d.ts.map +1 -0
- package/dist/mcp/tools/search-docs.js +102 -0
- package/dist/mcp/tools/search-docs.js.map +1 -0
- package/package.json +6 -2
- package/skills/acquire-funifier-knowledge/SKILL.md +155 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +86 -0
- package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
- package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
- package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
- package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
- package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
- package/skills/funifier/SKILL.md +88 -0
- package/skills/funifier/references/configure-security.md +96 -0
- package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
- package/skills/funifier/references/create-aggregate.md +144 -0
- package/skills/funifier/references/create-challenge.md +116 -0
- package/skills/funifier/references/create-competition.md +98 -0
- package/skills/funifier/references/create-crossword.md +574 -0
- package/skills/funifier/references/create-custom-object.md +91 -0
- package/skills/funifier/references/create-custom-page.md +135 -0
- package/skills/funifier/references/create-folder.md +104 -0
- package/skills/funifier/references/create-lastmile.md +643 -0
- package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
- package/skills/funifier/references/create-level.md +94 -0
- package/skills/funifier/references/create-lottery.md +913 -0
- package/skills/funifier/references/create-mystery.md +769 -0
- package/skills/funifier/references/create-notification.md +75 -0
- package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
- package/skills/funifier/references/create-quiz.md +98 -0
- package/skills/funifier/references/create-scheduler.md +141 -0
- package/skills/funifier/references/create-story.md +636 -0
- package/skills/funifier/references/create-swap.md +95 -0
- package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
- package/skills/funifier/references/create-virtual-good.md +96 -0
- package/skills/funifier/references/create-webhook.md +72 -0
- package/skills/funifier/references/create-websocket.md +71 -0
- package/skills/funifier/references/create-widget.md +76 -0
- package/skills/funifier/references/debug.md +87 -0
- package/skills/funifier/references/help.md +81 -0
- package/skills/funifier/references/implement-frontend.md +106 -0
- package/skills/funifier/references/import-csv.md +75 -0
- package/skills/funifier/references/manage-player.md +82 -0
- package/skills/funifier/references/manage-team.md +76 -0
- package/skills/funifier/references/upload-file.md +91 -0
- package/skills/funifier-create-aggregate/SKILL.md +0 -127
- package/skills/funifier-create-challenge/SKILL.md +0 -88
- package/skills/funifier-create-custom-page/SKILL.md +0 -127
- package/skills/funifier-create-level/SKILL.md +0 -87
- package/skills/funifier-create-quiz/SKILL.md +0 -87
- package/skills/funifier-create-scheduler/SKILL.md +0 -127
- package/skills/funifier-create-virtual-good/SKILL.md +0 -87
- package/skills/funifier-debug/SKILL.md +0 -92
- package/skills/funifier-help/SKILL.md +0 -86
- package/skills/funifier-implement-frontend/SKILL.md +0 -90
- package/skills/funifier-index/SKILL.md +0 -58
|
@@ -1,28 +1,493 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `avatar`
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
---
|
|
8
10
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
- [ ]
|
|
27
|
-
- [ ]
|
|
28
|
-
- [ ]
|
|
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.
|