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,83 +1,565 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `level`
|
|
2
2
|
|
|
3
3
|
**Acesso Studio:** `/studio/level`
|
|
4
4
|
**API Endpoint:** `/v3/level`
|
|
5
|
+
**Coleção MongoDB:** `level` (configuração global em `level_config`)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
---
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
## 1. Visão Geral
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
O módulo `level` define a **escada de progressão** de um jogo Funifier: uma lista ordenada de níveis que o jogador conquista à medida que acumula pontos. Cada documento da coleção `level` é uma **definição estática** (configuração do designer); o nível efetivamente alcançado por um jogador **não é gravado no documento do jogador**, e sim derivado da coleção `achievement`.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
- Para desbloquear funcionalidades por nível
|
|
14
|
-
- Para segmentar jogadores por experiência
|
|
15
|
-
- Exemplos: Júnior → Pleno → Sênior; faixas coloridas
|
|
13
|
+
Pontos arquiteturais que diferem da intuição REST/CRUD:
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
- O módulo `level` em si (`LevelRest` + `LevelManager` + `LevelDaoMongo`) é apenas **CRUD da definição de níveis**. Ele não contém o motor de promoção.
|
|
16
|
+
- O **motor de level-up real vive em `AchievementManager`** — métodos `evaluateLevelUp`, `findLatestLevelAchieved` e `updatePlayerStatus`. É lá que se decide quando o jogador sobe de nível, não no `LevelManager`.
|
|
17
|
+
- O nível atual de um jogador é o **achievement mais recente do tipo `LEVEL`** (`Achievement.TYPE_LEVEL = 3`). Níveis nunca são removidos/regredidos (comentário do código: *"challenges and levels will never be deducted to avoid problems"*).
|
|
18
|
+
- O snapshot de progresso (`LevelProgress`) é materializado dentro de `player_status.level_progress`, recalculado a cada `updatePlayerStatus`.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
- **Configuração global**: definir qual ponto controla os níveis via `/v3/database/level_config`
|
|
20
|
+
Relação com outros módulos:
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
- **`achievement`** — fonte da verdade. Gera o achievement `TYPE_LEVEL` na promoção e soma os pontos (`sumTotalRewards`) que disparam a subida. `updatePlayerStatus` é o ponto onde o nível é avaliado e materializado.
|
|
23
|
+
- **`point` / `point_category`** — a progressão consome o total de pontos. A categoria considerada depende de `level_config.pointCategory` (global) ou da categoria embutida no nível via `getPoint()`.
|
|
24
|
+
- **`challenge` (Requirement)** — o array `requirements` de um nível reusa a classe `Requirement` para exigir pré-condições (desafios, items, pontos) antes de liberar a promoção.
|
|
25
|
+
- **`trigger`** — eventos `before_win` / `after_win` na entidade `level` são disparados em torno do registro da promoção.
|
|
26
|
+
- **`notification`** — `NotificationDefinition[]` do nível com evento `EVENT_WIN` gera notificação na promoção.
|
|
27
|
+
- **`technique` (GameTechniqueManager)** — usa `level_config` e `getPoint()` para desenhar as ligações ponto → nível ("level up") no mapa do jogo; código de técnica do módulo = **GT85**.
|
|
28
|
+
- **`player_status`** — coleção materializada que guarda `level_progress` por jogador.
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
- [ ] Definir nome do nível (ex: "Apprentice", "Master")
|
|
26
|
-
- [ ] Definir descrição motivadora
|
|
27
|
-
- [ ] Definir minPoints (pontuação mínima crescente)
|
|
28
|
-
- [ ] Definir position (ordem sequencial, iniciando em 0)
|
|
29
|
-
- [ ] Configurar categoria de ponto global (level_config)
|
|
30
|
+
---
|
|
30
31
|
|
|
31
|
-
##
|
|
32
|
+
## 2. Arquitetura e Fluxos
|
|
32
33
|
|
|
33
|
-
###
|
|
34
|
-
**Método:** GET
|
|
35
|
-
**Endpoint:** `/v3/level`
|
|
34
|
+
### 2.1 Classes envolvidas
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
| Classe | Papel |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `com.funifier.engine.level.Level` | Entidade/POJO — documento raiz da coleção `level` |
|
|
39
|
+
| `com.funifier.engine.level.LevelConfig` | Configuração global (`level_config`, `_id: "global"`) — define `pointCategory` |
|
|
40
|
+
| `com.funifier.engine.level.LevelProgress` | DTO de progresso, **não persiste sozinho** — embutido em `player_status.level_progress` |
|
|
41
|
+
| `com.funifier.engine.level.LevelManager` | CRUD + queries auxiliares; **não promove jogadores** |
|
|
42
|
+
| `com.funifier.engine.level.LevelDaoMongo` | Acesso MongoDB direto (Jongo) à coleção `level` |
|
|
43
|
+
| `com.funifier.rest.v3.rest.LevelRest` | Controller REST v3 (`/v3/level`) |
|
|
44
|
+
| `com.funifier.engine.achievement.AchievementManager` | **Motor real de level-up** (`evaluateLevelUp`, `findLatestLevelAchieved`, `updatePlayerStatus`, `findPlayerStatusByLevel`) |
|
|
45
|
+
| `com.funifier.engine.challenge.Requirement` | Pré-requisitos de promoção (reusada de challenge) |
|
|
40
46
|
|
|
41
|
-
**
|
|
47
|
+
> A configuração `level_config` **não tem endpoint próprio** no `LevelRest`. Ela é manipulada pelo módulo genérico de banco/custom-object (`/v3/database/level_config`). `LevelManager.findConfig()` apenas a lê (`_id: "global"`).
|
|
48
|
+
|
|
49
|
+
### 2.2 Pipeline principal — promoção de nível
|
|
50
|
+
|
|
51
|
+
O motor não é acionado pelo `LevelRest`. Ele roda dentro de `AchievementManager.updatePlayerStatus(player, jongo)`, que é chamado **após qualquer registro de achievement** (ex.: ganho de pontos via `fireAction` linha 805/861, compras em `CatalogManager`, trocas em `SwapManager`).
|
|
52
|
+
|
|
53
|
+
Sequência real (linhas 1012-1055 de `AchievementManager.java`):
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
[1] while(evaluateLevelUp(player, jongo, achievements)); // promove 1 nível por iteração até não haver mais
|
|
57
|
+
[2] level = findLatestLevelAchieved(player, jongo); // nível atual = último achievement TYPE_LEVEL
|
|
58
|
+
[3] next_level = clevel.findOne("{position: {$gte: #}}", level.position+1).orderBy("{position:1}")
|
|
59
|
+
// se não há nível atual → primeiro nível por position asc
|
|
60
|
+
[4] total_levels = clevel.count();
|
|
61
|
+
[5] calcula percent_completed e next_points (ver 2.3)
|
|
62
|
+
[6] status.level_progress = new LevelProgress(level, percent, next_level, next_points, total_levels);
|
|
63
|
+
[7] player_status: remove({_id: player}) + save(status) // full replace do snapshot
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Detalhe de `evaluateLevelUp` (linhas 1132-1199) — avalia **uma** promoção:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
currentLevel = findLatestLevelAchieved(player) // pode ser null (jogador sem nível)
|
|
70
|
+
nextLevel = primeiro level com position >= currentLevel.position+1 (asc)
|
|
71
|
+
// se currentLevel == null → primeiro level por position asc
|
|
72
|
+
config = level_config (_id: "global")
|
|
73
|
+
pointCategory = (config.pointCategory não-vazio) ? config.pointCategory : null
|
|
74
|
+
points = sumTotalRewards(player, TYPE_POINT, pointCategory)
|
|
75
|
+
// pointCategory == null → soma TODOS os pontos do jogador
|
|
76
|
+
|
|
77
|
+
se nextLevel != null E points >= nextLevel.minPoints:
|
|
78
|
+
se evaluateRequirements(nextLevel.requirements, player, jongo): // ver 2.4
|
|
79
|
+
trigger.execute(level, EVENT_BEFORE_WIN)
|
|
80
|
+
addAchievement( Achievement(TYPE_LEVEL, item = nextLevel._id) ) // registra promoção
|
|
81
|
+
trigger.execute(level, EVENT_AFTER_WIN)
|
|
82
|
+
envia NotificationDefinition(EVENT_WIN) do nível
|
|
83
|
+
retorna true // → o while volta e tenta o PRÓXIMO nível
|
|
84
|
+
retorna false // → encerra o loop
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Consequência do `while`: se o jogador ganha pontos suficientes para pular vários níveis de uma vez, **cada nível intermediário gera seu próprio achievement, trigger e notificação** — ele "passa" por todos, não salta direto para o final.
|
|
88
|
+
|
|
89
|
+
### Fluxo de promoção — `evaluateLevelUp` (loop de `updatePlayerStatus`)
|
|
90
|
+
|
|
91
|
+
```mermaid
|
|
92
|
+
flowchart TD
|
|
93
|
+
A[updatePlayerStatus] --> B{evaluateLevelUp}
|
|
94
|
+
B --> C[currentLevel = findLatestLevelAchieved]
|
|
95
|
+
C --> D["nextLevel = level position >= current.position+1<br/>(ou 1º level se sem nível)"]
|
|
96
|
+
D --> E["pointCategory = level_config.pointCategory<br/>(ou null = todos os pontos)"]
|
|
97
|
+
E --> F["points = sumTotalRewards(TYPE_POINT, pointCategory)"]
|
|
98
|
+
F --> G{nextLevel != null<br/>E points >= nextLevel.minPoints?}
|
|
99
|
+
G -- não --> H[retorna false → fim do loop]
|
|
100
|
+
G -- sim --> I{evaluateRequirements<br/>nextLevel.requirements?}
|
|
101
|
+
I -- não --> H
|
|
102
|
+
I -- sim --> J[trigger before_win]
|
|
103
|
+
J --> K["addAchievement TYPE_LEVEL<br/>item = nextLevel._id"]
|
|
104
|
+
K --> L[trigger after_win + notificação EVENT_WIN]
|
|
105
|
+
L --> M[retorna true → tenta próximo nível]
|
|
106
|
+
M --> B
|
|
107
|
+
H --> N[calcula LevelProgress e grava player_status]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2.3 Cálculo de `LevelProgress` (percent / next_points)
|
|
111
|
+
|
|
112
|
+
Trecho real (linhas 1020-1055). Define quanto falta para o próximo nível:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
percent_completed = 100 // default quando não há próximo nível
|
|
116
|
+
next_points = 0
|
|
117
|
+
|
|
118
|
+
se next_level != null:
|
|
119
|
+
point1 = (level != null) ? level.getPoint() : null
|
|
120
|
+
point2 = next_level.getPoint()
|
|
121
|
+
// só usa total por categoria se AMBOS os níveis apontam para a MESMA categoria
|
|
122
|
+
total_points = (point1 != null && point2 != null && point1.equals(point2))
|
|
123
|
+
? sumTotalRewards(TYPE_POINT, point1)
|
|
124
|
+
: status.total_points // senão: total global de pontos
|
|
125
|
+
|
|
126
|
+
next_points = max(0, next_level.minPoints - total_points)
|
|
127
|
+
|
|
128
|
+
se level != null:
|
|
129
|
+
diff_total = total_points - level.minPoints
|
|
130
|
+
diff_next = next_level.minPoints - level.minPoints
|
|
131
|
+
senão: // jogador ainda sem nível → base 0
|
|
132
|
+
diff_total = total_points
|
|
133
|
+
diff_next = next_level.minPoints
|
|
134
|
+
|
|
135
|
+
percent_completed = (total_points <= next_level.minPoints)
|
|
136
|
+
? round2( (diff_total / diff_next) * 100 )
|
|
137
|
+
: 100
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`percent_completed` é arredondado para 2 casas decimais (`ParserUtil.setDoubleDecimalScale(x, 2)`).
|
|
141
|
+
|
|
142
|
+
### 2.4 Avaliação de `requirements` no level-up
|
|
143
|
+
|
|
144
|
+
`AchievementManager.evaluateRequirements(Requirement[] all, String player, Jongo jongo)` (linhas 2584-2601) é o validador usado pelo level-up:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
para cada r em requirements:
|
|
148
|
+
period = DateUtil.fromKeywordPeriodExpression(r.period)
|
|
149
|
+
total = sumTotalRewards(player, r.type, r.item, period)
|
|
150
|
+
se r.total > total: retorna false // requisito não atingido
|
|
151
|
+
retorna true
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Comportamento silencioso crítico:** esta variante **ignora `r.operation`**. Não existe dedução — todo requirement de um nível é tratado como `VERIFY` (apenas confere se `total >= r.total`). Configurar `operation: 1 (DEDUCT)` num requirement de nível **não deduz nada**; comporta-se como `VERIFY`.
|
|
155
|
+
|
|
156
|
+
### 2.5 Interação entre módulos na promoção
|
|
157
|
+
|
|
158
|
+
```mermaid
|
|
159
|
+
sequenceDiagram
|
|
160
|
+
participant Act as ActionManager / Catalog / Swap
|
|
161
|
+
participant AM as AchievementManager
|
|
162
|
+
participant Lvl as coleção level / level_config
|
|
163
|
+
participant Ach as coleção achievement
|
|
164
|
+
participant Trg as TriggerManager
|
|
165
|
+
participant Ntf as NotificationManager
|
|
166
|
+
|
|
167
|
+
Act->>AM: updatePlayerStatus(player)
|
|
168
|
+
loop até não promover
|
|
169
|
+
AM->>Ach: sumTotalRewards(TYPE_POINT, pointCategory)
|
|
170
|
+
AM->>Lvl: lê level_config + próximo nível (position)
|
|
171
|
+
AM->>Ach: evaluateRequirements (sumTotalRewards por requirement)
|
|
172
|
+
alt points >= minPoints e requirements ok
|
|
173
|
+
AM->>Trg: execute(level, before_win)
|
|
174
|
+
AM->>Ach: addAchievement(TYPE_LEVEL, nextLevel._id)
|
|
175
|
+
AM->>Trg: execute(level, after_win)
|
|
176
|
+
AM->>Ntf: send(EVENT_WIN)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
AM->>AM: monta LevelProgress
|
|
180
|
+
AM->>Ach: player_status.save(level_progress)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 2.6 CRUD da definição (síncrono)
|
|
184
|
+
|
|
185
|
+
`LevelDaoMongo` (coleção fixa `"level"`):
|
|
186
|
+
|
|
187
|
+
- `add(level)` → se `_id` ausente/vazio gera `Guid.newShortGuid()`, depois `c.save(level)` (**upsert** por `_id`).
|
|
188
|
+
- `update(level)` → `c.remove({_id: level._id})` **seguido de** `c.save(level)` → **full replace** (remove e regrava o documento inteiro; não há merge/patch).
|
|
189
|
+
- `delete(id)` → `c.remove({_id: id})`.
|
|
190
|
+
- `findById(id)` / `findAll()` → `findAll` ordena por `{position: 1}`.
|
|
191
|
+
|
|
192
|
+
`LevelManager.updateAllPositions()` (linhas 32-42): carrega **todos** os níveis ordenados por `{minPoints: 1}` e reatribui `position = índice` (0-based), salvando cada um — realinha `position` à ordem de `minPoints`.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 3. Estrutura dos Objetos
|
|
197
|
+
|
|
198
|
+
### 3.1 `Level` — documento raiz (coleção `level`)
|
|
199
|
+
|
|
200
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
201
|
+
|---|---|---|---|---|
|
|
202
|
+
| `_id` | String | auto (`Guid.newShortGuid()`) | — | Identificador. Gerado no POST se ausente/vazio. |
|
|
203
|
+
| `level` | String | — | Recomendado | Nome/título do nível (ex.: "Apprentice"). É o campo exibido como título no Studio (`GameManager`). |
|
|
204
|
+
| `position` | int | 0 | Sim (na prática) | Ordem do nível. **Governa a navegação "próximo nível"** no motor (não o `minPoints`). |
|
|
205
|
+
| `description` | String | — | Não | Descrição livre. |
|
|
206
|
+
| `minPoints` | int | 0 | Sim (na prática) | Limiar de pontos para promoção a este nível. |
|
|
207
|
+
| `image` | Image | — | Não | Imagem; estrutura `{small, medium, original}` cada um com `url` (vide exemplo do `LevelRest`). |
|
|
208
|
+
| `notifications` | NotificationDefinition[] | null | Não | Notificações disparadas; só as de `event = EVENT_WIN (0)` são usadas na promoção. |
|
|
209
|
+
| `requirements` | Requirement[] | null | Não | Pré-condições de promoção (ver 3.2). Tratadas sempre como `VERIFY`. |
|
|
210
|
+
| `i18n` | Map<String, Map<String,String>> | `{}` | Não | Traduções. Campo público, persistido como veio. |
|
|
211
|
+
| `extra` | Map<String, Object> | `{}` | Não | Campos arbitrários do designer. Persistido. |
|
|
212
|
+
| `techniques` | List\<String\> | `null` | Não | Códigos de técnica de jogo (GT). Ver 3.4. |
|
|
213
|
+
|
|
214
|
+
**Campo computado `point` (getter `getPoint()`):**
|
|
215
|
+
|
|
216
|
+
Não existe campo `point` armazenável diretamente. O getter `Level.getPoint()` (linhas 39-49) **deriva** a categoria de ponto do nível:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
se requirements tem EXATAMENTE 1 item
|
|
220
|
+
E requirements[0].type == TYPE_POINT (0)
|
|
221
|
+
E requirements[0].operation == OPERATION_VERIFY (0)
|
|
222
|
+
E requirements[0].total == minPoints:
|
|
223
|
+
point = requirements[0].item // a categoria de ponto
|
|
224
|
+
senão:
|
|
225
|
+
point = null
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Como Jackson serializa todo getter público sem `@JsonIgnore`, a propriedade `point` **aparece nas respostas REST** e **é gravada no documento Mongo** como snapshot do cálculo. Não há `setPoint` → um `point` enviado no corpo de um POST é **silenciosamente descartado** (regenerado a partir de `requirements`).
|
|
229
|
+
|
|
230
|
+
**Campos removidos/legados:**
|
|
231
|
+
|
|
232
|
+
- `apiKey` (linha 20) — **comentado no código** (legado: vincularia o nível a uma licença específica). Não existe em runtime.
|
|
233
|
+
- `getPoint()` não-nulo só ocorre quando o nível tem exatamente 1 requirement de ponto cujo `total` casa com `minPoints`; em qualquer outra configuração de `requirements`, `point` é `null`.
|
|
234
|
+
|
|
235
|
+
### 3.2 `Requirement` (subentidade de `requirements`)
|
|
236
|
+
|
|
237
|
+
| Campo | Tipo | Descrição |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| `total` | int | Quantidade mínima exigida (no level, sempre comparada como `>=`). |
|
|
240
|
+
| `type` | int | Tipo do item exigido (ver enum abaixo). |
|
|
241
|
+
| `item` | String | Id do item exigido (challenge_id, catalog_item_id, point_category…). |
|
|
242
|
+
| `operation` | int | `0 VERIFY` / `1 DEDUCT`. **Ignorado no level-up** (sempre VERIFY). |
|
|
243
|
+
| `period` | String | Expressão de período (ex.: `-5h;-0h`) para limitar o somatório. |
|
|
244
|
+
| `extra`, `folder`, `restrict`, `perPlayer` | — | Demais campos da classe; não usados pelo fluxo de level. |
|
|
245
|
+
|
|
246
|
+
Enum `type` (`Requirement`): `0 POINT`, `1 CHALLENGE`, `2 CATALOG_ITEM`, `3 LEVEL`, `4 CROWN`, `5 LOTTERY`, `6 MYSTERY_BOX`, `7 CHARACTER_STAR_STAT`, `50 LOTTERY_TICKET`.
|
|
247
|
+
|
|
248
|
+
### 3.3 `LevelConfig` — configuração global (coleção `level_config`)
|
|
249
|
+
|
|
250
|
+
| Campo | Tipo | Padrão | Descrição |
|
|
251
|
+
|---|---|---|---|
|
|
252
|
+
| `_id` | String | `"global"` | Único documento de config; o motor sempre lê `_id: "global"`. |
|
|
253
|
+
| `pointCategory` | String | null | Categoria de ponto que controla a progressão. Se vazio/null → progressão usa o **total de todos os pontos**. |
|
|
254
|
+
|
|
255
|
+
### 3.4 `LevelProgress` — snapshot embutido em `player_status.level_progress`
|
|
256
|
+
|
|
257
|
+
Os nomes JSON vêm dos getters (não dos campos):
|
|
258
|
+
|
|
259
|
+
| Propriedade JSON | Campo Java | Tipo | Descrição |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| `level` | `level` | Level | Nível atual (null se o jogador ainda não conquistou nenhum). |
|
|
262
|
+
| `percent` | `percent_completed` | double | % concluído rumo ao próximo nível (2 casas). 100 se não há próximo. |
|
|
263
|
+
| `next_level` | `next_level` | Level | Próximo nível por `position`; null no topo da escada. |
|
|
264
|
+
| `next_points` | `next_points` | int | Pontos faltantes para o próximo nível (`max(0, …)`). |
|
|
265
|
+
| `total_levels` | `total_levels` | int | Total de documentos na coleção `level`. |
|
|
266
|
+
|
|
267
|
+
### 3.5 Técnicas de jogo (`techniques`)
|
|
268
|
+
|
|
269
|
+
O único código associado ao módulo `level` é:
|
|
270
|
+
|
|
271
|
+
| Código GT | Significado operacional |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `GT85` | **Level up** — técnica de gamificação "níveis/progressão". Atribuída automaticamente a níveis sem `techniques` por `GameTechniqueManager.autoConfigureMissingTechniqueFields()` (constante `LEVEL_CODE = "GT85"`). |
|
|
274
|
+
|
|
275
|
+
> O campo `techniques` é uma `List<String>` livre; o sistema só **autoinjeta** `GT85` quando o nível não tem técnica alguma. Outros códigos GT pertencem a outros módulos (ex.: `GT01` point, `GT35` challenge, `GT03` leaderboard).
|
|
276
|
+
|
|
277
|
+
### 3.6 Ciclo de vida do nível de um jogador
|
|
278
|
+
|
|
279
|
+
```mermaid
|
|
280
|
+
stateDiagram-v2
|
|
281
|
+
[*] --> SemNivel: jogador novo (nenhum achievement TYPE_LEVEL)
|
|
282
|
+
SemNivel --> Nivel0: points >= level[pos=0].minPoints + requirements
|
|
283
|
+
Nivel0 --> Nivel1: points >= próximo.minPoints + requirements
|
|
284
|
+
Nivel1 --> NivelN: ... (1 promoção por iteração do while)
|
|
285
|
+
NivelN --> NivelN: nunca regride (levels não são deduzidos)
|
|
286
|
+
NivelN --> [*]: topo da escada (next_level == null)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 4. Endpoints
|
|
292
|
+
|
|
293
|
+
Base: `@Path("v3/level")`, `Produces: application/json; charset=UTF-8`. Autenticação: `@BeanParam AuthBean` (Bearer token) em todos.
|
|
294
|
+
|
|
295
|
+
### `GET /v3/level`
|
|
296
|
+
|
|
297
|
+
| Aspecto | Detalhe |
|
|
298
|
+
|---|---|
|
|
299
|
+
| Finalidade | Lista todos os níveis. |
|
|
300
|
+
| Implementação | `LevelManager.findAll()` → ordenado por `position` asc. |
|
|
301
|
+
| Resposta | Array de `Level`; campos nulos removidos (`JsonUtil.toJsonRemoveNullFields`). |
|
|
302
|
+
|
|
303
|
+
### `GET /v3/level/{id}`
|
|
304
|
+
|
|
305
|
+
| Aspecto | Detalhe |
|
|
306
|
+
|---|---|
|
|
307
|
+
| Finalidade | Busca um nível por `_id`. |
|
|
308
|
+
| Resposta | Objeto `Level` (200) ou objeto vazio se não encontrado. |
|
|
309
|
+
|
|
310
|
+
### `POST /v3/level`
|
|
311
|
+
|
|
312
|
+
| Aspecto | Detalhe |
|
|
313
|
+
|---|---|
|
|
314
|
+
| Finalidade | **Cria OU atualiza** um nível. |
|
|
315
|
+
| Full replace ou patch | **Full replace** — `c.save()` faz upsert por `_id`. |
|
|
316
|
+
| Status | `201 CREATED`. |
|
|
317
|
+
|
|
318
|
+
**Comportamento real:**
|
|
319
|
+
- Sem `_id` no corpo → gera `_id` curto automático.
|
|
320
|
+
- Com `_id` existente → **sobrescreve o documento inteiro** (não é merge). Campos omitidos são perdidos.
|
|
321
|
+
- **Não existe `PUT /v3/level/{id}`** — a atualização de um nível é feita por este POST com `_id` preenchido.
|
|
322
|
+
- `point` enviado no corpo é descartado (recalculado de `requirements`).
|
|
323
|
+
|
|
324
|
+
**Exemplo (do javadoc do `LevelRest`):**
|
|
42
325
|
```json
|
|
326
|
+
POST /v3/level
|
|
43
327
|
{
|
|
44
|
-
"level": "
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"minPoints":
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"_id": "L0"
|
|
328
|
+
"level": "Level",
|
|
329
|
+
"description": "Level Description",
|
|
330
|
+
"position": 1,
|
|
331
|
+
"minPoints": 100,
|
|
332
|
+
"image": {
|
|
333
|
+
"small": { "url": "http://funifier.com/level.png" },
|
|
334
|
+
"medium": { "url": "http://funifier.com/level.png" },
|
|
335
|
+
"original": { "url": "http://funifier.com/level.png" }
|
|
336
|
+
}
|
|
54
337
|
}
|
|
55
338
|
```
|
|
56
339
|
|
|
57
|
-
###
|
|
58
|
-
|
|
59
|
-
|
|
340
|
+
### `PUT /v3/level/position`
|
|
341
|
+
|
|
342
|
+
| Aspecto | Detalhe |
|
|
343
|
+
|---|---|
|
|
344
|
+
| Finalidade | Recalcula `position` de todos os níveis ordenando por `minPoints` asc. |
|
|
345
|
+
| Implementação | `LevelManager.updateAllPositions()` → `position = índice` (0-based). |
|
|
346
|
+
| Corpo | Nenhum (não recebe payload). |
|
|
347
|
+
| Resposta | `201 CREATED` `{ "status": "OK" }`. |
|
|
348
|
+
|
|
349
|
+
### `DELETE /v3/level/{id}`
|
|
350
|
+
|
|
351
|
+
| Aspecto | Detalhe |
|
|
352
|
+
|---|---|
|
|
353
|
+
| Finalidade | Remove a definição do nível. |
|
|
354
|
+
| Resposta | `204 NO_CONTENT`. |
|
|
355
|
+
| Efeito colateral | **Nenhum** sobre achievements/jogadores. Achievements `TYPE_LEVEL` que apontam para o `_id` removido permanecem; `findLatestLevelAchieved` pode retornar um nível inexistente (ver §5). |
|
|
356
|
+
|
|
357
|
+
> **Não exposto via `/v3/level`:** gestão de `level_config`. Use o módulo de banco: `PUT /v3/database/level_config` com `{ "_id": "global", "pointCategory": "<categoria>" }`.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 5. Regras de Negócio
|
|
362
|
+
|
|
363
|
+
Regras que existem no código mas não no schema:
|
|
364
|
+
|
|
365
|
+
1. **`position` define a ordem, `minPoints` define o limiar.** O motor escolhe o "próximo nível" por `{position: {$gte: atual.position+1}}` ordenado por `position`. O gatilho de promoção compara `points >= nextLevel.minPoints`. Se `position` e `minPoints` ficarem **desalinhados** (ex.: o nível com `position` seguinte tem `minPoints` menor que o atual), o jogador pode subir de forma inesperada ou um nível pode ficar inalcançável. Use `PUT /v3/level/position` para realinhar.
|
|
366
|
+
|
|
367
|
+
2. **Categoria de ponto da progressão:**
|
|
368
|
+
- `level_config.pointCategory` definido → conta apenas aquela categoria.
|
|
369
|
+
- vazio/ausente → soma **todos** os pontos (`sumTotalRewards(..., null)`).
|
|
370
|
+
- O `LevelConfig` é **global por tenant**; não há configuração por nível além do truque do `getPoint()`.
|
|
371
|
+
|
|
372
|
+
3. **`getPoint()` por nível** só vale quando o nível tem exatamente 1 requirement de ponto com `total == minPoints`. O `percent_completed` só usa esse total por categoria quando **ambos** (atual e próximo) apontam para a **mesma** categoria; caso contrário usa o total global.
|
|
373
|
+
|
|
374
|
+
4. **Promoção é monotônica e em cascata.** Níveis nunca regridem; um único `updatePlayerStatus` pode promover vários níveis em sequência (um achievement/trigger/notificação por nível).
|
|
375
|
+
|
|
376
|
+
5. **Requirements são sempre VERIFY** no contexto de nível (operação ignorada). Não há dedução de pontos/itens ao subir de nível.
|
|
377
|
+
|
|
378
|
+
6. **`findPlayerStatusByLevel(Level level)`** consulta `player_status` com `{level_progress.level: <objeto Level inteiro>}` — é uma **igualdade de objeto embutido completo**, frágil: qualquer divergência de campo (ex.: `point` recalculado, `image`, `extra`) faz o match falhar.
|
|
379
|
+
|
|
380
|
+
7. **Consistência eventual:** o nível só muda quando `updatePlayerStatus` roda (após ganho de pontos, compra, troca, etc.). Alterar `minPoints` de um nível **não** rebaixa nem promove jogadores retroativamente até o próximo `updatePlayerStatus` daquele jogador.
|
|
381
|
+
|
|
382
|
+
8. **Multi-tenant:** todo acesso passa por `FrontController.getInstance(apiKey)` → `ManagerFactory` → conexão Jongo do banco do tenant. Níveis e `level_config` são isolados por organização.
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 6. Comportamentos Automáticos
|
|
387
|
+
|
|
388
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
389
|
+
|---|---|---|---|
|
|
390
|
+
| Geração de `_id` | POST sem `_id` | Cria GUID curto | Sim (`level`) |
|
|
391
|
+
| Promoção de nível | `updatePlayerStatus` após ganho de pontos | Registra achievement `TYPE_LEVEL` | Sim (`achievement`) |
|
|
392
|
+
| Trigger `before_win`/`after_win` | Em torno de cada promoção (entidade `level`) | Executa triggers configurados | Conforme trigger |
|
|
393
|
+
| Notificação de nível | Promoção, `NotificationDefinition` com `EVENT_WIN` | Envia notificação ao jogador | Conforme notification |
|
|
394
|
+
| Recalculo de `LevelProgress` | Todo `updatePlayerStatus` | Atualiza `player_status.level_progress` | Sim (`player_status`, full replace) |
|
|
395
|
+
| Snapshot `point` | `c.save(level)` (POST) | Grava propriedade derivada `point` | Sim (`level`) |
|
|
396
|
+
| Autoinjeção `GT85` | `autoConfigureMissingTechniqueFields()` | Adiciona técnica a níveis sem `techniques` | Sim (`level`) |
|
|
397
|
+
|
|
398
|
+
### Encadeamento na promoção
|
|
399
|
+
|
|
400
|
+
```mermaid
|
|
401
|
+
flowchart LR
|
|
402
|
+
P[Ganho de pontos] --> U[updatePlayerStatus]
|
|
403
|
+
U --> E{evaluateLevelUp:<br/>points >= minPoints<br/>e requirements?}
|
|
404
|
+
E -- sim --> BW[trigger before_win]
|
|
405
|
+
BW --> AC[achievement TYPE_LEVEL]
|
|
406
|
+
AC --> AW[trigger after_win]
|
|
407
|
+
AW --> NT[notificação EVENT_WIN]
|
|
408
|
+
NT --> E
|
|
409
|
+
E -- não --> LP[grava level_progress<br/>em player_status]
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## 7. Suportado vs NÃO Suportado
|
|
415
|
+
|
|
416
|
+
### ✅ Suportado
|
|
417
|
+
|
|
418
|
+
- CRUD da definição de níveis (`GET`, `GET /{id}`, `POST`, `DELETE /{id}`).
|
|
419
|
+
- Reordenação automática por `minPoints` (`PUT /v3/level/position`).
|
|
420
|
+
- Progressão automática por pontos (categoria global ou total).
|
|
421
|
+
- Pré-requisitos de promoção via `requirements` (como VERIFY).
|
|
422
|
+
- Triggers `before_win`/`after_win` e notificações `EVENT_WIN` por nível.
|
|
423
|
+
- Materialização de progresso (`percent`, `next_points`, `next_level`, `total_levels`) em `player_status`.
|
|
424
|
+
- i18n e `extra` arbitrários.
|
|
425
|
+
- Isolamento multi-tenant.
|
|
426
|
+
|
|
427
|
+
### ❌ NÃO Suportado / Armadilhas
|
|
428
|
+
|
|
429
|
+
- **`PUT /v3/level/{id}` não existe** — atualização só via `POST` (full replace).
|
|
430
|
+
- **`POST` não faz patch** — campos omitidos são apagados do documento.
|
|
431
|
+
- **`operation` em `requirements` de nível é ignorado** — sempre VERIFY; não há dedução ao subir.
|
|
432
|
+
- **`LevelManager.findCurrentByPoints` e `findNextByPoints` são código morto** — nenhum chamador no projeto (confirmado via grafo). Além disso usam `minPoints` (não `position`), divergindo do motor real. Não confie neles para entender o comportamento.
|
|
433
|
+
- **`level_config` não tem endpoint no módulo level** — apenas leitura via `findConfig()`; escrita via `/v3/database/level_config`.
|
|
434
|
+
- **`point` enviado no corpo é descartado** — é derivado de `requirements`.
|
|
435
|
+
- **`apiKey`** no `Level` é legado comentado — inexistente em runtime.
|
|
436
|
+
- **DELETE não limpa achievements** — remover um nível não remove achievements `TYPE_LEVEL` que o referenciam; `findLatestLevelAchieved` pode retornar id órfão e `findById` devolver objeto vazio.
|
|
437
|
+
- **Sem rebaixamento** — reduzir pontos do jogador (estorno) **não** o rebaixa; o achievement `TYPE_LEVEL` mais recente permanece como nível atual.
|
|
438
|
+
- **`findPlayerStatusByLevel`** depende de igualdade de objeto embutido completo — quebradiço.
|
|
439
|
+
- Não há job/scheduler dedicado ao módulo `level` (não localizado no código).
|
|
60
440
|
|
|
61
|
-
|
|
62
|
-
**Método:** PUT
|
|
63
|
-
**Endpoint:** `/v3/database/level_config`
|
|
441
|
+
---
|
|
64
442
|
|
|
65
|
-
|
|
443
|
+
## 8. Segurança e Permissões
|
|
444
|
+
|
|
445
|
+
- **Autenticação:** todos os endpoints exigem `AuthBean` (Bearer token). Não há checagem de papel/role explícita dentro de `LevelRest` — o controle de acesso administrativo depende da chave/token do tenant resolvido por `FrontController.getInstance(apiKey)`.
|
|
446
|
+
- **Isolamento por organização:** cada tenant opera no próprio banco (conexão Jongo via `ManagerFactory`). `level` e `level_config` não cruzam tenants.
|
|
447
|
+
- **Superfície de script (Groovy):** `PublicManager` importa `com.funifier.engine.level.Level` em scripts dinâmicos (linha 172). Conteúdo de scripts públicos/customizados que manipulem níveis deve ser auditado — execução de script é um vetor sensível.
|
|
448
|
+
- **Operações destrutivas silenciosas:** `POST` sobrescreve o documento inteiro e `update` (DAO) faz `remove` + `save`. Uma chamada malformada pode apagar campos sem erro. `PUT /v3/level/position` reescreve `position` de **todos** os níveis de uma vez.
|
|
449
|
+
- Não há sanitização explícita dos campos do `Level` antes de `c.save` — confie no contrato do `AuthBean` e valide entradas na camada cliente.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 9. Observabilidade e Troubleshooting
|
|
454
|
+
|
|
455
|
+
**Diagnóstico — o jogador subiu de nível?**
|
|
456
|
+
```
|
|
457
|
+
GET /v3/level # confere a escada e a ordem (position)
|
|
458
|
+
GET /v3/level/<id> # confere minPoints/requirements de um nível
|
|
459
|
+
# nível atual do jogador está em player_status.level_progress:
|
|
460
|
+
GET /v3/player/<player_id>/status # ou inspecione a coleção player_status
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Queries úteis (via /v3/database, coleção achievement):**
|
|
464
|
+
```jsonc
|
|
465
|
+
// histórico de promoções de um jogador (type 3 = LEVEL)
|
|
466
|
+
{ "player": "<player_id>", "type": 3 } // ordenar por time desc → nível atual
|
|
467
|
+
|
|
468
|
+
// total de pontos que o motor enxerga (categoria global de level_config)
|
|
469
|
+
// equivale a sumTotalRewards(player, TYPE_POINT, pointCategory)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Erros comuns e causas:**
|
|
473
|
+
|
|
474
|
+
| Sintoma | Causa provável |
|
|
475
|
+
|---|---|
|
|
476
|
+
| Jogador não sobe de nível mesmo com pontos | `level_config.pointCategory` aponta para categoria errada/inexistente → `points = 0`; ou `requirements` do próximo nível não atendidos; ou `position` desalinhado de `minPoints`. |
|
|
477
|
+
| Jogador "pula" níveis com 1 ganho | Comportamento esperado: o `while` promove em cascata (cada nível ainda gera achievement/notificação). |
|
|
478
|
+
| `percent` parece errado | Categorias de ponto de `getPoint()` divergem entre nível atual e próximo → cálculo cai no total global. |
|
|
479
|
+
| Nível atual aponta para id inexistente | Nível foi deletado mas o achievement `TYPE_LEVEL` permaneceu. |
|
|
480
|
+
| Atualizar um nível "apagou" campos | `POST` é full replace; reenvie o documento completo. |
|
|
481
|
+
| `requirements` com dedução não deduziu | Esperado: no level, `operation` é ignorado (sempre VERIFY). |
|
|
482
|
+
|
|
483
|
+
**O que verificar quando algo não funciona:** confirme `level_config`; confirme que `position` é sequencial e coerente com `minPoints` (rode `PUT /v3/level/position`); confirme que algum evento dispara `updatePlayerStatus` (ganho de pontos, compra, troca).
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## 10. Exemplos Práticos
|
|
488
|
+
|
|
489
|
+
### 10.1 Exemplo mínimo funcional (progressão por total de pontos)
|
|
490
|
+
```json
|
|
491
|
+
POST /v3/level
|
|
492
|
+
{ "_id": "L0", "level": "Iniciante", "position": 0, "minPoints": 0 }
|
|
493
|
+
```
|
|
494
|
+
```json
|
|
495
|
+
POST /v3/level
|
|
496
|
+
{ "_id": "L1", "level": "Bronze", "position": 1, "minPoints": 100 }
|
|
497
|
+
```
|
|
498
|
+
Com `level_config` ausente/vazio, a soma de **todos** os pontos governa a subida.
|
|
499
|
+
|
|
500
|
+
### 10.2 Exemplo avançado (categoria específica + requirement de desafio)
|
|
501
|
+
```json
|
|
502
|
+
PUT /v3/database/level_config
|
|
503
|
+
{ "_id": "global", "pointCategory": "xp" }
|
|
504
|
+
```
|
|
505
|
+
```json
|
|
506
|
+
POST /v3/level
|
|
507
|
+
{
|
|
508
|
+
"_id": "L2",
|
|
509
|
+
"level": "Prata",
|
|
510
|
+
"position": 2,
|
|
511
|
+
"minPoints": 500,
|
|
512
|
+
"description": "Requer 500 xp e o desafio de onboarding.",
|
|
513
|
+
"image": { "medium": { "url": "https://cdn.exemplo.com/silver.png" } },
|
|
514
|
+
"requirements": [
|
|
515
|
+
{ "type": 1, "item": "challenge_onboarding", "total": 1, "operation": 0 }
|
|
516
|
+
],
|
|
517
|
+
"notifications": [ { "event": 0, "scope": 0, "title": "Você chegou ao Prata!" } ],
|
|
518
|
+
"i18n": { "en": { "level": "Silver" } },
|
|
519
|
+
"techniques": ["GT85"]
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
Para "minPoints em uma categoria via `getPoint()`", use **um único** requirement de ponto com `total == minPoints`:
|
|
66
523
|
```json
|
|
67
524
|
{
|
|
68
|
-
"_id": "
|
|
69
|
-
"
|
|
525
|
+
"_id": "L3", "level": "Ouro", "position": 3, "minPoints": 1000,
|
|
526
|
+
"requirements": [ { "type": 0, "item": "xp", "total": 1000, "operation": 0 } ]
|
|
70
527
|
}
|
|
71
528
|
```
|
|
529
|
+
Aqui `getPoint()` retornará `"xp"` e a resposta REST trará `"point": "xp"`.
|
|
530
|
+
|
|
531
|
+
### 10.3 Anti-pattern (o que NÃO fazer)
|
|
532
|
+
```json
|
|
533
|
+
// ❌ ERRADO: tentar "atualizar só o minPoints" via POST parcial
|
|
534
|
+
POST /v3/level
|
|
535
|
+
{ "_id": "L2", "minPoints": 600 }
|
|
536
|
+
```
|
|
537
|
+
Isto **apaga** `level`, `position`, `requirements`, `image` etc. do nível L2 (full replace). Sempre reenvie o documento completo.
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
// ❌ ERRADO: esperar dedução de pontos ao subir de nível
|
|
541
|
+
{ "type": 0, "item": "xp", "total": 100, "operation": 1 } // operation DEDUCT é ignorado no level → vira VERIFY
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
```jsonc
|
|
545
|
+
// ❌ ERRADO: position fora de ordem com minPoints
|
|
546
|
+
{ "_id": "A", "position": 0, "minPoints": 500 }
|
|
547
|
+
{ "_id": "B", "position": 1, "minPoints": 100 } // "próximo" nível tem minPoints menor → progressão incoerente
|
|
548
|
+
// Rode PUT /v3/level/position para realinhar position ao minPoints.
|
|
549
|
+
```
|
|
72
550
|
|
|
73
|
-
|
|
74
|
-
**Método:** PUT
|
|
75
|
-
**Endpoint:** `/v3/level/position`
|
|
76
|
-
**Descrição:** Recalcula posições com base no minPoints.
|
|
551
|
+
---
|
|
77
552
|
|
|
78
|
-
##
|
|
553
|
+
## Checklist de Configuração
|
|
79
554
|
|
|
80
|
-
- [ ]
|
|
81
|
-
- [ ]
|
|
82
|
-
- [ ]
|
|
83
|
-
- [ ]
|
|
555
|
+
- [ ] `_id` definido (ou deixe em branco para autogerar) e estável entre updates.
|
|
556
|
+
- [ ] `level` (nome) preenchido — é o título exibido no Studio.
|
|
557
|
+
- [ ] `minPoints` crescente e coerente com `position`.
|
|
558
|
+
- [ ] `position` sequencial a partir de 0 (rode `PUT /v3/level/position` após mudanças em `minPoints`).
|
|
559
|
+
- [ ] `level_config` (`_id: "global"`) configurado **antes** se a progressão deve usar uma categoria específica; senão a soma de todos os pontos é usada.
|
|
560
|
+
- [ ] A `pointCategory` de `level_config` existe no módulo `point`.
|
|
561
|
+
- [ ] Itens/desafios citados em `requirements` existem antes de criar o nível.
|
|
562
|
+
- [ ] Ciente: `POST` é full replace — sempre envie o documento completo ao "atualizar".
|
|
563
|
+
- [ ] Ciente: `operation` em `requirements` de nível é ignorado (sempre VERIFY).
|
|
564
|
+
- [ ] Ciente: deletar um nível não limpa achievements `TYPE_LEVEL` que o referenciam.
|
|
565
|
+
- [ ] `notifications` usam `event: 0 (EVENT_WIN)` para disparar na promoção.
|