funifier-mcp 0.2.24 → 0.2.25
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 +1 -0
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +1 -0
- package/datasource-funifier-docs/.search-index.json +5588 -4475
- package/datasource-funifier-docs/.skills-map.json +3 -1
- package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +511 -0
- package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +1 -1
- package/datasource-funifier-docs/knowledge/modules/action.md +96 -39
- package/datasource-funifier-docs/knowledge/modules/challenge.md +285 -48
- package/datasource-funifier-docs/knowledge/modules/leaderboard.md +173 -71
- package/datasource-funifier-docs/knowledge/modules/point.md +108 -37
- package/datasource-funifier-docs/knowledge/modules/trigger.md +1 -1
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +11 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/core/api-client.d.ts.map +1 -1
- package/dist/core/api-client.js +1 -1
- package/dist/core/api-client.js.map +1 -1
- package/dist/core/constants.d.ts +1 -0
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +1 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/mcp/bundle.js +68 -67
- package/dist/mcp/index.js +2 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/tools/get.d.ts.map +1 -1
- package/dist/mcp/tools/get.js +22 -8
- package/dist/mcp/tools/get.js.map +1 -1
- package/dist/mcp/tools/list.d.ts.map +1 -1
- package/dist/mcp/tools/list.js +9 -2
- package/dist/mcp/tools/list.js.map +1 -1
- package/dist/mcp/tools/save.d.ts.map +1 -1
- package/dist/mcp/tools/save.js +88 -7
- package/dist/mcp/tools/save.js.map +1 -1
- package/package.json +3 -2
- package/skills/funifier-create-action/SKILL.md +58 -18
- package/skills/funifier-create-aggregate/SKILL.md +1 -0
- package/skills/funifier-create-challenge/SKILL.md +1 -0
- package/skills/funifier-create-custom-page/SKILL.md +1 -0
- package/skills/funifier-create-leaderboard/SKILL.md +90 -18
- package/skills/funifier-create-level/SKILL.md +1 -0
- package/skills/funifier-create-point/SKILL.md +60 -18
- package/skills/funifier-create-quiz/SKILL.md +1 -0
- package/skills/funifier-create-scheduler/SKILL.md +1 -0
- package/skills/funifier-create-trigger/SKILL.md +110 -19
- package/skills/funifier-create-virtual-good/SKILL.md +1 -0
- package/skills/funifier-debug/SKILL.md +2 -0
- package/skills/funifier-help/SKILL.md +1 -0
- package/skills/funifier-implement-frontend/SKILL.md +1 -0
- package/skills/funifier-index/SKILL.md +9 -1
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"knowledge/modules/trigger.md",
|
|
4
4
|
"knowledge/guides/triggers-guide.md",
|
|
5
5
|
"knowledge/guides/java-entities.md",
|
|
6
|
-
"knowledge/guides/java-managers.md"
|
|
6
|
+
"knowledge/guides/java-managers.md",
|
|
7
|
+
"knowledge/guides/trigger-examples.md"
|
|
7
8
|
],
|
|
8
9
|
"funifier-create-scheduler": [
|
|
9
10
|
"knowledge/modules/scheduler.md",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"knowledge/guides/java-libraries.md",
|
|
62
63
|
"knowledge/modules/trigger.md",
|
|
63
64
|
"knowledge/guides/triggers-guide.md",
|
|
65
|
+
"knowledge/guides/trigger-examples.md",
|
|
64
66
|
"knowledge/modules/scheduler.md",
|
|
65
67
|
"knowledge/guides/aggregates.md",
|
|
66
68
|
"knowledge/modules/public.md"
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# Trigger Examples — Padrões de Produção
|
|
2
|
+
|
|
3
|
+
Referência prática de eventos, entidades, constantes e exemplos reais para criação de triggers no Funifier.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Eventos Oficiais (dropdown do Studio)
|
|
8
|
+
|
|
9
|
+
| Evento | Quando é acionado |
|
|
10
|
+
|--------|-------------------|
|
|
11
|
+
| `before_win` | Antes de registrar uma conquista (ponto, desafio, item) |
|
|
12
|
+
| `after_win` | Depois de registrar uma conquista |
|
|
13
|
+
| `before_lose` | Antes de remover uma conquista |
|
|
14
|
+
| `after_lose` | Depois de remover uma conquista |
|
|
15
|
+
| `before_create` | Antes de persistir um novo documento |
|
|
16
|
+
| `after_create` | Depois de persistir um novo documento |
|
|
17
|
+
| `before_update` | Antes de atualizar um documento existente |
|
|
18
|
+
| `after_update` | Depois de atualizar um documento |
|
|
19
|
+
| `before_delete` | Antes de deletar um documento |
|
|
20
|
+
| `after_delete` | Depois de deletar um documento |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Eventos Estendidos (free-text only — não aparecem no dropdown)
|
|
25
|
+
|
|
26
|
+
| Evento | Quando é acionado |
|
|
27
|
+
|--------|-------------------|
|
|
28
|
+
| `before_bulk` | Antes de um insert/update em lote via API |
|
|
29
|
+
| `after_bulk` | Depois de um insert/update em lote via API |
|
|
30
|
+
| `csv_before_bulk` | Antes de processar um import CSV |
|
|
31
|
+
| `csv_after_bulk` | Depois de processar um import CSV |
|
|
32
|
+
|
|
33
|
+
> ⚠ Nesses eventos a variável `entity` recebe uma **List** (não um único documento). Itere com `for (Object item : entity)`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Entidades Disponíveis
|
|
38
|
+
|
|
39
|
+
**Dropdown hardcoded do Studio:**
|
|
40
|
+
`action`, `catalog`, `catalog_item`, `crown`, `leaderboard`, `level`, `player`, `team`, `challenge`, `point_category`, `character_star_stats_level`, `upload`, `mystery_box`, `lottery`
|
|
41
|
+
|
|
42
|
+
**Entidades dinâmicas** (carregadas via `/v3/database/collections`):
|
|
43
|
+
`achievement` e todas as coleções customizadas (`__c`) do projeto aparecem aqui.
|
|
44
|
+
Excluídas: `action_log`, `challenge_progress`, `player_status`, `security`, `technique_field`, `join_log`.
|
|
45
|
+
|
|
46
|
+
**Free-text**: o formulário do Studio permite digitar qualquer nome de entidade/evento não listado.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Constantes de Achievement
|
|
51
|
+
|
|
52
|
+
| Constante Java | Valor int | Descrição |
|
|
53
|
+
|----------------|-----------|-----------|
|
|
54
|
+
| `Achievement.TYPE_POINT` | `0` | Ponto acumulado |
|
|
55
|
+
| `Achievement.TYPE_CHALLENGE` | `1` | Desafio completado |
|
|
56
|
+
| `Achievement.TYPE_VIRTUAL_GOOD` | `2` | Item de catálogo comprado |
|
|
57
|
+
| `Achievement.TYPE_LEVEL` | `3` | Nível alcançado |
|
|
58
|
+
| `Achievement.TYPE_LOTTERY` | `5` | Loteria |
|
|
59
|
+
| `Achievement.TYPE_COMPETITION` | `9` | Competição |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Campos das Entidades Java nos Triggers
|
|
64
|
+
|
|
65
|
+
### ActionLog (`entity` em triggers de `action`)
|
|
66
|
+
```java
|
|
67
|
+
entity.actionId // String — ID da ação (ex: "complete_task")
|
|
68
|
+
entity.userId // String — ID do jogador que executou
|
|
69
|
+
entity.attributes // Map<String, Object> — atributos da ação
|
|
70
|
+
entity.time // Date — momento da execução
|
|
71
|
+
entity.id // String — alias para entity._id
|
|
72
|
+
entity._id // String — ID do registro de log
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Player (`entity` em triggers de `player`)
|
|
76
|
+
```java
|
|
77
|
+
entity._id / entity.id // String — ID do jogador
|
|
78
|
+
entity.name // String
|
|
79
|
+
entity.email // String
|
|
80
|
+
entity.extra // Map<String, Object> — campos customizados
|
|
81
|
+
entity.teams // List<String> — IDs das equipes
|
|
82
|
+
entity.image // ImageSet — getOriginal().getUrl(), getMedium(), getSmall()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Achievement (`entity` em triggers de `achievement` / `point_category`)
|
|
86
|
+
```java
|
|
87
|
+
entity._id / entity.id // String
|
|
88
|
+
entity.player // String — ID do jogador
|
|
89
|
+
entity.total // double — quantidade
|
|
90
|
+
entity.type // int — ver tabela de constantes acima
|
|
91
|
+
entity.item // String — ID do ponto/desafio/item
|
|
92
|
+
entity.time // Date
|
|
93
|
+
entity.extra // Map<String, Object>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## context.extra — Campos Disponíveis
|
|
99
|
+
|
|
100
|
+
Disponível nos eventos `before_win`, `before_create`, `after_create` quando a trigger é acionada por uma cadeia de conquistas:
|
|
101
|
+
|
|
102
|
+
```java
|
|
103
|
+
context.extra.parentAchievement // Achievement que gerou este evento
|
|
104
|
+
context.extra.parentAchievement.id // String
|
|
105
|
+
context.extra.parentAchievement.type // int (usar constantes acima)
|
|
106
|
+
context.extra.parentAchievement.item // String
|
|
107
|
+
context.extra.parentAchievement.player // String
|
|
108
|
+
context.extra.parentAchievement.total // double
|
|
109
|
+
|
|
110
|
+
context.extra.parentActionLog // ActionLog que originou a cadeia
|
|
111
|
+
context.extra.parentActionLog.id // String
|
|
112
|
+
context.extra.parentActionLog.actionId // String
|
|
113
|
+
context.extra.parentActionLog.userId // String
|
|
114
|
+
context.extra.parentActionLog.attributes // Map
|
|
115
|
+
|
|
116
|
+
context.extra.originActionLog // ActionLog raiz (ponta da cadeia)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## ⚠ Regra Crítica: Triggers NÃO impedem persistência via throw
|
|
122
|
+
|
|
123
|
+
O Funifier **sempre salva o documento** antes de executar o trigger. Não é possível usar `throw` para cancelar a persistência.
|
|
124
|
+
|
|
125
|
+
**Padrão correto para rejeitar uma criação:**
|
|
126
|
+
1. Use `after_create` (não `before_create`)
|
|
127
|
+
2. Valide no trigger; se inválido:
|
|
128
|
+
```java
|
|
129
|
+
database.delete(entity._id, "collection__c");
|
|
130
|
+
entity.clear();
|
|
131
|
+
entity.put("message", "Motivo da rejeição");
|
|
132
|
+
return;
|
|
133
|
+
```
|
|
134
|
+
3. Para lógica complexa, use uma `BusinessException` interna para centralizar o cleanup:
|
|
135
|
+
```java
|
|
136
|
+
// A exceção é capturada dentro do próprio trigger — não propaga para a plataforma
|
|
137
|
+
try {
|
|
138
|
+
if (condicaoInvalida) throw new BusinessException("Motivo");
|
|
139
|
+
// ... mais validações ...
|
|
140
|
+
} catch (BusinessException e) {
|
|
141
|
+
database.delete(entity._id, "collection__c");
|
|
142
|
+
entity.clear();
|
|
143
|
+
entity.put("message", e.getMessage());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public class BusinessException extends Exception {
|
|
147
|
+
private String message;
|
|
148
|
+
public BusinessException(String message) { super(message); this.message = message; }
|
|
149
|
+
public String getMessage() { return this.message; }
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**`before_create` é para enriquecimento apenas:** normalizar strings, preencher defaults, adicionar metadados.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Exemplos Anotados
|
|
158
|
+
|
|
159
|
+
### Exemplo 1 — `action | before_win`: Atualizar coleção relacionada
|
|
160
|
+
|
|
161
|
+
Padrão: ler atributos da ActionLog, atualizar um documento em coleção customizada.
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"name": "Update Task Status on Action Win",
|
|
166
|
+
"entity": "action",
|
|
167
|
+
"event": "before_win",
|
|
168
|
+
"active": true,
|
|
169
|
+
"script": "void trigger(event, entity, player, database) {\n if (!entity.actionId.equals(\"complete_task\")) return;\n\n String taskId = String.valueOf(entity.attributes.get(\"taskId\"));\n if (taskId == null || taskId.equals(\"null\")) return;\n\n HashMap<String, Object> task = manager.getJongoConnection()\n .getCollection(\"task__c\")\n .findOne(\"{_id:#}\", taskId).as(HashMap.class);\n\n if (task != null) {\n task.put(\"status\", \"done\");\n task.put(\"completedAt\", new Date());\n manager.getJongoConnection().getCollection(\"task__c\").save(task);\n entity.attributes.put(\"status\", \"processed\");\n println(\"task updated: \" + JsonUtil.toJson(task));\n }\n}\n"
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```groovy
|
|
174
|
+
void trigger(event, entity, player, database) {
|
|
175
|
+
if (!entity.actionId.equals("complete_task")) return;
|
|
176
|
+
|
|
177
|
+
String taskId = String.valueOf(entity.attributes.get("taskId"));
|
|
178
|
+
if (taskId == null || taskId.equals("null")) return;
|
|
179
|
+
|
|
180
|
+
HashMap<String, Object> task = manager.getJongoConnection()
|
|
181
|
+
.getCollection("task__c")
|
|
182
|
+
.findOne("{_id:#}", taskId).as(HashMap.class);
|
|
183
|
+
|
|
184
|
+
if (task != null) {
|
|
185
|
+
task.put("status", "done");
|
|
186
|
+
task.put("completedAt", new Date());
|
|
187
|
+
manager.getJongoConnection().getCollection("task__c").save(task);
|
|
188
|
+
entity.attributes.put("status", "processed");
|
|
189
|
+
println("task updated: " + JsonUtil.toJson(task));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### Exemplo 2 — `achievement | before_create`: Enriquecer com origem
|
|
197
|
+
|
|
198
|
+
Padrão: ler `context.extra` para saber qual action log ou achievement gerou este registro.
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"name": "Tag Achievement with Origin",
|
|
203
|
+
"entity": "achievement",
|
|
204
|
+
"event": "before_create",
|
|
205
|
+
"active": true,
|
|
206
|
+
"script": "void trigger(event, entity, player, database) {\n if (entity.type != Achievement.TYPE_POINT) return;\n\n if (entity.extra == null) entity.extra = new HashMap<>();\n if (context.extra == null) return;\n\n if (context.extra.parentAchievement != null &&\n context.extra.parentAchievement.type == Achievement.TYPE_CHALLENGE) {\n entity.extra.put(\"originChallenge\", context.extra.parentAchievement.id);\n }\n\n if (context.extra.parentActionLog != null) {\n entity.extra.put(\"originAction\", context.extra.parentActionLog.id);\n entity.extra.put(\"originActionId\", context.extra.parentActionLog.actionId);\n }\n\n println(\"entity: \" + JsonUtil.toJson(entity));\n}\n"
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```groovy
|
|
211
|
+
void trigger(event, entity, player, database) {
|
|
212
|
+
if (entity.type != Achievement.TYPE_POINT) return;
|
|
213
|
+
|
|
214
|
+
if (entity.extra == null) entity.extra = new HashMap<>();
|
|
215
|
+
if (context.extra == null) return;
|
|
216
|
+
|
|
217
|
+
if (context.extra.parentAchievement != null &&
|
|
218
|
+
context.extra.parentAchievement.type == Achievement.TYPE_CHALLENGE) {
|
|
219
|
+
entity.extra.put("originChallenge", context.extra.parentAchievement.id);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (context.extra.parentActionLog != null) {
|
|
223
|
+
entity.extra.put("originAction", context.extra.parentActionLog.id);
|
|
224
|
+
entity.extra.put("originActionId", context.extra.parentActionLog.actionId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
println("entity: " + JsonUtil.toJson(entity));
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### Exemplo 3 — `action | after_win`: Criar Achievement programaticamente
|
|
234
|
+
|
|
235
|
+
Padrão: creditar pontos para outro jogador como efeito colateral de uma ação.
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"name": "Credit Referrer Points on Invite Accept",
|
|
240
|
+
"entity": "action",
|
|
241
|
+
"event": "after_win",
|
|
242
|
+
"active": true,
|
|
243
|
+
"script": "void trigger(event, entity, player, database) {\n if (!entity.actionId.equals(\"accept_invite\")) return;\n\n String referrerId = String.valueOf(entity.attributes.get(\"referrer\"));\n if (referrerId == null || referrerId.equals(\"null\")) return;\n\n Achievement credit = new Achievement();\n credit.id = Guid.newShortGuid();\n credit.type = Achievement.TYPE_POINT;\n credit.player = referrerId;\n credit.item = \"xp\";\n credit.total = 50;\n credit.time = new Date();\n credit.extra = new HashMap();\n credit.extra.put(\"origin\", entity.id);\n credit.extra.put(\"originAction\", entity.actionId);\n\n manager.getAchievementManager().addAchievement(credit);\n println(\"credited: \" + JsonUtil.toJson(credit));\n}\n"
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
```groovy
|
|
248
|
+
void trigger(event, entity, player, database) {
|
|
249
|
+
if (!entity.actionId.equals("accept_invite")) return;
|
|
250
|
+
|
|
251
|
+
String referrerId = String.valueOf(entity.attributes.get("referrer"));
|
|
252
|
+
if (referrerId == null || referrerId.equals("null")) return;
|
|
253
|
+
|
|
254
|
+
Achievement credit = new Achievement();
|
|
255
|
+
credit.id = Guid.newShortGuid();
|
|
256
|
+
credit.type = Achievement.TYPE_POINT;
|
|
257
|
+
credit.player = referrerId;
|
|
258
|
+
credit.item = "xp";
|
|
259
|
+
credit.total = 50;
|
|
260
|
+
credit.time = new Date();
|
|
261
|
+
credit.extra = new HashMap();
|
|
262
|
+
credit.extra.put("origin", entity.id);
|
|
263
|
+
credit.extra.put("originAction", entity.actionId);
|
|
264
|
+
|
|
265
|
+
manager.getAchievementManager().addAchievement(credit);
|
|
266
|
+
println("credited: " + JsonUtil.toJson(credit));
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### Exemplo 4 — `player | after_create`: Inicializar dados do jogador
|
|
273
|
+
|
|
274
|
+
Padrão: ao criar um jogador, buscar dados relacionados em coleção customizada e preencher `extra`.
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{
|
|
278
|
+
"name": "Initialize Player from Employee Data",
|
|
279
|
+
"entity": "player",
|
|
280
|
+
"event": "after_create",
|
|
281
|
+
"active": true,
|
|
282
|
+
"script": "void trigger(event, entity, player, database) {\n println(\"new player: \" + JsonUtil.toJson(entity));\n\n HashMap<String, Object> employee = manager.getJongoConnection()\n .getCollection(\"employee__c\")\n .findOne(\"{email:#}\", entity.email).as(HashMap.class);\n\n if (employee == null) return;\n\n Player p = manager.getPlayerManager().findById(entity._id);\n if (p == null) return;\n\n if (p.extra == null) p.extra = new HashMap();\n p.extra.put(\"department\", employee.get(\"department\"));\n p.extra.put(\"role\", employee.get(\"role\"));\n p.extra.put(\"employeeId\", employee.get(\"_id\"));\n\n manager.getPlayerManager().insert(p);\n println(\"player initialized: \" + p._id);\n}\n"
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```groovy
|
|
287
|
+
void trigger(event, entity, player, database) {
|
|
288
|
+
println("new player: " + JsonUtil.toJson(entity));
|
|
289
|
+
|
|
290
|
+
HashMap<String, Object> employee = manager.getJongoConnection()
|
|
291
|
+
.getCollection("employee__c")
|
|
292
|
+
.findOne("{email:#}", entity.email).as(HashMap.class);
|
|
293
|
+
|
|
294
|
+
if (employee == null) return;
|
|
295
|
+
|
|
296
|
+
Player p = manager.getPlayerManager().findById(entity._id);
|
|
297
|
+
if (p == null) return;
|
|
298
|
+
|
|
299
|
+
if (p.extra == null) p.extra = new HashMap();
|
|
300
|
+
p.extra.put("department", employee.get("department"));
|
|
301
|
+
p.extra.put("role", employee.get("role"));
|
|
302
|
+
p.extra.put("employeeId", employee.get("_id"));
|
|
303
|
+
|
|
304
|
+
manager.getPlayerManager().insert(p);
|
|
305
|
+
println("player initialized: " + p._id);
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### Exemplo 5a — `custom__c | before_create`: Normalizar campos antes de persistir
|
|
312
|
+
|
|
313
|
+
Padrão: `before_create` **não rejeita** — apenas enriquece/normaliza o `entity` antes de salvar.
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"name": "Normalize Employee Fields",
|
|
318
|
+
"entity": "employee__c",
|
|
319
|
+
"event": "before_create",
|
|
320
|
+
"active": true,
|
|
321
|
+
"script": "void trigger(event, entity, player, database) {\n entity.put(\"email\", normalizeEmail(entity.get(\"email\")));\n entity.put(\"name\", normalizeName(entity.get(\"name\")));\n entity.put(\"employeeId\", extractNumeric(entity.get(\"employeeId\")));\n}\n\nString normalizeEmail(Object value) {\n if (value == null) return \"\";\n String s = String.valueOf(value).replaceAll(\"\\\\s\", \"\");\n return s.equals(\"null\") ? \"\" : s.toLowerCase();\n}\n\nString normalizeName(Object value) {\n if (value == null) return \"\";\n String s = String.valueOf(value).trim();\n return s.equals(\"null\") ? \"\" : s;\n}\n\nString extractNumeric(Object value) {\n if (value == null) return \"\";\n try {\n return String.valueOf(Long.parseLong(String.valueOf(value).replaceAll(\"\\\\s\", \"\")));\n } catch (NumberFormatException e) {\n return \"\";\n }\n}\n"
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
```groovy
|
|
326
|
+
void trigger(event, entity, player, database) {
|
|
327
|
+
entity.put("email", normalizeEmail(entity.get("email")));
|
|
328
|
+
entity.put("name", normalizeName(entity.get("name")));
|
|
329
|
+
entity.put("employeeId", extractNumeric(entity.get("employeeId")));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
String normalizeEmail(Object value) {
|
|
333
|
+
if (value == null) return "";
|
|
334
|
+
String s = String.valueOf(value).replaceAll("\\s", "");
|
|
335
|
+
return s.equals("null") ? "" : s.toLowerCase();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
String normalizeName(Object value) {
|
|
339
|
+
if (value == null) return "";
|
|
340
|
+
String s = String.valueOf(value).trim();
|
|
341
|
+
return s.equals("null") ? "" : s;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
String extractNumeric(Object value) {
|
|
345
|
+
if (value == null) return "";
|
|
346
|
+
try {
|
|
347
|
+
return String.valueOf(Long.parseLong(String.valueOf(value).replaceAll("\\s", "")));
|
|
348
|
+
} catch (NumberFormatException e) {
|
|
349
|
+
return "";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
### Exemplo 5b — `custom__c | after_create`: Validar e rejeitar criação
|
|
357
|
+
|
|
358
|
+
Padrão: document já foi salvo — se inválido, deletar e retornar erro no `entity`.
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"name": "Validate Order on Create",
|
|
363
|
+
"entity": "order__c",
|
|
364
|
+
"event": "after_create",
|
|
365
|
+
"active": true,
|
|
366
|
+
"script": "void trigger(event, entity, player, database) {\n try {\n if (player == null || player.isEmpty()) {\n throw new BusinessException(\"Player obrigatório.\");\n }\n\n HashMap<String, Object> schedule = manager.getJongoConnection()\n .getCollection(\"schedule__c\")\n .findOne(\"{status:#}\", \"open\").as(HashMap.class);\n\n if (schedule == null) {\n throw new BusinessException(\"Nenhuma janela de cadastro aberta.\");\n }\n\n long existing = manager.getJongoConnection().getCollection(\"order__c\")\n .count('{\"_id\":{\"$ne\":#},\"owner\":#,\"status\":{\"$ne\":\"cancelled\"}}',\n entity._id, player);\n\n if (existing >= 3) {\n throw new BusinessException(\"Limite de cadastros atingido nesta janela.\");\n }\n\n } catch (BusinessException e) {\n database.delete(entity._id, \"order__c\");\n entity.clear();\n entity.put(\"message\", e.getMessage());\n println(\"rejected: \" + e.getMessage());\n return;\n }\n}\n\npublic class BusinessException extends Exception {\n private String message;\n public BusinessException(String msg) { super(msg); this.message = msg; }\n public String getMessage() { return this.message; }\n}\n"
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```groovy
|
|
371
|
+
void trigger(event, entity, player, database) {
|
|
372
|
+
try {
|
|
373
|
+
if (player == null || player.isEmpty()) {
|
|
374
|
+
throw new BusinessException("Player obrigatório.");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
HashMap<String, Object> schedule = manager.getJongoConnection()
|
|
378
|
+
.getCollection("schedule__c")
|
|
379
|
+
.findOne("{status:#}", "open").as(HashMap.class);
|
|
380
|
+
|
|
381
|
+
if (schedule == null) {
|
|
382
|
+
throw new BusinessException("Nenhuma janela de cadastro aberta.");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
long existing = manager.getJongoConnection().getCollection("order__c")
|
|
386
|
+
.count('{"_id":{"$ne":#},"owner":#,"status":{"$ne":"cancelled"}}',
|
|
387
|
+
entity._id, player);
|
|
388
|
+
|
|
389
|
+
if (existing >= 3) {
|
|
390
|
+
throw new BusinessException("Limite de cadastros atingido nesta janela.");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
} catch (BusinessException e) {
|
|
394
|
+
database.delete(entity._id, "order__c");
|
|
395
|
+
entity.clear();
|
|
396
|
+
entity.put("message", e.getMessage());
|
|
397
|
+
println("rejected: " + e.getMessage());
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
public class BusinessException extends Exception {
|
|
403
|
+
private String message;
|
|
404
|
+
public BusinessException(String msg) { super(msg); this.message = msg; }
|
|
405
|
+
public String getMessage() { return this.message; }
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### Exemplo 6 — `custom__c | after_bulk`: Processar import em lote
|
|
412
|
+
|
|
413
|
+
Padrão: `entity` é uma **List** — itere e processe cada item.
|
|
414
|
+
|
|
415
|
+
```json
|
|
416
|
+
{
|
|
417
|
+
"name": "Process Imported Records",
|
|
418
|
+
"entity": "import__c",
|
|
419
|
+
"event": "after_bulk",
|
|
420
|
+
"active": true,
|
|
421
|
+
"script": "void trigger(event, entity, player, database) {\n if (entity == null || entity.isEmpty()) return;\n\n println(\"bulk size: \" + entity.size());\n\n for (Object raw : entity) {\n HashMap<String, Object> item = (HashMap<String, Object>) raw;\n if (item == null) continue;\n\n String employeeId = String.valueOf(item.get(\"employeeId\"));\n if (employeeId == null || employeeId.equals(\"null\")) continue;\n\n HashMap<String, Object> employee = manager.getJongoConnection()\n .getCollection(\"employee__c\")\n .findOne(\"{_id:#}\", employeeId).as(HashMap.class);\n\n if (employee != null) {\n item.put(\"department\", employee.get(\"department\"));\n item.put(\"processed\", true);\n manager.getJongoConnection().getCollection(\"import__c\").save(item);\n }\n }\n\n println(\"bulk processed: \" + entity.size() + \" records\");\n}\n"
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```groovy
|
|
426
|
+
void trigger(event, entity, player, database) {
|
|
427
|
+
if (entity == null || entity.isEmpty()) return;
|
|
428
|
+
|
|
429
|
+
println("bulk size: " + entity.size());
|
|
430
|
+
|
|
431
|
+
for (Object raw : entity) {
|
|
432
|
+
HashMap<String, Object> item = (HashMap<String, Object>) raw;
|
|
433
|
+
if (item == null) continue;
|
|
434
|
+
|
|
435
|
+
String employeeId = String.valueOf(item.get("employeeId"));
|
|
436
|
+
if (employeeId == null || employeeId.equals("null")) continue;
|
|
437
|
+
|
|
438
|
+
HashMap<String, Object> employee = manager.getJongoConnection()
|
|
439
|
+
.getCollection("employee__c")
|
|
440
|
+
.findOne("{_id:#}", employeeId).as(HashMap.class);
|
|
441
|
+
|
|
442
|
+
if (employee != null) {
|
|
443
|
+
item.put("department", employee.get("department"));
|
|
444
|
+
item.put("processed", true);
|
|
445
|
+
manager.getJongoConnection().getCollection("import__c").save(item);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
println("bulk processed: " + entity.size() + " records");
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
### Exemplo 7 — Registrar uma ação programaticamente (`ActionManager.track`)
|
|
456
|
+
|
|
457
|
+
Padrão: criar e disparar um ActionLog de dentro de um trigger, acionando as regras de gamificação normalmente.
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"name": "Track Secondary Action on Win",
|
|
462
|
+
"entity": "action",
|
|
463
|
+
"event": "after_win",
|
|
464
|
+
"active": true,
|
|
465
|
+
"script": "void trigger(event, entity, player, database) {\n if (!entity.actionId.equals(\"complete_task\")) return;\n\n ActionLog log = new ActionLog();\n log.id = Guid.newShortGuid();\n log.userId = entity.userId;\n log.actionId = \"task_completed_by_team\";\n log.time = new Date();\n log.attributes = new HashMap();\n log.attributes.put(\"taskId\", entity.attributes.get(\"taskId\"));\n log.attributes.put(\"origin\", entity.id);\n manager.getActionManager().track(log);\n println(\"tracked async: \" + JsonUtil.toJson(log));\n}\n"
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
```groovy
|
|
470
|
+
void trigger(event, entity, player, database) {
|
|
471
|
+
if (!entity.actionId.equals("complete_task")) return;
|
|
472
|
+
|
|
473
|
+
// Async: dispara e não bloqueia — conquistas processadas em background
|
|
474
|
+
ActionLog log = new ActionLog();
|
|
475
|
+
log.id = Guid.newShortGuid(); // .id, não ._id
|
|
476
|
+
log.userId = entity.userId;
|
|
477
|
+
log.actionId = "task_completed_by_team";
|
|
478
|
+
log.time = new Date();
|
|
479
|
+
log.attributes = new HashMap();
|
|
480
|
+
log.attributes.put("taskId", entity.attributes.get("taskId"));
|
|
481
|
+
log.attributes.put("origin", entity.id);
|
|
482
|
+
manager.getActionManager().track(log);
|
|
483
|
+
println("tracked async: " + JsonUtil.toJson(log));
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Variante síncrona — use quando precisar saber quais conquistas foram geradas:
|
|
488
|
+
|
|
489
|
+
```groovy
|
|
490
|
+
void trigger(event, entity, player, database) {
|
|
491
|
+
// Add action filter: if (!entity.actionId.equals("your_action")) return;
|
|
492
|
+
|
|
493
|
+
ActionLog log = new ActionLog();
|
|
494
|
+
log.id = Guid.newShortGuid();
|
|
495
|
+
log.userId = entity.userId;
|
|
496
|
+
log.actionId = "referral_bonus";
|
|
497
|
+
log.time = new Date();
|
|
498
|
+
log.attributes = new HashMap();
|
|
499
|
+
|
|
500
|
+
// trackSynchonous bloqueia e retorna as conquistas geradas
|
|
501
|
+
List<Achievement> earned = manager.getActionManager().trackSynchonous(log);
|
|
502
|
+
if (earned != null) {
|
|
503
|
+
println("earned: " + earned.size() + " achievements");
|
|
504
|
+
for (Achievement a : earned) {
|
|
505
|
+
println("achievement: type=" + a.type + " item=" + a.item + " total=" + a.total);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
> ⚠ Nunca chame `track` ou `trackSynchonous` dentro de um trigger `before_win` / `after_win` para a **mesma entidade/ação** que disparou o trigger — causa loop infinito. Use uma action diferente ou verifique com `entity.actionId` antes.
|
|
@@ -109,7 +109,7 @@ manager.getPlayerManager().delete("john");
|
|
|
109
109
|
```java
|
|
110
110
|
Action a = manager.getActionManager().findActionById("sell");
|
|
111
111
|
manager.getActionManager().track(actionLog);
|
|
112
|
-
List<Achievement> results = manager.getActionManager().
|
|
112
|
+
List<Achievement> results = manager.getActionManager().trackSynchonous(actionLog);
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
### CatalogManager
|