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.
- 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/index.js +2 -2
- package/dist/mcp/index.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 +3 -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 +132 -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 +47 -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,751 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `staging`
|
|
2
2
|
|
|
3
|
-
**Acesso Studio:** `/studio/integration/staging`
|
|
3
|
+
**Acesso Studio:** `/studio/integration/staging` (rótulo de UI — ver nota arquitetural)
|
|
4
|
+
**API Endpoint:** `/v3/gamification` (sub-rotas `/clone/*` e `/data/*`)
|
|
5
|
+
**Coleção MongoDB:** não existe coleção `staging`. As operações atuam sobre a coleção `game` (banco do sistema Funifier) e sobre os bancos individuais por gamification (cada banco é nomeado pelo `apiKey` da gamification).
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> **Nota arquitetural — confirmada no código:** não existe rota `/v3/staging` no backend `funifier-service`. A busca por `staging` em `src/main/**.java` retorna **uma única ocorrência de produção**: o comentário `//staging : send selected data` em `GamificationRest.java:340`, imediatamente acima do endpoint `POST /v3/gamification/data/copy`. O módulo Studio chamado `staging` é, do ponto de vista do backend, um subconjunto de endpoints do recurso `/v3/gamification` (`clone/{id}`, `clone/remote`, `data/copy`, `data/diff`). O caminho `/studio/integration/staging` é o rótulo da UI do Studio (frontend) e **não é derivável deste repositório**.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**Arquivos-fonte analisados:**
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
| Arquivo | Papel |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `src/main/java/com/funifier/rest/v3/rest/GamificationRest.java` | Resource JAX-RS (`@Path("v3/gamification")`) |
|
|
14
|
+
| `src/main/java/com/funifier/engine/game/GameManager.java` | Manager — clone, copy, diff, copyDatabase, delete |
|
|
15
|
+
| `src/main/java/com/funifier/engine/game/GameDaoMongo.java` | DAO Mongo da coleção `game` |
|
|
16
|
+
| `src/main/java/com/funifier/engine/game/Game.java` | Entidade raiz (gamification) |
|
|
17
|
+
| `src/main/java/com/funifier/engine/game/GameDataCopy.java` | DTO de payload para copy/diff |
|
|
18
|
+
| `src/main/java/com/funifier/engine/packages/Component.java` | DTO de item individual de copy/diff |
|
|
19
|
+
| `src/main/java/com/funifier/engine/game/GameUsage.java` | Subentidade de métricas de uso |
|
|
20
|
+
| `src/main/java/com/funifier/rest/v3/AuthBean.java` | Resolução de credenciais, proxy e scope |
|
|
21
|
+
| `src/main/java/com/funifier/engine/util/Entity.java` | Mapeamento enum → nome de coleção |
|
|
22
|
+
| `src/main/java/com/funifier/engine/player/Principal.java` | `TYPE_USER = 0` |
|
|
23
|
+
| `src/main/java/com/funifier/engine/account/Account.java` | Lista `games[]` e `games_allowed` |
|
|
10
24
|
|
|
11
|
-
|
|
12
|
-
- Para validar atualizações antes do go-live
|
|
13
|
-
- Para migrar dados entre servidores de diferentes regiões
|
|
14
|
-
- Para criar ambientes espelho de produção
|
|
25
|
+
> **NÃO localizado no código** (documentado na seção 7): não há Service dedicado (`StagingService`), Repository próprio, Scheduler/Job, enum de status, nem pipeline de aggregation específico de `staging`. Toda a lógica vive em `GamificationRest` + `GameManager`.
|
|
15
26
|
|
|
16
|
-
|
|
27
|
+
---
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
- [ ] Clonar dados de produção para staging
|
|
20
|
-
- [ ] Testar novas funcionalidades em staging
|
|
21
|
-
- [ ] Migrar alterações aprovadas para produção
|
|
29
|
+
## 1. Visão Geral
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
O módulo `staging` fornece operações para tratar gamifications como **ambientes** (produção, homologação, backup) e mover configuração/dados entre eles. São quatro operações reais, todas no recurso `/v3/gamification`:
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
1. **Clone** (`POST /clone/{id}`) — cópia integral de uma gamification (banco MongoDB inteiro), em três modos: mesma conta, conta diferente no mesmo servidor, ou servidor remoto.
|
|
34
|
+
2. **Clone remoto — recepção** (`POST /clone/remote`) — endpoint interno chamado pelo servidor de origem durante o clone remoto.
|
|
35
|
+
3. **Cópia seletiva de dados** (`POST /data/copy`) — copia documentos ou coleções inteiras de uma gamification para outra, na mesma conta.
|
|
36
|
+
4. **Diff de dados** (`POST /data/diff`) — compara documentos entre duas gamifications da mesma conta e devolve um JSON Patch (RFC 6902).
|
|
37
|
+
|
|
38
|
+
**Papel arquitetural:**
|
|
39
|
+
- `clone` opera **no nível do banco MongoDB** (cópia de banco inteiro via `copydb` ou `$merge` por coleção).
|
|
40
|
+
- `data/copy` e `data/diff` operam **coleção-a-coleção** e são restritos à **mesma conta** (`accountId`).
|
|
41
|
+
- **Não há entidade "ambiente" persistida.** Não existe modelo de staging no banco. A relação origem→destino é informada em cada requisição (`source`/`target` ou path param + body).
|
|
42
|
+
|
|
43
|
+
**Casos de uso típicos:**
|
|
44
|
+
- Criar uma gamification de homologação a partir da produção.
|
|
45
|
+
- Promover configurações específicas (`action`, `challenge`, `level`) entre ambientes.
|
|
46
|
+
- Inspecionar diferenças de configuração antes de uma migração.
|
|
47
|
+
- Migrar uma gamification para outro servidor/região Funifier.
|
|
48
|
+
|
|
49
|
+
**Módulos/dependências relacionados:** `AccountManager` (validação de credenciais e limite de gamifications), `ManagerFactory` (obtém a conexão Jongo de cada gamification), `SchedulerManager`/`StaticManager`/`S3Service` (apenas no `delete`), `Guid` (geração de apiKey).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Arquitetura e Fluxos
|
|
54
|
+
|
|
55
|
+
### 2.1 Pipeline — Clone local, mesma conta
|
|
56
|
+
|
|
57
|
+
`GamificationRest.clone()` (linha 272) → `GameManager.clone()` (linha 184):
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
1. [Auth] AccountManager.isAppSecretAllowed(apiKey, appSecret) GamificationRest.java:276
|
|
61
|
+
2. [Guard] proxyEndpoint == null/vazio → modo LOCAL GamificationRest.java:281
|
|
62
|
+
3. [Guard] gamification.accountId == null || == conta atual → mesma conta :285
|
|
63
|
+
4. [Limit] AccountManager.allowCreateMoreGamifications(account) :286 (senão 401)
|
|
64
|
+
5. GameManager.clone(id, gamification, removeLogs) :287
|
|
65
|
+
5.1 source = findByApiKey(id) GameManager.java:185
|
|
66
|
+
5.2 gamification.accountId = accountId ?: source.accountId :188
|
|
67
|
+
5.3 gamification.apiKey = apiKey ?: Guid.newShortGuid() :189
|
|
68
|
+
5.4 add(gamification) → salva em `game` + atualiza Account :190
|
|
69
|
+
5.5 copyDatabase(source.apiKey, gamification.apiKey) :194
|
|
70
|
+
5.6 se removeLogs == true (default): drop de coleções de runtime :196-214
|
|
71
|
+
6. HTTP 201 com a nova Game GamificationRest.java:288
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**`copyDatabase()` (linha 582) — detecção de versão do MongoDB:**
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
v = versão do MongoDB via comando admin "buildinfo" GameManager.java:589-593
|
|
78
|
+
se v[0] < 4 OU (v[0] == 4 E v[1] < 2):
|
|
79
|
+
comando admin "copydb" {fromdb, todb} ← deprecated :603-607
|
|
80
|
+
senão:
|
|
81
|
+
para cada coleção do banco origem:
|
|
82
|
+
aggregate "{ $merge: { into: { db: toApiKey, coll: <col> } }}" :616
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> **Comportamento silencioso / falha:** se o `buildinfo` lançar exceção, `v` permanece `null` (capturado em try/catch vazio, `GameManager.java:594-596`) e a linha `if(v[0] < 4 ...)` (`:599`) lança **NullPointerException**. Não há fallback.
|
|
86
|
+
|
|
87
|
+
### 2.2 Pipeline — Clone local, conta diferente
|
|
88
|
+
|
|
89
|
+
Mesma rota, com header `X-Proxy-Authorization` e `gamification.accountId` diferente da conta atual (`GamificationRest.java:296-312`):
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
1. [Auth] isAppSecretAllowed(apiKey, appSecret) ← conta de ORIGEM :276
|
|
93
|
+
2. [Auth] isAppSecretAllowed(proxyApiKey, proxyAppSecret) ← conta de DESTINO :298
|
|
94
|
+
3. [Limit] allowCreateMoreGamifications(proxyAccount) :301 (senão 401)
|
|
95
|
+
4. GameManager.clone(id, gamification, removeLogs) :302 (mesmo pipeline 2.1)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> As credenciais de proxy são extraídas do header `X-Proxy-Authorization` por `AuthBean.getProxyApiKey()` / `getProxyAppSecret()` (`AuthBean.java:127-160`).
|
|
99
|
+
|
|
100
|
+
### 2.3 Pipeline — Clone remoto (outro servidor)
|
|
101
|
+
|
|
102
|
+
Mesma rota, com `X-Proxy-Authorization` **e** `X-Proxy-Endpoint` (`GamificationRest.java:315-318`) → `GameManager.cloneRemoteSend()` (linha 231):
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
1. gamification.apiKey = apiKey ?: source.apiKey ← mantém o MESMO apiKey da origem! GameManager.java:244
|
|
106
|
+
2. PASSO 1 — registrar a gamification remota: :245
|
|
107
|
+
cloneRemoteRegisterGamificationHelper() → POST {endpoint}/v3/gamification/clone/remote
|
|
108
|
+
body: { operation: "create_gamification", data: <Game> } :367-395
|
|
109
|
+
3. PASSO 2 — enviar coleções de estrutura: :249-280
|
|
110
|
+
blacklist = ["system.indexes"] (+ coleções de log se removeLogs) :251-267
|
|
111
|
+
para cada coleção NÃO blacklistada:
|
|
112
|
+
cloneRemoteSaveCollectionHelper() → lê em lotes e POSTa por lote :275
|
|
113
|
+
buffer flush quando list.size() > 500 (ou seja, a cada 501 docs) :302
|
|
114
|
+
POST {endpoint}/v3/gamification/clone/remote
|
|
115
|
+
body: { operation: "save_collection", apikey, collection, data: [...] } :304-317
|
|
116
|
+
4. Retorna { all: [coleções], out: [respostas OK], err: [erros] } :282-284
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> **Acumulação de erros:** cada lote é envolto em try/catch (`:312`, `:345`); falhas vão para `err[]` e o processo **continua**. Nenhuma exceção interrompe o clone remoto — falhas parciais são silenciosas exceto pelo array `err`.
|
|
120
|
+
|
|
121
|
+
#### Diagrama — Interação no clone remoto (`cloneRemoteSend`)
|
|
122
|
+
|
|
123
|
+
```mermaid
|
|
124
|
+
sequenceDiagram
|
|
125
|
+
participant C as Cliente
|
|
126
|
+
participant S as Servidor Origem<br/>(cloneRemoteSend)
|
|
127
|
+
participant R as Servidor Remoto<br/>(cloneRemoteReceive)
|
|
128
|
+
C->>S: POST /clone/{id}<br/>X-Proxy-Endpoint + X-Proxy-Authorization
|
|
129
|
+
S->>R: POST /clone/remote<br/>{operation: create_gamification, data}
|
|
130
|
+
R-->>S: {status: OK} ou 401
|
|
131
|
+
loop Para cada coleção não-blacklistada
|
|
132
|
+
loop Lotes de até 501 documentos
|
|
133
|
+
S->>R: POST /clone/remote<br/>{operation: save_collection, apikey, collection, data[]}
|
|
134
|
+
R-->>S: resposta (OK → out[], erro → err[])
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
S-->>C: 201 { all, out, err }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 2.4 Pipeline — Recepção de clone remoto (`/clone/remote`)
|
|
141
|
+
|
|
142
|
+
`GamificationRest.cloneRemote()` (linha 330) → `GameManager.cloneRemoteReceive()` (linha 400):
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
[Auth] isAppSecretAllowed(apiKey, appSecret) GamificationRest.java:331
|
|
146
|
+
System.out.println(JsonUtil.toJson(source)) ← LOGA O PAYLOAD INTEIRO GameManager.java:402
|
|
147
|
+
operation == "create_gamification": :405-420
|
|
148
|
+
gamification.accountId = accountId ?: account_da_requisicao :409
|
|
149
|
+
current = findByApiKey(gamification.apiKey) :412
|
|
150
|
+
se current != null E current.accountId != account → 401 :413-415
|
|
151
|
+
add(gamification) :417
|
|
152
|
+
operation == "save_collection": :421-451
|
|
153
|
+
current = findByApiKey(apikey) :431
|
|
154
|
+
se current != null E current.accountId != account → 401 :432-434
|
|
155
|
+
[!] collection é lida como String crua (source.get("collection")) :427 (sem validação)
|
|
156
|
+
para cada doc: marshaller.marshall(o) → BasicDBObject.parse → c.save(doc) :441-447
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 2.5 Pipeline — Cópia seletiva (`/data/copy`)
|
|
160
|
+
|
|
161
|
+
`GamificationRest.dataCopy()` (linha 344) → `GameManager.copy()` (linha 457):
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
1. [Auth] isAppSecretAllowed GamificationRest.java:347
|
|
165
|
+
2. [Guard] proxyEndpoint == null/vazio (senão NO-OP, retorna {}) :350
|
|
166
|
+
3. sourceGame = findByApiKey(data.source); targetGame = findByApiKey(data.target) :352-353
|
|
167
|
+
4. [Guard] source != null E target != null E target.accountId == source.accountId :356
|
|
168
|
+
(senão NO-OP: retorna {} com HTTP 201, sem copiar nada)
|
|
169
|
+
5. GameManager.copy(data) :357
|
|
170
|
+
para cada Component item em data.items (que tenha id E type não-vazios): GameManager.java:462-463
|
|
171
|
+
item.id == "*" → aggregate "{ $merge: { into: { db: target, coll: type }}}" :466
|
|
172
|
+
item.id != "*" → obj = source.findOne({_id: id}); target.save(obj) :471-472
|
|
173
|
+
6. result.put("status","OK") → HTTP 201 GamificationRest.java:358
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
> **Sem try/catch no loop de `copy()`** (`GameManager.java:462-476`). Se um `item.id` específico não existir na origem, `findOne(...).as(Object.class)` retorna `null` e `target.save(null)` (`:472`) lança exceção — a requisição inteira falha **no meio da iteração**, deixando os itens anteriores já copiados. Diferente de `diff()`, não há proteção por item.
|
|
177
|
+
|
|
178
|
+
### 2.6 Pipeline — Diff (`/data/diff`)
|
|
179
|
+
|
|
180
|
+
`GamificationRest.dataDiff()` (linha 376) → `GameManager.diff()` (linha 481):
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
1-4. Mesmos guards de auth e mesma conta de data/copy GamificationRest.java:379-388
|
|
184
|
+
5. GameManager.diff(data) :389
|
|
185
|
+
para cada Component item (id E type não-vazios): GameManager.java:492-493
|
|
186
|
+
item.id == "*" → source.find().limit(100) ← LIMITE FIXO 100 :496
|
|
187
|
+
para cada doc: target.findOne({_id}); diffObjects(...) :497-507
|
|
188
|
+
item.id != "*" → source.findOne; target.findOne; diffObjects(...) :512-516
|
|
189
|
+
cada chamada a diffObjects() é envolta em try/catch :502-506, 515-519
|
|
190
|
+
→ exceção apenas faz e.printStackTrace() e o item é DESCARTADO
|
|
191
|
+
6. Retorna { source, target, diff: [...] } :524
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**`diffObjects(type, id, source, target)` (linha 533):**
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
resolve título (depende do type): GameManager.java:540-562
|
|
198
|
+
action → source.get("action")
|
|
199
|
+
point_category → source.get("category")
|
|
200
|
+
challenge → source.get("challenge")
|
|
201
|
+
level → source.get("level")
|
|
202
|
+
outros → "title" ?: "name" ?: "label" ?: "_id"
|
|
203
|
+
se source != null E target == null → operations = "[op: create]" :565-566
|
|
204
|
+
senão → JsonDiff.asJsonPatch(target, source) :571-577
|
|
205
|
+
(RFC 6902; só inclui se != "[]")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> **Deletions nunca são reportadas.** O bloco `else if(source == null && target != null) { "[op: delete]" }` está **comentado** (`GameManager.java:568-570`). Além disso, mesmo que fosse reativado, é **inalcançável**: quando `source == null` a resolução de título no topo (`source.get("action")`, `source.containsKey(...)`) lança NullPointerException antes de chegar lá; a exceção é capturada em `diff()` e o item é silenciosamente descartado. Documentos presentes só no `target` jamais aparecem no resultado.
|
|
209
|
+
|
|
210
|
+
### 2.7 Diagrama — Modos de operação do clone
|
|
211
|
+
|
|
212
|
+
```mermaid
|
|
213
|
+
flowchart LR
|
|
214
|
+
A["POST /v3/gamification/clone/{id}"] --> AUTH{isAppSecretAllowed?}
|
|
215
|
+
AUTH -- não --> X401[401 UNAUTHORIZED]
|
|
216
|
+
AUTH -- sim --> B{X-Proxy-Endpoint?}
|
|
217
|
+
B -- "vazio (LOCAL)" --> C{accountId do body}
|
|
218
|
+
C -- "null ou conta atual" --> L1{allowCreateMore?}
|
|
219
|
+
L1 -- sim --> D["GameManager.clone()<br/>copyDatabase local"]
|
|
220
|
+
L1 -- não --> X401
|
|
221
|
+
C -- "conta diferente" --> E{Proxy auth válido?}
|
|
222
|
+
E -- sim --> L2{allowCreateMore proxy?}
|
|
223
|
+
L2 -- sim --> D
|
|
224
|
+
L2 -- não --> X401
|
|
225
|
+
E -- não --> X401
|
|
226
|
+
B -- "URL remota" --> G["GameManager.cloneRemoteSend()<br/>lotes HTTP de 501 docs"]
|
|
227
|
+
G --> H["POST remoto<br/>/v3/gamification/clone/remote"]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 3. Estrutura dos Objetos
|
|
233
|
+
|
|
234
|
+
### 3.1 `Game` — documento raiz (coleção `game`)
|
|
235
|
+
|
|
236
|
+
Mapeada por `Game.java`. `@JsonIgnoreProperties(ignoreUnknown=true)` — campos desconhecidos no JSON são silenciosamente ignorados na desserialização.
|
|
237
|
+
|
|
238
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
239
|
+
| --- | --- | --- | --- | --- |
|
|
240
|
+
| `_id` | String | gerado | — | `apiKey` (anotado `@JsonProperty("_id")`). Também é o **nome do banco MongoDB** da gamification |
|
|
241
|
+
| `created_at` | Date | servidor | — | Definido no primeiro save; preservado em updates via `add()` (`GameManager.java:95`) |
|
|
242
|
+
| `updated_at` | Date | servidor | — | Sempre sobrescrito em cada save (`:99`) |
|
|
243
|
+
| `templateId` | String | — | — | Referência a template de criação |
|
|
244
|
+
| `inlineEditable` | boolean | `false` | — | Flag de edição inline no Studio |
|
|
245
|
+
| `description` | String | — | — | Descrição livre |
|
|
246
|
+
| `name` | String | — | — | Nome da gamification (não há validação de obrigatoriedade no backend) |
|
|
247
|
+
| `accountId` | String | — | ✓* | Conta proprietária. *Definido pelo servidor em `insert`/`clone`; `add()` ignora a Game se `accountId == null` (`GameManager.java:77`) |
|
|
248
|
+
| `image` | `Image` | — | — | Objeto com `small`, `medium`, `original` (cada um `ImageItem`) |
|
|
249
|
+
| `extra` | `Map<String,Object>` | `{}` | — | Metadados extras livres |
|
|
250
|
+
| `i18n` | `Map<String,Map<String,String>>` | `{}` | — | Strings de internacionalização |
|
|
251
|
+
| `paying` | boolean | `false` | — | Flag de conta pagante |
|
|
252
|
+
| `usage` | `List<GameUsage>` | `[]` | — | Configurações de métricas de uso |
|
|
253
|
+
|
|
254
|
+
**Campos computados (não persistem como campo próprio):**
|
|
255
|
+
- `getImageUrl()` (`Game.java:80-82`) — retorna `image.small.url`, ou `null`. É um getter derivado, não um campo armazenado.
|
|
256
|
+
|
|
257
|
+
**Campos legados comentados no código-fonte (`Game.java:33-36, 84-100`):**
|
|
258
|
+
- `imageUrl` (String) — substituído por `image`.
|
|
259
|
+
- `timeZone` (String) — removido; getter/setter comentados.
|
|
260
|
+
|
|
261
|
+
> Por estarem comentados, esses campos **não são desserializados nem persistidos**. Enviá-los no JSON é inócuo (ignorados por `@JsonIgnoreProperties`).
|
|
262
|
+
|
|
263
|
+
### 3.2 `GameDataCopy` — payload de `data/copy` e `data/diff`
|
|
264
|
+
|
|
265
|
+
`GameDataCopy.java`. `@JsonIgnoreProperties(ignoreUnknown=true)`.
|
|
266
|
+
|
|
267
|
+
| Campo | Tipo | Obrigatório | Descrição |
|
|
268
|
+
| --- | --- | --- | --- |
|
|
269
|
+
| `source` | String | ✓ | apiKey da gamification de origem |
|
|
270
|
+
| `target` | String | ✓ | apiKey da gamification de destino |
|
|
271
|
+
| `items` | `List<Component>` | ✓ | Itens a copiar/comparar (default `[]`) |
|
|
272
|
+
|
|
273
|
+
### 3.3 `Component` — item de copy/diff
|
|
274
|
+
|
|
275
|
+
`Component.java`. `@JsonIgnoreProperties(ignoreUnknown=true)`.
|
|
276
|
+
|
|
277
|
+
| Campo | Tipo | Obrigatório (runtime) | Descrição |
|
|
278
|
+
| --- | --- | --- | --- |
|
|
279
|
+
| `_id` | String | ✓ | ID do documento, ou `*` para a coleção inteira (`@JsonProperty("_id")`) |
|
|
280
|
+
| `type` | String | ✓ | **Nome da coleção MongoDB** (ex.: `action`, `challenge`, `level`) |
|
|
281
|
+
| `title` | String | — | Apenas rótulo — **ignorado** por `copy()` e `diff()` |
|
|
282
|
+
| `content` | String | — | Apenas rótulo — **ignorado** por `copy()` e `diff()` |
|
|
283
|
+
|
|
284
|
+
> Os javadocs em `Component.java` marcam todos os 4 campos como "required", mas o runtime só usa `id` e `type`: o loop em `copy()`/`diff()` exige `id` e `type` não-vazios (`GameManager.java:463`, `:493`) e ignora `title`/`content`. São aceitos pelo desserializador e servem apenas de label na UI.
|
|
285
|
+
|
|
286
|
+
### 3.4 `GameUsage` — subentidade de métricas (`Game.usage[]`)
|
|
287
|
+
|
|
288
|
+
`GameUsage.java`.
|
|
289
|
+
|
|
290
|
+
| Campo | Tipo | Descrição |
|
|
291
|
+
| --- | --- | --- |
|
|
292
|
+
| `_id` | String | Identificador (`@JsonProperty("_id")`) |
|
|
293
|
+
| `title` | String | Título da métrica |
|
|
294
|
+
| `type` | String | `custom` ou `players_with_action_logs` (constantes `TYPE_CUSTOM`, `TYPE_PLAYERS_WITH_ACTION_LOGS`) |
|
|
295
|
+
| `collection` | String | Coleção MongoDB onde o aggregate é executado (ex.: `player`) |
|
|
296
|
+
| `aggregate` | String | Pipeline de aggregation; deve retornar um campo `total` |
|
|
297
|
+
|
|
298
|
+
### 3.5 Coleções de runtime removidas no `clone` com `removeLogs=true`
|
|
299
|
+
|
|
300
|
+
Nomes reais resolvidos via `Entity.java`. **11 coleções são dropadas** + remoção parcial em `principal`. A coleção `team` **NÃO** é removida (linha comentada).
|
|
301
|
+
|
|
302
|
+
| Constante `Entity` | Coleção | Ação no clone LOCAL (`GameManager.java:199-213`) |
|
|
303
|
+
| --- | --- | --- |
|
|
304
|
+
| `PLAYER` | `player` | `drop()` |
|
|
305
|
+
| `PLAYER_STATUS` | `player_status` | `drop()` |
|
|
306
|
+
| `ACHIEVEMENT` | `achievement` | `drop()` |
|
|
307
|
+
| `ACTION_LOG` | `action_log` | `drop()` |
|
|
308
|
+
| `NOTIFICATION` | `notification` | `drop()` |
|
|
309
|
+
| `LEADER` | `leader` | `drop()` |
|
|
310
|
+
| `TEAM` | `team` | **não removida** (comentada, `:206`) |
|
|
311
|
+
| `TEAM_PLAYER` | `team_player` | `drop()` |
|
|
312
|
+
| `PRINCIPAL` | `principal` | `remove("{type:#}", 0)` — remove só `type == 0` (`TYPE_USER`); mantém os demais principals |
|
|
313
|
+
| `LOTTERY_TICKET` | `lottery_ticket` | `drop()` |
|
|
314
|
+
| `PROGRESS_LOG` | `progress_log` | `drop()` |
|
|
315
|
+
| `CHALLENGE_PROGRESS` | `challenge_progress` | `drop()` |
|
|
316
|
+
| `GAMIFICATION_STATUS` | `game_status` | `drop()` (enum `GAMIFICATION_STATUS` → coleção `game_status`, `Entity.java:140`) |
|
|
317
|
+
|
|
318
|
+
> **`Principal.TYPE_USER = 0` é um inteiro** (`Principal.java:25`). A query real é `{type: 0}`, **não** `{type: "user"}`. Principals com `type != 0` (ex.: tipos de máquina/serviço) são preservados no clone local.
|
|
319
|
+
|
|
320
|
+
> **Divergência local × remoto:** no clone **remoto** (`cloneRemoteSend`, `:262`) a coleção `principal` é colocada **inteira** na blacklist (não é enviada de jeito nenhum quando `removeLogs=true`), enquanto no clone **local** apenas `type == 0` é removido. Resultado: clone remoto com `removeLogs` perde **todos** os principals; clone local mantém os não-usuário.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 4. Endpoints
|
|
325
|
+
|
|
326
|
+
Todos sob `@Path("v3/gamification")` (`GamificationRest.java:35`), `Produces: application/json; charset=UTF-8`. As respostas passam por `JsonUtil.toJsonRemoveNullFields(...)` — **campos nulos são omitidos** do JSON de saída.
|
|
327
|
+
|
|
328
|
+
### `GET /v3/gamification` — listar gamifications
|
|
329
|
+
|
|
330
|
+
| Aspecto | Detalhe |
|
|
331
|
+
| --- | --- |
|
|
332
|
+
| Finalidade | Lista gamifications da conta autenticada (`findAllGamifications`, `:59`) |
|
|
333
|
+
| Autenticação | `isAppSecretAllowed` (credenciais de conta); senão 401 |
|
|
334
|
+
| Status sucesso | 200 OK |
|
|
335
|
+
|
|
336
|
+
**Query params** (`GameManager.findAllByAccountId`, linha 647):
|
|
337
|
+
|
|
338
|
+
| Param | Tipo | Comportamento real |
|
|
339
|
+
| --- | --- | --- |
|
|
340
|
+
| `name` | String | `{name: {$regex: <valor>}}` — match por regex |
|
|
341
|
+
| `published_min` | String | RFC 3339 **ou** keyword (`-1d`, `-30m`, unidades `y M w d h m s`) → `created_at >= valor` (inclusivo) |
|
|
342
|
+
| `published_max` | String | RFC 3339 ou keyword → `created_at <= valor` |
|
|
343
|
+
| `orderby` | String | Campo de ordenação concatenado **cru** na query (`name`, `description`, `created_at`) |
|
|
344
|
+
| `reverse` | boolean | `true` → `-1` (DESC); default ASC. Parse tolerante a erro (`:71`) |
|
|
345
|
+
| `max_results` | int | `<= 0` ⇒ default **100** (`GameManager.java:660`) |
|
|
346
|
+
|
|
347
|
+
### `GET /v3/gamification/{id}` — buscar por apiKey
|
|
348
|
+
|
|
349
|
+
| Aspecto | Detalhe |
|
|
350
|
+
| --- | --- |
|
|
351
|
+
| Finalidade | `find()` (`:102`) |
|
|
352
|
+
| Autenticação | Dois caminhos: (1) `isAppSecretAllowed` → **200 OK**; (2) Bearer de marketplace com claim `account` igual ao `gamification.accountId` → **201 CREATED** |
|
|
353
|
+
|
|
354
|
+
> **Quirk confirmado:** o caminho de marketplace retorna `Response.Status.CREATED` (201) para um GET (`GamificationRest.java:112`). Não é o convencional 200. Se nenhum caminho casar → 401.
|
|
355
|
+
|
|
356
|
+
### `POST /v3/gamification` — criar
|
|
357
|
+
|
|
358
|
+
| Aspecto | Detalhe |
|
|
359
|
+
| --- | --- |
|
|
360
|
+
| Finalidade | `insert()` (`:158`) |
|
|
361
|
+
| Autenticação | `isAppSecretAllowed` (conta) **ou** Bearer marketplace com claim `account` válido |
|
|
362
|
+
| Full replace ou patch | Criação. `accountId` é **sempre sobrescrito** pelo servidor (`:161` ou `:173`) |
|
|
363
|
+
| Status sucesso | 201 CREATED |
|
|
364
|
+
|
|
365
|
+
**Comportamento real (via `GameManager.add()`, linha 75):**
|
|
366
|
+
- `apiKey` ausente ⇒ gera `Guid.newShortGuid()` e define `created_at` (`:83-86`).
|
|
367
|
+
- `apiKey` presente e já existe ⇒ **preserva o `created_at` antigo** (`:95`).
|
|
368
|
+
- `updated_at` sempre sobrescrito (`:99`).
|
|
369
|
+
- Adiciona o apiKey em `Account.games[]` se ausente (`:106-108`).
|
|
370
|
+
- Chama `calculateStatisticsByAccount(accountId)` (`:113`).
|
|
371
|
+
- Antes, verifica `allowCreateMoreGamifications` (`:160`) → 401 "account is not allowed to create more gamifications" se o limite for atingido.
|
|
372
|
+
|
|
373
|
+
### `DELETE /v3/gamification/{id}` — excluir (cascade)
|
|
374
|
+
|
|
375
|
+
| Aspecto | Detalhe |
|
|
376
|
+
| --- | --- |
|
|
377
|
+
| Finalidade | `delete()` (`:196`) |
|
|
378
|
+
| Autenticação | `isAppSecretAllowed` E (a gamification é da conta **ou** o scope contém `CROSSDOMAIN`) |
|
|
379
|
+
| Status | 200 OK; 401 com mensagem se não autorizado |
|
|
380
|
+
|
|
381
|
+
**Cascade real (ordem em `GameManager.delete()`, linha 135):**
|
|
382
|
+
1. `ManagerFactory.getInstance(apiKey).getSchedulerManager().deleteAll()` (`:141`)
|
|
383
|
+
2. `StaticManager.deleteByApiKey(apiKey)` (`:144`)
|
|
384
|
+
3. `ManagerFactory.stopInstance(apiKey)` (`:147`)
|
|
385
|
+
4. `MONGO.dropDatabase(apiKey)` — **destrói o banco MongoDB inteiro da gamification** (`:151`)
|
|
386
|
+
5. `game` collection: `remove({_id: apiKey})` (`:157`)
|
|
387
|
+
6. Remove o apiKey de `Account.games[]` (`:162`)
|
|
388
|
+
7. `S3Service.deleteAllFilesByApiKey(apiKey)` (`:167`)
|
|
389
|
+
|
|
390
|
+
> **Irreversível e não-transacional.** Não há soft-delete nem rollback. Os 7 passos rodam em sequência sem transação: se um passo intermediário falhar (ex.: `dropDatabase`, atualização de `Account`, S3), a gamification fica em **estado inconsistente** (banco já dropado mas registro ainda em `game`, ou vice-versa).
|
|
391
|
+
|
|
392
|
+
### `POST /v3/gamification/clone/{id}` — clonar
|
|
393
|
+
|
|
394
|
+
| Aspecto | Detalhe |
|
|
395
|
+
| --- | --- |
|
|
396
|
+
| Finalidade | `clone()` (`:272`) — local mesma conta, local outra conta, ou remoto |
|
|
397
|
+
| Autenticação | `isAppSecretAllowed` (conta de origem); proxy auth se conta destino diferir |
|
|
398
|
+
| Status sucesso | 201 CREATED |
|
|
399
|
+
|
|
400
|
+
**Path param:** `id` — apiKey da gamification **origem**.
|
|
401
|
+
|
|
402
|
+
**Query param:**
|
|
403
|
+
|
|
404
|
+
| Param | Tipo | Comportamento real |
|
|
405
|
+
| --- | --- | --- |
|
|
406
|
+
| `removeLogs` | String | `remove = !"false".equals(removeLogs)` (`:278`). Ou seja: **somente** a string literal `"false"` mantém os logs; ausente ou qualquer outro valor ⇒ `removeLogs=true` (remove) |
|
|
407
|
+
|
|
408
|
+
**Headers especiais:**
|
|
409
|
+
|
|
410
|
+
| Header | Uso |
|
|
411
|
+
| --- | --- |
|
|
412
|
+
| `X-Proxy-Authorization` | Credenciais da conta de destino (clone entre contas) ou do servidor remoto |
|
|
413
|
+
| `X-Proxy-Endpoint` | URL do servidor remoto; **define o modo remoto** (`:281`) |
|
|
414
|
+
|
|
415
|
+
**Body (`Game`):** `name`, `description`, `accountId` (opcional), `image`, `_id`/`apiKey` (opcional).
|
|
416
|
+
|
|
417
|
+
**Comportamento real:**
|
|
418
|
+
- Clone local sem `apiKey` no body ⇒ gera novo GUID (`GameManager.java:189`).
|
|
419
|
+
- Clone **remoto** sem `apiKey` no body ⇒ usa o **mesmo apiKey da origem** (`:244`).
|
|
420
|
+
- Clone remoto retorna `{ all, out, err }`; erros acumulados sem falhar.
|
|
421
|
+
|
|
422
|
+
### `POST /v3/gamification/clone/remote` — recepção interna
|
|
423
|
+
|
|
424
|
+
| Aspecto | Detalhe |
|
|
425
|
+
| --- | --- |
|
|
426
|
+
| Finalidade | `cloneRemote()` (`:330`) — recebe dados do `cloneRemoteSend` do servidor origem |
|
|
427
|
+
| Autenticação | `isAppSecretAllowed` (conta) |
|
|
428
|
+
| Body | `{ operation: "create_gamification" \| "save_collection", ... }` |
|
|
429
|
+
|
|
430
|
+
> Endpoint interno. Não destinado a uso direto. **Loga o payload inteiro em `System.out`** (`GameManager.java:402`) e **não valida** o nome da coleção (`:427`) — ver seção 8.
|
|
431
|
+
|
|
432
|
+
### `POST /v3/gamification/data/copy` — cópia seletiva
|
|
433
|
+
|
|
434
|
+
| Aspecto | Detalhe |
|
|
435
|
+
| --- | --- |
|
|
436
|
+
| Finalidade | `dataCopy()` (`:344`) — comentário `//staging : send selected data` (`:340`) |
|
|
437
|
+
| Autenticação | `isAppSecretAllowed` (conta) |
|
|
438
|
+
| Restrição | **Apenas local** (`proxyEndpoint` deve ser vazio) e **mesma conta** |
|
|
439
|
+
| Status | 201 CREATED |
|
|
440
|
+
|
|
441
|
+
**Body:** `GameDataCopy` (ver 3.2).
|
|
442
|
+
|
|
443
|
+
**Comportamento real:**
|
|
444
|
+
- `_id: "*"` ⇒ `$merge` da coleção inteira da origem no destino (`GameManager.java:466`). Sem `whenMatched`/`whenNotMatched` explícitos → padrão MongoDB: `whenMatched: "merge"`, `whenNotMatched: "insert"`. Documentos com `_id` coincidente têm campos **mesclados** (não substituídos); documentos só do destino são **mantidos**.
|
|
445
|
+
- `_id` específico ⇒ `findOne` na origem + `save` no destino (`:471-472`).
|
|
446
|
+
- Sem retorno detalhado de itens copiados — só `{ "status": "OK" }`.
|
|
447
|
+
|
|
448
|
+
### `POST /v3/gamification/data/diff` — comparação
|
|
449
|
+
|
|
450
|
+
| Aspecto | Detalhe |
|
|
451
|
+
| --- | --- |
|
|
452
|
+
| Finalidade | `dataDiff()` (`:376`) |
|
|
453
|
+
| Autenticação | `isAppSecretAllowed` (conta) |
|
|
454
|
+
| Restrição | Apenas local e mesma conta |
|
|
455
|
+
| Status | 201 CREATED |
|
|
456
|
+
|
|
457
|
+
**Body:** mesmo `GameDataCopy`.
|
|
458
|
+
|
|
459
|
+
**Comportamento real:**
|
|
460
|
+
- `_id: "*"` ⇒ **limita a 100 documentos** da origem (`.limit(100)`, hardcoded, `GameManager.java:496`).
|
|
461
|
+
- Retorna operações em **RFC 6902 JSON Patch** (`[{op, path, value}]`) como string no campo `operations`.
|
|
462
|
+
- Item presente só na origem ⇒ `operations: "[op: create]"`.
|
|
463
|
+
- Item presente só no destino (deletion) ⇒ **nunca reportado** (ver 2.6).
|
|
464
|
+
- Source e target em contas diferentes (ou alguma gamification inexistente) ⇒ retorna `{}` sem erro.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## 5. Regras de Negócio
|
|
469
|
+
|
|
470
|
+
**Clone — isolamento de conta (`GamificationRest.java:285-312`):** o clone local só prossegue se `gamification.accountId` for nulo, igual à conta atual, ou — se diferente — acompanhado de `X-Proxy-Authorization` válido para a conta destino. Caso contrário, 401.
|
|
471
|
+
|
|
472
|
+
**Clone — preservação de apiKey no remoto (`GameManager.java:244`):** sem `apiKey` no body, a gamification remota recebe o **mesmo apiKey da origem**. Intencional para migração de ambiente, mas colide se o servidor destino já tiver gamification com esse apiKey de **outra** conta (→ 401 em `cloneRemoteReceive`).
|
|
473
|
+
|
|
474
|
+
**Copy/Diff — mesma conta obrigatória (`GamificationRest.java:356`, `:388`):** verificação `target.accountId.equals(source.accountId)`. Se falhar (ou alguma gamification não existir, ou `proxyEndpoint` estar preenchido), o handler simplesmente cai para o `return` final com `result` vazio → **HTTP 201 com `{}`**, sem mensagem de erro.
|
|
475
|
+
|
|
476
|
+
**Limite de gamifications por conta (`Account.games_allowed` / `games[]`):** `insert` e `clone` chamam `allowCreateMoreGamifications`. Atingido o limite → 401 (na conta de origem ou, no clone entre contas, na conta de destino proxy).
|
|
477
|
+
|
|
478
|
+
**Versionamento do MongoDB (`copyDatabase`, `:599`):** versão detectada em runtime via `buildinfo`. `< 4.2` usa `copydb` (deprecated); `>= 4.2` usa `$merge` por coleção. Falha na detecção ⇒ NPE.
|
|
479
|
+
|
|
480
|
+
**Diff — resolução de título (`diffObjects`, `:540-562`):** por tipo (`action`→`action`, `point_category`→`category`, `challenge`→`challenge`, `level`→`level`); demais tipos: `title` > `name` > `label` > `_id`.
|
|
481
|
+
|
|
482
|
+
**`add()` ignora Game sem conta (`GameManager.java:77`):** se `accountId == null`, `add()` não faz nada (sem erro). Nos fluxos REST isso não ocorre porque o servidor preenche `accountId` antes.
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## 6. Comportamentos Automáticos
|
|
487
|
+
|
|
488
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
489
|
+
| --- | --- | --- | --- |
|
|
490
|
+
| Geração de apiKey | `add()`/`clone()` local sem apiKey | `Guid.newShortGuid()` | Persistido em `game._id` |
|
|
491
|
+
| Preservação de `created_at` | `add()` em apiKey existente | Mantém data original | Persistido em `game` |
|
|
492
|
+
| Sobrescrita de `updated_at` | Todo `add()`/save | Atualiza para `now()` | Persistido em `game` |
|
|
493
|
+
| Atualização de `Account.games[]` | `add()` (create/clone) | Adiciona apiKey à lista da conta | Persistido em `account` |
|
|
494
|
+
| Recálculo de estatísticas | `add()` | `calculateStatisticsByAccount(accountId)` | Persistido (stats da conta) |
|
|
495
|
+
| Remoção de coleções de runtime | `clone()` com `removeLogs=true` (default) | 11 coleções dropadas no destino | Irreversível |
|
|
496
|
+
| Remoção de principals usuário | `clone()` local com `removeLogs=true` | `remove({type:0})` em `principal` | Irreversível (clone local) |
|
|
497
|
+
| Log de payload | `cloneRemoteReceive()` | `System.out.println` do payload | Não persistido (stdout) |
|
|
498
|
+
|
|
499
|
+
#### Diagrama — `clone()` local com `removeLogs=true`
|
|
500
|
+
|
|
501
|
+
```mermaid
|
|
502
|
+
flowchart LR
|
|
503
|
+
A["clone(id, gamification, true)"] --> B["source = findByApiKey(id)"]
|
|
504
|
+
B --> C["add(gamification)<br/>(grava em game + Account)"]
|
|
505
|
+
C --> D["copyDatabase(from, to)"]
|
|
506
|
+
D --> E{MongoDB >= 4.2?}
|
|
507
|
+
E -- sim --> F["$merge por coleção"]
|
|
508
|
+
E -- não --> G["comando admin copydb"]
|
|
509
|
+
F --> H["drop de 11 coleções de runtime"]
|
|
510
|
+
G --> H
|
|
511
|
+
H --> I["principal.remove({type:0})"]
|
|
512
|
+
I --> J["retorna nova Game (201)"]
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 7. Suportado vs NÃO Suportado
|
|
518
|
+
|
|
519
|
+
### ✅ Suportado
|
|
520
|
+
|
|
521
|
+
- Clone local na mesma conta (`POST /clone/{id}` sem proxy).
|
|
522
|
+
- Clone local entre contas (com `X-Proxy-Authorization`).
|
|
523
|
+
- Clone para servidor remoto (com `X-Proxy-Authorization` + `X-Proxy-Endpoint`).
|
|
524
|
+
- Cópia de documento individual entre gamifications (`data/copy` com `_id` específico).
|
|
525
|
+
- Cópia de coleção inteira via `$merge` (`data/copy` com `_id: "*"`).
|
|
526
|
+
- Diff de documento individual (`data/diff` com `_id` específico).
|
|
527
|
+
- Diff de coleção (máx. 100 docs) (`data/diff` com `_id: "*"`).
|
|
528
|
+
- Clone com/sem dados de runtime (`removeLogs`).
|
|
529
|
+
- Detecção automática de versão do MongoDB.
|
|
530
|
+
- Acumulação de erros no clone remoto (não interrompe no primeiro erro).
|
|
531
|
+
- Exclusão em cascade (`DELETE`) incluindo drop do banco e arquivos S3.
|
|
532
|
+
|
|
533
|
+
### ❌ NÃO Suportado / armadilhas confirmadas no código
|
|
534
|
+
|
|
535
|
+
- **Rota `/v3/staging`** — não existe. Não há Service, Repository, Scheduler, enum de status nem coleção `staging`.
|
|
536
|
+
- **`data/copy` / `data/diff` cross-server** — o guard `proxyEndpoint == null` faz a operação virar **no-op silencioso** (retorna `{}`) quando há `X-Proxy-Endpoint`.
|
|
537
|
+
- **`data/copy` / `data/diff` cross-account** — retornam `{}` (201) sem copiar/comparar e sem erro.
|
|
538
|
+
- **Detecção de deletions no diff** — bloco `[op: delete]` comentado (`GameManager.java:568-570`) **e** inalcançável por NPE quando `source == null`. Documentos só no target são ignorados.
|
|
539
|
+
- **`data/diff` além de 100 docs por coleção** — `.limit(100)` hardcoded (`:496`). Coleções maiores são truncadas sem aviso.
|
|
540
|
+
- **`data/copy` resiliente a id inexistente** — não há try/catch no loop; `target.save(null)` aborta a requisição no meio (`:472`).
|
|
541
|
+
- **Validação do nome da coleção em `cloneRemoteReceive`** — qualquer string é aceita como `collection` (`:427`).
|
|
542
|
+
- **Rollback de clone ou de delete** — inexistente; ambos são irreversíveis e o `delete` é não-transacional.
|
|
543
|
+
- **Campos `title`/`content` de `Component`** — aceitos no JSON mas ignorados pelo runtime.
|
|
544
|
+
- **Campos legados `imageUrl`/`timeZone` de `Game`** — comentados; não desserializados nem persistidos.
|
|
545
|
+
- **Transacionalidade no clone remoto** — falhas parciais por lote vão para `err[]`; não há atomicidade nem retry.
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 8. Segurança e Permissões
|
|
550
|
+
|
|
551
|
+
**Autenticação:** todas as operações de staging exigem `isAppSecretAllowed(apiKey, appSecret)` — credenciais de **nível de conta** (header `Authorization: Account ...` ou `Basic`). Para auth `Account`, `AuthBean.getApiKey()` retorna na verdade o **accountId** (`AuthBean.java:64-65`) — daí o uso de `authBean.getApiKey()` como conta nos handlers. Bearer de player **não** habilita clone/copy/diff.
|
|
552
|
+
|
|
553
|
+
**Isolamento por conta:** `data/copy` e `data/diff` comparam `accountId` de origem e destino; `clone` entre contas exige credenciais de proxy da conta destino; `cloneRemoteReceive` rejeita (401) gravação em gamification de outra conta.
|
|
554
|
+
|
|
555
|
+
**Scope `CROSSDOMAIN` no delete (`GamificationRest.java:201-202`):** com `Application.SCOPE_CROSSDOMAIN` no scope, um token pode **excluir gamifications de outra conta**. Documentado explicitamente: `authBean.getScope().indexOf(Application.SCOPE_CROSSDOMAIN) != -1`.
|
|
556
|
+
|
|
557
|
+
**`cloneRemoteReceive` aceita coleção arbitrária (`GameManager.java:427`):** o campo `collection` é lido como String crua e usado direto em `jongo.getCollection(collection)` sem validar contra `Entity`. Um cliente com credenciais válidas da conta poderia gravar documentos em coleções internas (`principal`, `security`, etc.). A barreira é apenas a posse de credenciais de conta + a checagem de `accountId`.
|
|
558
|
+
|
|
559
|
+
**Sequestro de metadados via `clone` na mesma conta (`GameManager.java:189-190`):** se o body informar um `apiKey` de gamification **já existente da mesma conta**, `clone()` mantém esse apiKey e chama `add()`, que **preserva `created_at` mas sobrescreve nome/descrição/imagem/etc.**; em seguida `copyDatabase` faz `$merge` no banco existente. Quem tiver o app secret da conta pode sobrescrever metadados e mesclar dados em uma gamification existente.
|
|
560
|
+
|
|
561
|
+
**Vazamento de dados em log (`GameManager.java:402`):** `cloneRemoteReceive` imprime o payload completo recebido em `System.out`. Em ambientes que coletam stdout, dados de todas as coleções clonadas ficam expostos nos logs.
|
|
562
|
+
|
|
563
|
+
**Colisão de apiKey no clone remoto (`:244`, `:413`):** sem `apiKey` no body, a gamification remota herda o apiKey da origem. Se o destino já tiver esse apiKey em outra conta → 401; se for da mesma conta → **sobrescreve** via `add()`.
|
|
564
|
+
|
|
565
|
+
**Injeção em `orderby` (`GameManager.java:683`):** o valor de `orderby` é concatenado **cru** na string de aggregation (`"{$sort : {" + orderby + " : #}}"`). Não há allowlist no backend; valores inesperados podem alterar/quebrar o estágio `$sort`.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## 9. Observabilidade e Troubleshooting
|
|
570
|
+
|
|
571
|
+
**Verificar se a gamification existe / inspecionar:**
|
|
572
|
+
```
|
|
573
|
+
GET /v3/gamification/{apiKey}
|
|
574
|
+
Authorization: Account <token>
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**Listar gamifications da conta:**
|
|
578
|
+
```
|
|
579
|
+
GET /v3/gamification
|
|
580
|
+
Authorization: Account <token>
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**Verificar resultado do clone local:** após o 201, buscar a nova gamification pelo `_id` retornado.
|
|
584
|
+
|
|
585
|
+
**Diagnosticar clone remoto:** o retorno `{ all, out, err }` (`GameManager.java:282-284`) lista todas as coleções (`all`), respostas OK (`out`) e falhas (`err`). Verifique `err[]` para coleções/lotes que falharam — o processo **não** para no primeiro erro.
|
|
586
|
+
|
|
587
|
+
**Erros comuns:**
|
|
588
|
+
|
|
589
|
+
| Sintoma | Causa provável |
|
|
590
|
+
| --- | --- |
|
|
591
|
+
| `401 UNAUTHORIZED_CLIENT` | Credenciais inválidas, ou conta destino sem proxy auth válido no clone entre contas |
|
|
592
|
+
| `401 account is not allowed to create more gamifications` | Limite `games_allowed` atingido |
|
|
593
|
+
| `401 gamification dont belong to this account` | Clone remoto: apiKey já pertence a outra conta (`cloneRemoteReceive`, `:414`) |
|
|
594
|
+
| `401 you dont have permission to save data in this gamification` | Clone remoto: tentou salvar coleção em gamification de outra conta (`:433`) |
|
|
595
|
+
| Resposta `{}` (201) em `data/copy` ou `data/diff` | Contas diferentes, gamification inexistente, ou `X-Proxy-Endpoint` preenchido |
|
|
596
|
+
| `data/diff` retorna menos itens que o esperado | `.limit(100)` por coleção; ou item lançou exceção e foi descartado (ver stdout) |
|
|
597
|
+
| `data/copy` falha no meio | `_id` específico inexistente na origem → `save(null)` aborta o loop |
|
|
598
|
+
| `NullPointerException` em `copyDatabase` | `buildinfo` falhou ao detectar a versão do MongoDB |
|
|
599
|
+
| Deletions não aparecem no diff | Comportamento esperado — diff não reporta documentos ausentes na origem |
|
|
600
|
+
|
|
601
|
+
**O que verificar quando "o staging não funciona":**
|
|
602
|
+
1. As credenciais são de **conta** (não Bearer de player)?
|
|
603
|
+
2. Source e target são da **mesma conta**? (copy/diff)
|
|
604
|
+
3. Há `X-Proxy-Endpoint` indevidamente setado tornando copy/diff um no-op?
|
|
605
|
+
4. Para clone remoto: o `apiKey` foi informado para evitar colisão no destino?
|
|
606
|
+
5. Coleções > 100 docs no diff: o resultado está truncado.
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## 10. Exemplos Práticos
|
|
611
|
+
|
|
612
|
+
### Exemplo mínimo — clonar (mesmo servidor, mesma conta, sem logs)
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
POST /v3/gamification/clone/minha_gamification_api_key
|
|
616
|
+
Authorization: Account NTQwZjE0YjAwZmZlZWI4YzJmZT
|
|
617
|
+
Content-Type: application/json
|
|
618
|
+
|
|
619
|
+
{ "name": "Staging - Cópia de Produção" }
|
|
620
|
+
```
|
|
621
|
+
Resposta `201`:
|
|
622
|
+
```json
|
|
623
|
+
{
|
|
624
|
+
"_id": "a1b2c3d4e5f6",
|
|
625
|
+
"name": "Staging - Cópia de Produção",
|
|
626
|
+
"accountId": "540f14b00ffeeb8c2xx00000",
|
|
627
|
+
"created_at": 1700000000000,
|
|
628
|
+
"updated_at": 1700000000000
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
Internamente: banco MongoDB copiado integralmente; `player`, `action_log`, `achievement`, etc. (11 coleções) dropadas; `principal` com `type:0` removidos.
|
|
632
|
+
|
|
633
|
+
### Exemplo — clonar preservando dados de runtime
|
|
634
|
+
|
|
635
|
+
```
|
|
636
|
+
POST /v3/gamification/clone/minha_gamification_api_key?removeLogs=false
|
|
637
|
+
Authorization: Account NTQwZjE0YjAwZmZlZWI4YzJmZT
|
|
638
|
+
|
|
639
|
+
{ "name": "Backup Completo" }
|
|
640
|
+
```
|
|
641
|
+
> Lembre: **somente** `removeLogs=false` preserva os logs. `removeLogs=0`, `removeLogs=no` ou ausência ⇒ remove.
|
|
642
|
+
|
|
643
|
+
### Exemplo — copiar actions e challenges específicos entre ambientes (mesma conta)
|
|
644
|
+
|
|
645
|
+
```
|
|
646
|
+
POST /v3/gamification/data/copy
|
|
647
|
+
Authorization: Account NTQwZjE0YjAwZmZlZWI4YzJmZT
|
|
648
|
+
Content-Type: application/json
|
|
649
|
+
|
|
650
|
+
{
|
|
651
|
+
"source": "staging_api_key",
|
|
652
|
+
"target": "producao_api_key",
|
|
653
|
+
"items": [
|
|
654
|
+
{ "_id": "action_id_novo", "type": "action" },
|
|
655
|
+
{ "_id": "*", "type": "challenge" }
|
|
656
|
+
]
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
Resposta `201`: `{ "status": "OK" }`
|
|
660
|
+
|
|
661
|
+
### Exemplo avançado — diff de todos os actions
|
|
662
|
+
|
|
663
|
+
```
|
|
664
|
+
POST /v3/gamification/data/diff
|
|
665
|
+
Authorization: Account NTQwZjE0YjAwZmZlZWI4YzJmZT
|
|
666
|
+
Content-Type: application/json
|
|
667
|
+
|
|
668
|
+
{
|
|
669
|
+
"source": "staging_api_key",
|
|
670
|
+
"target": "producao_api_key",
|
|
671
|
+
"items": [ { "_id": "*", "type": "action" } ]
|
|
672
|
+
}
|
|
673
|
+
```
|
|
674
|
+
Resposta:
|
|
675
|
+
```json
|
|
676
|
+
{
|
|
677
|
+
"source": "staging_api_key",
|
|
678
|
+
"target": "producao_api_key",
|
|
679
|
+
"diff": [
|
|
680
|
+
{
|
|
681
|
+
"type": "action",
|
|
682
|
+
"_id": "login_action",
|
|
683
|
+
"title": "login",
|
|
684
|
+
"operations": "[{\"op\":\"replace\",\"path\":\"/points\",\"value\":10}]"
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
"type": "action",
|
|
688
|
+
"_id": "nova_action_so_no_staging",
|
|
689
|
+
"title": "boas_vindas",
|
|
690
|
+
"operations": "[op: create]"
|
|
691
|
+
}
|
|
692
|
+
]
|
|
693
|
+
}
|
|
694
|
+
```
|
|
695
|
+
> Limitado a 100 docs da origem. Actions presentes só em produção (deletados no staging) **não** aparecem.
|
|
696
|
+
|
|
697
|
+
### Exemplo — clone para servidor remoto
|
|
698
|
+
|
|
699
|
+
```
|
|
700
|
+
POST /v3/gamification/clone/minha_gamification_api_key
|
|
701
|
+
Authorization: Account TokenDaContaOrigem
|
|
702
|
+
X-Proxy-Authorization: Account TokenDaContaNoServidorRemoto
|
|
703
|
+
X-Proxy-Endpoint: https://eu1.service.funifier.com
|
|
704
|
+
Content-Type: application/json
|
|
705
|
+
|
|
706
|
+
{ "name": "Gamification EU", "apiKey": "minha_gamification_eu" }
|
|
707
|
+
```
|
|
708
|
+
Resposta:
|
|
709
|
+
```json
|
|
710
|
+
{ "all": ["action","challenge","level","point_category"], "out": ["{...}"], "err": [] }
|
|
711
|
+
```
|
|
712
|
+
> Sempre informe `apiKey` no body: sem ele, o destino herda o apiKey da origem e pode colidir (`GameManager.java:244`).
|
|
713
|
+
|
|
714
|
+
### Anti-pattern 1 — `data/copy` entre contas diferentes
|
|
715
|
+
|
|
716
|
+
```
|
|
717
|
+
POST /v3/gamification/data/copy
|
|
718
|
+
Authorization: Account token_conta_A
|
|
719
|
+
|
|
720
|
+
{ "source": "game_da_conta_A", "target": "game_da_conta_B", "items": [ ... ] }
|
|
721
|
+
```
|
|
722
|
+
**Resultado:** `201` com `{}` — **nada é copiado, sem erro**. Para mover entre contas use `POST /clone/{id}` com `X-Proxy-Authorization`.
|
|
723
|
+
|
|
724
|
+
### Anti-pattern 2 — `data/copy` de `_id` inexistente na origem
|
|
725
|
+
|
|
726
|
+
```json
|
|
727
|
+
{ "source": "stg", "target": "prod", "items": [ { "_id": "id_que_nao_existe", "type": "action" } ] }
|
|
728
|
+
```
|
|
729
|
+
**Resultado:** `findOne` retorna null → `target.save(null)` lança exceção → a requisição inteira falha; itens anteriores da lista já foram copiados (sem rollback). Sempre confirme a existência do `_id` na origem antes.
|
|
730
|
+
|
|
731
|
+
### Anti-pattern 3 — confiar no diff para detectar exclusões
|
|
732
|
+
|
|
733
|
+
Usar `data/diff` esperando ver itens "a remover" no destino **não funciona**: deletions nunca são reportadas. Para sincronizar exclusões, compare manualmente as listas de `_id` de cada lado.
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Checklist de Configuração
|
|
738
|
+
|
|
739
|
+
- [ ] Credenciais de **conta** (`Authorization: Account ...`) — Bearer de player não habilita staging.
|
|
740
|
+
- [ ] Limite `games_allowed` da conta não atingido antes de `clone`/`insert`.
|
|
741
|
+
- [ ] Clone entre contas: `X-Proxy-Authorization` com credenciais válidas da conta destino.
|
|
742
|
+
- [ ] Clone remoto: `X-Proxy-Endpoint` com a URL do servidor destino.
|
|
743
|
+
- [ ] Clone remoto: **definir `apiKey` no body** para evitar colisão (senão herda o da origem).
|
|
744
|
+
- [ ] `data/copy`/`data/diff`: confirmar que `source` e `target` são da **mesma conta** (senão `{}` silencioso).
|
|
745
|
+
- [ ] `data/copy`/`data/diff`: **não** enviar `X-Proxy-Endpoint` (vira no-op).
|
|
746
|
+
- [ ] `data/copy` com `_id` específico: confirmar que o documento **existe na origem** (senão a requisição aborta).
|
|
747
|
+
- [ ] `data/diff` com `"_id": "*"`: ciente do limite fixo de **100 docs** por coleção.
|
|
748
|
+
- [ ] Armadilha: `removeLogs` default é `true`; só `removeLogs=false` preserva runtime.
|
|
749
|
+
- [ ] Armadilha: `data/copy` com `"_id": "*"` faz `$merge` — não apaga documentos extras no destino.
|
|
750
|
+
- [ ] Armadilha: clone remoto com `removeLogs` **descarta toda** a coleção `principal` (≠ clone local).
|
|
751
|
+
- [ ] Armadilha: diff não reporta deletions (itens só no destino).
|