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,34 +1,333 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `action`
|
|
2
2
|
|
|
3
|
-
**Acesso Studio:** `/studio/action`
|
|
3
|
+
**Acesso Studio:** `/studio/action` _(convenção de UI; não verificável no `funifier-service`)_
|
|
4
4
|
**API Endpoint:** `/v3/action`
|
|
5
|
+
**Endpoint Legado:** `/2.0.0/action` (`com.funifier.rest.engine.ActionRest`)
|
|
6
|
+
**Endpoint System:** `/v3/system/action` (gamificação de sistema)
|
|
7
|
+
**Coleção MongoDB:** `action`
|
|
5
8
|
|
|
6
|
-
|
|
9
|
+
> Documento de engenharia reversa baseado **exclusivamente** no código de `funifier-service` (commit `830037e`). Cobre o **cadastro/registro de ações** (entidade `Action`, coleção `action`). O registro de execuções (firing) — entidade `ActionLog`, coleção `action_log`, endpoints `/v3/action/log*` — está documentado em `action-log.md`. Os dois compartilham o mesmo controller (`ActionRest`).
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
---
|
|
9
12
|
|
|
10
|
-
##
|
|
13
|
+
## 1. Visão Geral
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
- Para definir os comportamentos que serão rastreados
|
|
14
|
-
- Para criar a base de eventos que disparam challenges e recompensas
|
|
15
|
+
O módulo `action` é o **catálogo de tipos de ação** que os jogadores podem executar na gamificação. Cada documento da coleção `action` é a *definição* de um comportamento rastreável (ex: `sell`, `watch_video`, `checkin`), não a ocorrência dele. A ocorrência é registrada como `action_log` (vide `action-log.md`).
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Papel arquitetural:
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
- **Vocabulário de eventos da gamificação.** Nenhum `action_log` pode ser registrado para um `actionId` que não exista e esteja `active` (validado em `ActionRest.insertLog`, linhas 415-421).
|
|
20
|
+
- **Esquema de atributos.** O campo `attributes` declara quais chaves o `action_log.attributes` pode carregar e o *tipo* esperado de cada uma. O tipo é consultado por consumidores (challenge, leaderboard, crowning, csv) para decidir coerção de valor (`ActionAttribute.TYPE_NUMBER`/`TYPE_BOOLEAN`).
|
|
21
|
+
- **Configuração de comportamento no disparo.** Uma `Action` pode carregar `points`, `notifications` e `limit`. Esses campos não fazem nada no momento do cadastro — são lidos quando um `action_log` correspondente é processado (`AchievementManager.fireAction`).
|
|
22
|
+
- **Base para challenges e triggers.** `ChallengeRule.actionId` e `trigger_html.actions` referenciam `action._id`. Por isso a exclusão de uma action tem efeito cascata (seção 6).
|
|
23
|
+
|
|
24
|
+
Problemas que resolve:
|
|
25
|
+
|
|
26
|
+
- Define o que pode ser rastreado e com qual contexto (atributos).
|
|
27
|
+
- Permite recompensa direta por ação (pontos por ação, sem precisar de challenge).
|
|
28
|
+
- Permite limitar a frequência de registro de uma ação (anti-fraude / anti-spam) via `limit`.
|
|
29
|
+
|
|
30
|
+
Relação com outros módulos:
|
|
31
|
+
|
|
32
|
+
| Módulo | Relação |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `action-log` | Toda inserção de log valida a existência e `active` da action; resolve `actionId` por `_id` **ou** nome. |
|
|
35
|
+
| `achievement` | `AchievementManager.fireAction` lê `action.points` (linha 152) e `action.notifications` (linha 196) ao processar um log. |
|
|
36
|
+
| `challenge` | `ChallengeRule.actionId` aponta para `action._id`. Excluir a action **remove a rule** do challenge. |
|
|
37
|
+
| `trigger` (`trigger_html`) | `trigger_html.actions` referencia actions; excluir a action a remove dessa lista. |
|
|
38
|
+
| `point_category` | `RewardPoint.category` referencia uma categoria de pontos. |
|
|
39
|
+
| `player` | Limites `per: "player"` contam `action_log` por `userId`. |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 2. Arquitetura e Fluxos
|
|
44
|
+
|
|
45
|
+
### 2.1 Classes envolvidas
|
|
46
|
+
|
|
47
|
+
| Classe | Papel |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `com.funifier.engine.action.Action` | Entidade/POJO raiz — documento da coleção `action`. |
|
|
50
|
+
| `com.funifier.engine.action.ActionAttribute` | Subentidade — definição de atributo (`name`, `type`, `get_value_script`). |
|
|
51
|
+
| `com.funifier.engine.action.Limit` | Subentidade — configuração de limite de frequência. |
|
|
52
|
+
| `com.funifier.engine.action.LinkedAction` | Subentidade **legada/morta** — sem consumidores (seção 7). |
|
|
53
|
+
| `com.funifier.engine.action.ActionDaoMongo` | Repositório Jongo. Coleção `action`. Faz sanitização e cascata de exclusão. |
|
|
54
|
+
| `com.funifier.engine.action.ActionManager` | Manager. Delega CRUD ao DAO; calcula disponibilidade; orquestra o tracking de logs. |
|
|
55
|
+
| `com.funifier.engine.action.ActionAsyncProcessor` | Thread por gamificação que consome a fila de `action_log` (firing assíncrono). |
|
|
56
|
+
| `com.funifier.rest.v3.rest.ActionRest` | Controller REST v3 (`/v3/action` + `/v3/action/log*` + `/queue/*`). |
|
|
57
|
+
| `com.funifier.engine.challenge.RewardPoint` | Subentidade de `points` (recompensa direta por ação). |
|
|
58
|
+
| `com.funifier.engine.notify.NotificationDefinition` | Subentidade de `notifications`. |
|
|
59
|
+
| `com.funifier.engine.image.Image` | Subentidade de `image` (`small`/`medium`/`original`). |
|
|
60
|
+
|
|
61
|
+
Não há validação dedicada de schema; `Action` usa `@JsonIgnoreProperties(ignoreUnknown=true)` — **campos desconhecidos são silenciosamente descartados na desserialização**.
|
|
62
|
+
|
|
63
|
+
### 2.2 Pipeline de criação/atualização — `POST /v3/action`
|
|
64
|
+
|
|
65
|
+
Sequência real (`ActionRest.insert` → `ActionManager.addAction` → `ActionDaoMongo.addAction`):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
[1] ActionRest.insert(action) → ManagerFactory.getActionManager().addAction(action)
|
|
69
|
+
[2] ActionManager.addAction(action) → mongo.addAction(action, jongo)
|
|
70
|
+
[3] ActionDaoMongo.addAction:
|
|
71
|
+
se action.id == null:
|
|
72
|
+
action.id = Guid.newShortGuid() // gera ID curto
|
|
73
|
+
senão:
|
|
74
|
+
action.id = Normalizer.normalize(id, NFD)
|
|
75
|
+
.replaceAll("[^\\p{ASCII}]", "") // remove acentos/não-ASCII
|
|
76
|
+
.trim()
|
|
77
|
+
.replaceAll(" ", "_") // espaços viram _
|
|
78
|
+
.replaceAll("[^a-zA-Z0-9_]", "") // remove o resto
|
|
79
|
+
para cada attribute:
|
|
80
|
+
att.name = Normalizer.normalize(att.name, NFD)
|
|
81
|
+
.replaceAll("[^\\p{ASCII}]", "")
|
|
82
|
+
.trim()
|
|
83
|
+
.replaceAll(" ", "_")
|
|
84
|
+
.replaceAll("[^a-zA-Z0-9_.]", "") // nomes de attribute também permitem '.'
|
|
85
|
+
c.save(action) // UPSERT por _id (Jongo)
|
|
86
|
+
[4] Retorna 201 CREATED com a action (JsonUtil.toJsonRemoveNullFields)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Síncrono.** Sem transação. `c.save` é **upsert por `_id`**: reenviar a mesma action substitui o documento inteiro (full replace, não patch).
|
|
90
|
+
|
|
91
|
+
#### Fluxo de criação/atualização — `POST /v3/action`
|
|
92
|
+
|
|
93
|
+
```mermaid
|
|
94
|
+
flowchart LR
|
|
95
|
+
A[POST /v3/action] --> B{_id informado?}
|
|
96
|
+
B -- não --> C[gera Guid.newShortGuid]
|
|
97
|
+
B -- sim --> D["sanitiza _id<br/>NFD, strip não-ASCII,<br/>espaços→_, strip ^a-zA-Z0-9_"]
|
|
98
|
+
C --> E["sanitiza nomes de attributes<br/>(permite . além de a-zA-Z0-9_)"]
|
|
99
|
+
D --> E
|
|
100
|
+
E --> F["c.save(action)<br/>UPSERT por _id"]
|
|
101
|
+
F --> G[201 CREATED]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
> **Não existe endpoint PUT.** `ActionManager.updateAction` / `ActionDaoMongo.updateAction` existem mas **não são acessíveis pelo REST** — toda escrita passa por `addAction` (upsert).
|
|
105
|
+
|
|
106
|
+
### 2.3 Pipeline de exclusão — `DELETE /v3/action/:id` (cascata)
|
|
107
|
+
|
|
108
|
+
`ActionRest.delete` → `ActionManager.deleteAction` → `ActionDaoMongo.deleteAction`:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
[1] updateChallenges(id):
|
|
112
|
+
para CADA challenge da coleção `challenge`:
|
|
113
|
+
remove toda ChallengeRule cujo actionId == id
|
|
114
|
+
se removeu alguma rule → cc.remove(_id) + cc.save(challenge) // re-salva o challenge alterado
|
|
115
|
+
[2] updateActionConfigs(id):
|
|
116
|
+
para CADA trigger_html da coleção `trigger_html`:
|
|
117
|
+
remove a action da lista `actions` onde action.id == id
|
|
118
|
+
se removeu → re-salva o trigger_html
|
|
119
|
+
[3] c.remove("{_id:#}", id) // remove o documento da action
|
|
120
|
+
[4] Retorna 204 NO CONTENT
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Esse comportamento é **silencioso e cascateado** — vide seção 6. Não há log nem aviso; um challenge pode ficar com **zero rules** após a exclusão da action.
|
|
124
|
+
|
|
125
|
+
### 2.4 Como a configuração da action influencia o disparo (firing)
|
|
126
|
+
|
|
127
|
+
Os campos `points`, `notifications`, `limit` e `active` só produzem efeito quando um `action_log` correspondente é processado. Onde cada um é lido:
|
|
128
|
+
|
|
129
|
+
| Campo | Lido em | Efeito |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `active` | `ActionRest.insertLog:419` | Se `false`, o log é rejeitado (`"action ... is not active"`). |
|
|
132
|
+
| `limit` | `ActionRest.insertLog:494` / `ActionManager.evaluateLimit` | Bloqueia o registro se exceder a frequência. |
|
|
133
|
+
| `points` | `AchievementManager.fireAction:152` | Gera `Achievement` TYPE_POINT para o jogador (recompensa direta). |
|
|
134
|
+
| `notifications` | `AchievementManager.fireAction:196` | Envia notificações de `EVENT_WIN` (scope ≠ custom). |
|
|
135
|
+
| `attributes[].type` | `AchievementManager:2054`, `LeaderBoardManager:127`, `CsvManager:186`, `CrowningManager` | Coerção de valor (Number/Boolean) na agregação dos logs. |
|
|
136
|
+
|
|
137
|
+
```mermaid
|
|
138
|
+
sequenceDiagram
|
|
139
|
+
participant C as Cliente
|
|
140
|
+
participant R as ActionRest
|
|
141
|
+
participant AM as ActionManager
|
|
142
|
+
participant Ach as AchievementManager
|
|
143
|
+
C->>R: POST /v3/action/log {actionId, userId, attributes}
|
|
144
|
+
R->>AM: findActionByIdOrName(actionId)
|
|
145
|
+
AM-->>R: Action (erro 400 se null ou active=false)
|
|
146
|
+
R->>R: resolve player + checa Limit
|
|
147
|
+
alt async=false (síncrono)
|
|
148
|
+
R->>AM: trackSynchonous(log)
|
|
149
|
+
AM->>AM: triggerManager.execute(BEFORE_WIN) + addLog
|
|
150
|
+
AM->>Ach: fireAction(log)
|
|
151
|
+
Ach->>Ach: lê action.points → Achievement TYPE_POINT (só para player)
|
|
152
|
+
Ach->>Ach: lê action.notifications (EVENT_WIN) → envia
|
|
153
|
+
else async=true (default)
|
|
154
|
+
R->>AM: track(log) → enfileira (ActionAsyncProcessor)
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Importante:** recompensa por `points` e `notifications` da action só ocorrem quando o `principal` é **jogador** (`principal.isPlayer()`); para times, esta seção do `fireAction` é ignorada.
|
|
159
|
+
|
|
160
|
+
### 2.5 Listagem — `GET /v3/action`
|
|
161
|
+
|
|
162
|
+
`ActionManager.findAllActions(id, action, q, fields, orderby, reverse, max_results)` monta um pipeline de aggregation sobre a coleção `action`:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
$match { _id: {$exists:true}
|
|
166
|
+
[, _id: <id>]
|
|
167
|
+
[, <q literal — concatenado cru>]
|
|
168
|
+
[, action: {$regex: <action>, $options:'i'}] }
|
|
169
|
+
[$project { <fields separados por vírgula>: 1 }]
|
|
170
|
+
[$sort { <orderby>: 1|-1 }]
|
|
171
|
+
$limit <max_results> // default 100 quando max_results <= 0
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`q`, `orderby` e `fields` são **concatenados literalmente** na string do pipeline (superfície de injeção — seção 8). A resposta é um array JSON via `Callback.callback` (**não paginado**; o header `Range` não se aplica a `/v3/action`, apenas aos endpoints `/log*`).
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 3. Estrutura dos Objetos
|
|
179
|
+
|
|
180
|
+
### 3.1 `Action` — documento raiz (coleção `action`)
|
|
181
|
+
|
|
182
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
183
|
+
|---|---|---|---|---|
|
|
184
|
+
| `_id` | String | auto (`shortGuid`) ou sanitizado | — | ID da action. Se omitido, gerado; se informado, **sanitizado** (NFD, sem acentos, espaços→`_`, só `a-zA-Z0-9_`). |
|
|
185
|
+
| `action` | String | — | recomendado | Nome amigável (ex: `"Sell"`). Pode ser usado para resolver a action no registro de log (`findActionByIdOrName`). |
|
|
186
|
+
| `attributes` | `ActionAttribute[]` | — | não | Definição dos atributos aceitos no `action_log`. Vide 3.2. |
|
|
187
|
+
| `points` | `RewardPoint[]` | — | não | Pontos concedidos **diretamente** quando a action é disparada (jogadores). Vide 3.4. |
|
|
188
|
+
| `notifications` | `NotificationDefinition[]` | — | não | Notificações disparadas no `EVENT_WIN` ao disparar a action. Vide 3.5. |
|
|
189
|
+
| `image` | `Image` | — | não | Imagem (`small`/`medium`/`original`). Vide 3.6. |
|
|
190
|
+
| `limit` | `Limit` | — | não | Limite de frequência de registro de logs. Vide 3.3. |
|
|
191
|
+
| `active` | boolean | `true` | não | Se `false`, logs para esta action são rejeitados. |
|
|
192
|
+
| `extra` | `Map<String,Object>` | `{}` | não | Metadado livre. Persistido, **não interpretado** pelo firing. |
|
|
193
|
+
| `available` | Boolean | — | — | **Campo calculado em runtime** (não persistido por design). Vide abaixo. |
|
|
194
|
+
| `linkedActions` | `LinkedAction[]` | — | não | **Legado/morto** — aceito e persistido, **nunca lido**. Vide 3.7 e seção 7. |
|
|
195
|
+
|
|
196
|
+
**Campo computado (não-persistente):**
|
|
197
|
+
|
|
198
|
+
- `available` — preenchido somente em `GET /v3/action?available=true&player=<id>` por `ActionManager.isActionsAvailable` → `evaluateLimit`. `true` quando a action **não tem `limit`** ou o jogador ainda está abaixo do limite; `false` quando excedeu o limite **ou** a expressão de `limit.total` lançou exceção (falha silenciosa). Não é gravado no Mongo a menos que o cliente o envie explicitamente no POST.
|
|
199
|
+
|
|
200
|
+
**Campos silenciosamente sanitizados no save (`ActionDaoMongo.addAction`):**
|
|
201
|
+
|
|
202
|
+
- `_id` informado → normalizado (perde acentos, espaços viram `_`, caracteres especiais removidos). Ex: `"Vender Açaí"` → `Vender_Acai`.
|
|
203
|
+
- `attributes[].name` → mesma normalização (adicionalmente permite `.`).
|
|
204
|
+
|
|
205
|
+
**Campos aceitos mas descartados:**
|
|
206
|
+
|
|
207
|
+
- Qualquer chave não mapeada na classe `Action` → descartada (`@JsonIgnoreProperties(ignoreUnknown=true)`).
|
|
208
|
+
|
|
209
|
+
### 3.2 `ActionAttribute` — subentidade de `attributes`
|
|
210
|
+
|
|
211
|
+
| Campo | Tipo | Obrigatório | Descrição |
|
|
212
|
+
|---|---|---|---|
|
|
213
|
+
| `name` | String | sim | Nome do atributo (sanitizado, permite `.`). |
|
|
214
|
+
| `type` | String | recomendado | `"String"`, `"Number"` ou `"Boolean"`. **Não validado na escrita.** |
|
|
215
|
+
| `get_value_script` | String | não | Exclusivo de trigger HTML — script de localização do valor numa página. |
|
|
216
|
+
|
|
217
|
+
**`type` não é validado no cadastro.** As constantes `ActionAttribute.TYPE_STRING/NUMBER/BOOLEAN` só são consultadas por *consumidores* dos logs para decidir coerção: `Number` → cast numérico; `Boolean` → cast booleano; qualquer outro valor → tratado como string. Logo, um `type` arbitrário (ex: `"Integer"`) é aceito silenciosamente e se comporta como `String`.
|
|
218
|
+
|
|
219
|
+
### 3.3 `Limit` — subentidade de `limit`
|
|
220
|
+
|
|
221
|
+
| Campo | Tipo | Descrição |
|
|
222
|
+
|---|---|---|
|
|
223
|
+
| `total` | Object (Number **ou** String) | Número máximo de registros no período, **ou** fórmula Mustache+exp4j avaliada com `{player, action_log}` (ex: `"{{player.extra.max_actions}}"`). |
|
|
224
|
+
| `per` | String | Escopo da contagem: `"player"`, `"team"` ou `"gamification"`. |
|
|
225
|
+
| `every` | String | Janela de tempo (ex: `"1h"`, `"5h"`, `"1d"`). Se ausente → janela de `-10y` (efetivamente sempre). |
|
|
226
|
+
| `query` | String | Filtro Mongo adicional, Mustache-parseado com `{player, action_log}`, **concatenado cru** na query de contagem. |
|
|
227
|
+
|
|
228
|
+
Constantes: `PER_PLAYER = "player"`, `PER_TEAM = "team"`, `PER_GAME = "gamification"`.
|
|
229
|
+
|
|
230
|
+
> ⚠️ **`per: "team"` é um branch morto.** A verificação de limite (`evaluateLimit`, `trackWithRestrictions`, `ActionRest.insertLog`, `insertLogBulk`) só trata `PER_PLAYER` e `PER_GAME`. Com `per: "team"` nenhum branch executa, `count` permanece `0`, `0 >= total` é falso → **o limite nunca bloqueia**. Vide seção 7.
|
|
231
|
+
|
|
232
|
+
Lógica de contagem (quando `total > 0`):
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
from = (every != null) ? agora - every : agora - 10anos
|
|
236
|
+
PER_PLAYER → db.action_log.count({userId, actionId, time: {$gte: from} <query>})
|
|
237
|
+
PER_GAME → db.action_log.count({actionId, time: {$gte: from} <query>})
|
|
238
|
+
se count >= total → bloqueia
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 3.4 `RewardPoint` — subentidade de `points`
|
|
242
|
+
|
|
243
|
+
| Campo | Tipo | Descrição |
|
|
244
|
+
|---|---|---|
|
|
245
|
+
| `total` | double | Quantidade base de pontos. |
|
|
246
|
+
| `category` | String | ID da categoria de pontos (`point_category`). |
|
|
247
|
+
| `operation` | int | `0` NONE; `1` MULTIPLY_BY_ATTRIBUTE; `2` DIVIDE_BY_ATTRIBUTE. |
|
|
248
|
+
| `value` | String | Nome do atributo do `action_log` usado em multiply/divide. |
|
|
249
|
+
| `perPlayer` | boolean | Presente no schema; **não consultado** no firing de action. |
|
|
250
|
+
|
|
251
|
+
Cálculo em `fireAction` (linhas 152-191), apenas para jogadores e quando `total != 0`:
|
|
19
252
|
|
|
20
|
-
```json
|
|
21
|
-
{
|
|
22
|
-
"_id": "watch_video",
|
|
23
|
-
"action": "Watch Video",
|
|
24
|
-
"active": true,
|
|
25
|
-
"attributes": []
|
|
26
|
-
}
|
|
27
253
|
```
|
|
254
|
+
NONE → totalPoints = total
|
|
255
|
+
MULTIPLY_BY_ATTRIBUTE → totalPoints = total * action_log.attributes[value]
|
|
256
|
+
DIVIDE_BY_ATTRIBUTE → totalPoints = total / action_log.attributes[value]
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
> Se o atributo de `value` não existir ou não for numérico, a exceção é **engolida** e `totalPoints` permanece com o `total` base (a operação degrada silenciosamente para NONE).
|
|
260
|
+
|
|
261
|
+
### 3.5 `NotificationDefinition` — subentidade de `notifications`
|
|
262
|
+
|
|
263
|
+
| Campo | Tipo | Descrição |
|
|
264
|
+
|---|---|---|
|
|
265
|
+
| `event` | int | `0` WIN; `1` CREATE; `2` CHANGE; `3` LOSE; `4` REMOVE. |
|
|
266
|
+
| `type` | int | `0` TEXT; `1` VIDEO. |
|
|
267
|
+
| `scope` | int | `0` PRIVATE; `1` NEWSFEED; `99` CUSTOM. |
|
|
268
|
+
| `content` | String | Conteúdo/mensagem. |
|
|
269
|
+
| `tag` | String | Tag opcional. |
|
|
270
|
+
|
|
271
|
+
No disparo da action, somente `event = EVENT_WIN (0)` com `scope ≠ SCOPE_CUSTOM (99)` é enviado (`getNotificationsByEvent` ignora scope custom).
|
|
272
|
+
|
|
273
|
+
### 3.6 `Image` — subentidade de `image`
|
|
274
|
+
|
|
275
|
+
Três variantes `ImageItem` (`small`, `medium`, `original`), cada uma com `url` (e `size`/`width`/`height`/`depth`, default `0`). Na prática, preencher `url` igual nas três variantes é suficiente.
|
|
276
|
+
|
|
277
|
+
### 3.7 `LinkedAction` — subentidade de `linkedActions` _(legado/morto)_
|
|
278
|
+
|
|
279
|
+
| Campo | Tipo | Descrição |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `action` | String | ID da action filha a ser rastreada. |
|
|
282
|
+
| `playerAttributeName` | String | Nome do atributo da action pai que referencia o jogador da action filha. |
|
|
283
|
+
|
|
284
|
+
**Sem consumidores em `funifier-service`.** A classe e o campo só possuem getters/setters; nenhuma lógica de firing os lê. Aceito e persistido, porém inerte.
|
|
285
|
+
|
|
286
|
+
---
|
|
28
287
|
|
|
29
|
-
|
|
288
|
+
## 4. Endpoints
|
|
30
289
|
|
|
31
|
-
|
|
290
|
+
Todos sob `ActionRest` (`@Path("v3/action")`). Autenticação: Bearer token (`AuthBean`). Abaixo apenas os endpoints do **cadastro de actions**; os endpoints de log (`/log`, `/log/bulk`, `/log/frequency`, `/log/aggregate`, `/log/{id}`, `/log/attribute/.../index/*`) e de fila (`/queue/*`) estão em `action-log.md`.
|
|
291
|
+
|
|
292
|
+
### `GET /v3/action`
|
|
293
|
+
|
|
294
|
+
| Aspecto | Detalhe |
|
|
295
|
+
|---|---|
|
|
296
|
+
| Finalidade | Listar/pesquisar actions. |
|
|
297
|
+
| Paginação | **Não paginado** (`Callback.callback`, array completo). |
|
|
298
|
+
| Resposta | Array de `Action` com campos nulos removidos. |
|
|
299
|
+
|
|
300
|
+
**Query params:**
|
|
301
|
+
|
|
302
|
+
| Param | Tipo | Descrição |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| `id` | String | Filtra por `_id` exato. |
|
|
305
|
+
| `action` | String | Filtra por nome (`$regex`, case-insensitive). |
|
|
306
|
+
| `q` | String | Critério Mongo bruto (concatenado no `$match`). |
|
|
307
|
+
| `fields` | String | Projeção (lista separada por vírgula). |
|
|
308
|
+
| `orderby` | String | Campo de ordenação. |
|
|
309
|
+
| `reverse` | boolean | `true` = descendente. |
|
|
310
|
+
| `max_results` | int | Limite; default `100` quando `<= 0`. |
|
|
311
|
+
| `player` | String | Usado com `available`. |
|
|
312
|
+
| `available` | String | `"true"` + `player` → calcula `Action.available` por `evaluateLimit`. |
|
|
313
|
+
|
|
314
|
+
### `GET /v3/action/:id`
|
|
315
|
+
|
|
316
|
+
| Aspecto | Detalhe |
|
|
317
|
+
|---|---|
|
|
318
|
+
| Finalidade | Buscar uma action por `_id` **exato** (`findActionById`). |
|
|
319
|
+
| Observação | **Não** resolve por nome amigável — diferente do caminho de log. |
|
|
320
|
+
|
|
321
|
+
### `POST /v3/action`
|
|
322
|
+
|
|
323
|
+
| Aspecto | Detalhe |
|
|
324
|
+
|---|---|
|
|
325
|
+
| Finalidade | Criar **ou** atualizar action. |
|
|
326
|
+
| Full replace ou patch | **Full replace** (upsert por `_id` via `c.save`). |
|
|
327
|
+
| Status | `201 CREATED`. |
|
|
328
|
+
| Comportamento real | Gera/sanitiza `_id`; sanitiza `attributes[].name`; descarta campos desconhecidos. Não há erro em `_id` duplicado (upsert). |
|
|
329
|
+
|
|
330
|
+
**Exemplo (action com atributos, pontos e limite):**
|
|
32
331
|
|
|
33
332
|
```json
|
|
34
333
|
{
|
|
@@ -38,56 +337,186 @@ Exemplos típicos: `login`, `acessar_feed`, `assistir_video`, `jogar_mini`, `com
|
|
|
38
337
|
"attributes": [
|
|
39
338
|
{ "name": "product", "type": "String" },
|
|
40
339
|
{ "name": "price", "type": "Number" }
|
|
41
|
-
]
|
|
340
|
+
],
|
|
341
|
+
"points": [
|
|
342
|
+
{ "total": 10, "category": "xp", "operation": 1, "value": "price" }
|
|
343
|
+
],
|
|
344
|
+
"limit": { "total": 5, "per": "player", "every": "1d" }
|
|
42
345
|
}
|
|
43
346
|
```
|
|
44
347
|
|
|
45
|
-
|
|
348
|
+
### `DELETE /v3/action/:id`
|
|
349
|
+
|
|
350
|
+
| Aspecto | Detalhe |
|
|
351
|
+
|---|---|
|
|
352
|
+
| Finalidade | Excluir action. |
|
|
353
|
+
| Status | `204 NO CONTENT`. |
|
|
354
|
+
| Comportamento real | **Cascata silenciosa**: remove a action das rules de **todos** os challenges e da lista `actions` de **todos** os `trigger_html` antes de remover o documento (seções 2.3 e 6). |
|
|
355
|
+
|
|
356
|
+
### Variantes adicionais
|
|
357
|
+
|
|
358
|
+
- **Legado** `/2.0.0/action` (`rest.engine.ActionRest`) — endpoints `GET/DELETE /{id}` e `/log` antigos. Preferir v3.
|
|
359
|
+
- **System** `/v3/system/action` (`rest.v3.rest.system.ActionRest`) — equivalente para a gamificação de sistema/admin.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 5. Regras de Negócio
|
|
364
|
+
|
|
365
|
+
Regras presentes no código que **não** estão no schema:
|
|
366
|
+
|
|
367
|
+
1. **`_id` é sempre saneado.** Não é possível persistir `_id` com acentos, espaços ou símbolos — eles são transformados/removidos no save. O `_id` final pode diferir do enviado.
|
|
368
|
+
2. **POST é upsert.** Reenviar com `_id` existente substitui o documento inteiro (full replace). Campos omitidos são apagados.
|
|
369
|
+
3. **Resolução por nome no firing.** Ao registrar um `action_log`, o `actionId` é resolvido por `_id` **ou** por `action` (nome). O log persistido sempre usa o `_id` canônico (`log.actionId = a.getId()`).
|
|
370
|
+
4. **`active = false` bloqueia logs.** Em `/log` retorna 400; em `/log/bulk` o item é silenciosamente descartado.
|
|
371
|
+
5. **Limite por período.** `limit` restringe quantos logs de uma action podem ser registrados por jogador/gamificação numa janela (`every`). `total` pode ser uma fórmula dinâmica baseada em `player`/`action_log`.
|
|
372
|
+
6. **Pontos diretos.** Uma action com `points` concede pontos ao jogador a cada log processado, independentemente de challenges.
|
|
373
|
+
7. **Multi-tenant.** Cada gamificação (apiKey) tem sua própria coleção `action` e sua própria thread `ActionAsyncProcessor` (`"Funifier-ActionLog-Async-<apiKey>"`).
|
|
374
|
+
8. **Consistência eventual no firing assíncrono.** Por padrão (`async=true`), o log é enfileirado e processado por uma thread; os efeitos (pontos, achievements) não são imediatos na resposta.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 6. Comportamentos Automáticos
|
|
379
|
+
|
|
380
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
381
|
+
|---|---|---|---|
|
|
382
|
+
| Geração de `_id` | POST sem `_id` | `Guid.newShortGuid()` | Sim |
|
|
383
|
+
| Sanitização de `_id` | POST com `_id` | NFD/strip/espaços→`_` | Sim |
|
|
384
|
+
| Sanitização de `attributes[].name` | POST | NFD/strip (permite `.`) | Sim |
|
|
385
|
+
| Upsert | POST com `_id` existente | Substitui documento inteiro | Sim |
|
|
386
|
+
| **Cascata de exclusão em challenges** | DELETE | Remove a action das rules de todos os challenges; re-salva os alterados | Sim (challenge) |
|
|
387
|
+
| **Cascata de exclusão em trigger_html** | DELETE | Remove a action da lista `actions` de todos os `trigger_html` | Sim (trigger_html) |
|
|
388
|
+
| Cálculo de `available` | GET `?available=true&player=` | Preenche `Action.available` via limite | Não |
|
|
389
|
+
| Recompensa por `points` | Log processado (firing) | Cria `Achievement` TYPE_POINT (só player) | Sim (achievement) |
|
|
390
|
+
| Notificações por `notifications` | Log processado (firing) | Envia notificações `EVENT_WIN` | Sim (notification) |
|
|
391
|
+
|
|
392
|
+
#### Fluxo de exclusão em cascata — `DELETE /v3/action/:id`
|
|
393
|
+
|
|
394
|
+
```mermaid
|
|
395
|
+
flowchart TD
|
|
396
|
+
A["DELETE /v3/action/:id"] --> B["updateChallenges(id)"]
|
|
397
|
+
A --> C["updateActionConfigs(id)"]
|
|
398
|
+
B --> B1["varre TODOS os challenges"]
|
|
399
|
+
B1 --> B2{"rule.actionId == id?"}
|
|
400
|
+
B2 -- sim --> B3["remove a rule + re-salva o challenge"]
|
|
401
|
+
B2 -- não --> B4["mantém"]
|
|
402
|
+
C --> C1["varre TODOS os trigger_html"]
|
|
403
|
+
C1 --> C2["remove id de actions + re-salva config"]
|
|
404
|
+
B3 --> D["c.remove(_id) da action"]
|
|
405
|
+
B4 --> D
|
|
406
|
+
C2 --> D
|
|
407
|
+
D --> E[204 No Content]
|
|
408
|
+
```
|
|
46
409
|
|
|
47
|
-
|
|
410
|
+
> ⚠️ A documentação antiga afirmava que excluir uma action "pode causar inconsistências, verifique antes". **Isso está incorreto.** O sistema *auto-limpa* as rules dos challenges e as listas de trigger_html. O efeito colateral real é: um challenge que dependia exclusivamente daquela action fica **sem rules** (e potencialmente nunca mais completa).
|
|
48
411
|
|
|
49
|
-
|
|
412
|
+
---
|
|
50
413
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
414
|
+
## 7. Suportado vs NÃO Suportado
|
|
415
|
+
|
|
416
|
+
### ✅ Suportado
|
|
417
|
+
|
|
418
|
+
- CRUD de actions: `GET /v3/action`, `GET /v3/action/:id`, `POST /v3/action` (upsert), `DELETE /v3/action/:id` (com cascata).
|
|
419
|
+
- Atributos tipados (`String`/`Number`/`Boolean`) consumidos por challenge, leaderboard, crowning, csv.
|
|
420
|
+
- `active` para habilitar/desabilitar o registro de logs.
|
|
421
|
+
- `limit` com `per: "player"` e `per: "gamification"`, `every` (janela), `query` (filtro extra) e `total` numérico ou fórmula Mustache+exp4j.
|
|
422
|
+
- `points` (recompensa direta por ação, com operações NONE/MULTIPLY/DIVIDE).
|
|
423
|
+
- `notifications` de `EVENT_WIN`.
|
|
424
|
+
- `image` (small/medium/original).
|
|
425
|
+
- Cálculo de disponibilidade (`available`) por jogador.
|
|
426
|
+
- Resolução de action por `_id` ou nome no registro de logs.
|
|
58
427
|
|
|
59
|
-
|
|
428
|
+
### ❌ NÃO Suportado / Armadilhas
|
|
60
429
|
|
|
61
|
-
|
|
430
|
+
- **`linkedActions`** — campo e classe `LinkedAction` existem, são persistidos, mas **nenhum código os lê**. Inerte.
|
|
431
|
+
- **`limit.per: "team"`** — constante `PER_TEAM` existe, mas **nenhum branch trata** esse valor; o limite nunca bloqueia (no-op silencioso). Use `"player"` ou `"gamification"`.
|
|
432
|
+
- **Validação de `attributes[].type`** — inexistente na escrita. Tipos inválidos são aceitos e tratados como `String`.
|
|
433
|
+
- **Endpoint `PUT`** — não existe. `updateAction` no manager/DAO é inacessível pelo REST.
|
|
434
|
+
- **Patch parcial** — não suportado; POST é full replace (upsert).
|
|
435
|
+
- **`extra`** — persistido mas não interpretado pelo firing de action.
|
|
436
|
+
- **`RewardPoint.perPlayer`** — presente no schema, não consultado no firing de action.
|
|
437
|
+
- **`points`/`notifications` para times** — ignorados; só processados quando o principal é jogador.
|
|
438
|
+
- **Paginação em `GET /v3/action`** — não há; retorna o array completo.
|
|
439
|
+
- **Inconsistência de janela no limite** (vide seção 8) — o mesmo `limit` pode avaliar diferente conforme o ponto de entrada.
|
|
62
440
|
|
|
63
|
-
|
|
441
|
+
---
|
|
64
442
|
|
|
65
|
-
|
|
443
|
+
## 8. Segurança e Permissões
|
|
66
444
|
|
|
67
|
-
|
|
445
|
+
- **Autenticação:** Bearer token via `AuthBean`/`FrontController.getInstance(apiKey)`. Cada apiKey isola a coleção `action` da sua gamificação.
|
|
446
|
+
- **Isolamento multi-tenant:** garantido pela resolução de `ManagerFactory` por apiKey; não há cruzamento entre gamificações.
|
|
447
|
+
- **Registro de log por jogador:** em `/v3/action/log`, um jogador autenticado só pode registrar log para si mesmo (`ActionRest.insertLog:489`); registro para terceiros exige credencial de admin.
|
|
68
448
|
|
|
69
|
-
|
|
70
|
-
- Crie ações **genéricas** e use atributos para detalhar: prefira `comprar` com atributo `produto` a criar `comprar_sapato` e `comprar_camisa`
|
|
71
|
-
- Omita `attributes` (ou envie `[]`) quando o contexto não for necessário para nenhum challenge
|
|
449
|
+
**Superfícies de injeção (confirmadas no código):**
|
|
72
450
|
|
|
73
|
-
|
|
451
|
+
1. **`GET /v3/action` — `q`, `orderby`, `fields`** são concatenados literalmente na string do pipeline de aggregation (`ActionManager.findAllActions`). Entrada controlável pode alterar o pipeline (NoSQL injection / DoS por query). Mitigação no código: nenhuma.
|
|
452
|
+
2. **`limit.query`** é Mustache-parseado com `{player, action_log}` e **concatenado cru** na query de contagem (`ActionRest.insertLog:522`, `evaluateLimit:285`, `trackWithRestrictions:200`). Valores controláveis que caiam nos placeholders entram na query sem sanitização.
|
|
453
|
+
3. **`limit.total` como fórmula** é avaliada via `ExpressionBuilder` (exp4j) sobre string Mustache — exceções são engolidas, podendo silenciosamente liberar o registro.
|
|
74
454
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
455
|
+
**Inconsistência de comportamento (mesma config, resultado diferente conforme entrada):**
|
|
456
|
+
|
|
457
|
+
| Caminho | Janela de contagem |
|
|
458
|
+
|---|---|
|
|
459
|
+
| `ActionRest.insertLog` / `insertLogBulk` | `{$gte: from, $lte: log.time}` |
|
|
460
|
+
| `ActionManager.trackWithRestrictions` | `{$gte: from}` |
|
|
461
|
+
| `ActionManager.evaluateLimit` (cálculo de `available`) | `{$gte: from}` |
|
|
462
|
+
|
|
463
|
+
O `available` exibido (sem limite superior) pode divergir do que de fato será permitido em `insertLog` (com `$lte log.time`).
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## 9. Observabilidade e Troubleshooting
|
|
468
|
+
|
|
469
|
+
**Verificar se uma action existe / está ativa:**
|
|
78
470
|
|
|
79
|
-
**Busca múltipla com funifier_list:** o parâmetro `search` aceita termos separados por vírgula (OR):
|
|
80
471
|
```
|
|
81
|
-
|
|
472
|
+
GET /v3/action/<id>
|
|
473
|
+
GET /v3/action?action=<nome> # busca por nome (regex, case-insensitive)
|
|
82
474
|
```
|
|
83
475
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
476
|
+
**Verificar disponibilidade para um jogador (limite):**
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
GET /v3/action?available=true&player=<playerId>
|
|
480
|
+
# cada action retorna "available": true|false
|
|
481
|
+
```
|
|
87
482
|
|
|
88
|
-
|
|
483
|
+
**Inspecionar via banco (coleção `action`):**
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
db.action.findOne({_id: "<id>"})
|
|
487
|
+
db.action.find({active: false}) # actions desativadas
|
|
488
|
+
db.action.find({"limit.per": "team"}) # ATENÇÃO: limite inerte (no-op)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Antes de excluir uma action — quais challenges/triggers a usam:**
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
db.challenge.find({"rules.actionId": "<id>"}) # serão alterados pela cascata
|
|
495
|
+
db.trigger_html.find({"actions._id": "<id>"}) # serão alterados pela cascata
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Erros comuns (registro de log) e causas:**
|
|
499
|
+
|
|
500
|
+
| Mensagem | Causa |
|
|
501
|
+
|---|---|
|
|
502
|
+
| `action <x> does not exist` | `actionId` não casa com `_id` nem `action` (nome). |
|
|
503
|
+
| `action <x> is not active` | `active = false` na action. |
|
|
504
|
+
| `you have exced the limit ...` | `limit` excedido para o período/escopo. |
|
|
505
|
+
| `it was not possible to identify the limit ...` | Fórmula `limit.total` lançou exceção ao avaliar. |
|
|
506
|
+
|
|
507
|
+
**O que verificar quando algo não funciona:**
|
|
508
|
+
|
|
509
|
+
- Action não concede pontos → confira `points` na action **e** que o principal é jogador (times não recebem). Confira que o log foi de fato processado (firing assíncrono por padrão).
|
|
510
|
+
- Limite não bloqueia → confira se `per` é `"player"`/`"gamification"` (não `"team"`).
|
|
511
|
+
- `_id` "mudou" após criar → sanitização (acentos/espaços/símbolos removidos).
|
|
512
|
+
- Atributo numérico tratado como texto → confira `attributes[].type == "Number"` na definição da action.
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## 10. Exemplos Práticos
|
|
517
|
+
|
|
518
|
+
### 10.1 Mínimo funcional — ação simples
|
|
89
519
|
|
|
90
|
-
**Ação simples:**
|
|
91
520
|
```json
|
|
92
521
|
{
|
|
93
522
|
"_id": "watch_video",
|
|
@@ -96,7 +525,8 @@ POST com `_id` existente = update (upsert). POST sem `_id` = cria com ID gerado.
|
|
|
96
525
|
}
|
|
97
526
|
```
|
|
98
527
|
|
|
99
|
-
|
|
528
|
+
### 10.2 Avançado — atributos, pontos dinâmicos, limite e notificação
|
|
529
|
+
|
|
100
530
|
```json
|
|
101
531
|
{
|
|
102
532
|
"_id": "sell",
|
|
@@ -105,33 +535,55 @@ POST com `_id` existente = update (upsert). POST sem `_id` = cria com ID gerado.
|
|
|
105
535
|
"attributes": [
|
|
106
536
|
{ "name": "product", "type": "String" },
|
|
107
537
|
{ "name": "price", "type": "Number" }
|
|
108
|
-
]
|
|
538
|
+
],
|
|
539
|
+
"points": [
|
|
540
|
+
{ "total": 1, "category": "revenue", "operation": 1, "value": "price" }
|
|
541
|
+
],
|
|
542
|
+
"notifications": [
|
|
543
|
+
{ "event": 0, "type": 0, "scope": 1, "content": "Você registrou uma venda!" }
|
|
544
|
+
],
|
|
545
|
+
"limit": {
|
|
546
|
+
"total": "{{player.extra.daily_quota}}",
|
|
547
|
+
"per": "player",
|
|
548
|
+
"every": "1d",
|
|
549
|
+
"query": "attributes.product:'{{action_log.attributes.product}}'"
|
|
550
|
+
}
|
|
109
551
|
}
|
|
110
552
|
```
|
|
111
553
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
554
|
+
- `operation: 1` (MULTIPLY_BY_ATTRIBUTE) com `value: "price"` → pontos = `1 * price`.
|
|
555
|
+
- `limit.total` é fórmula dinâmica resolvida do `player.extra.daily_quota`.
|
|
556
|
+
- `limit.query` restringe a contagem ao mesmo `product`.
|
|
115
557
|
|
|
116
|
-
|
|
117
|
-
> ```
|
|
118
|
-
> funifier_database action=aggregate collection=challenge pipeline='[{"$match": {"rules.actionId": "<action_id>"}}]'
|
|
119
|
-
> ```
|
|
558
|
+
### 10.3 Anti-pattern — o que NÃO fazer
|
|
120
559
|
|
|
121
|
-
|
|
560
|
+
```json
|
|
561
|
+
{
|
|
562
|
+
"_id": "Vender Açaí #1",
|
|
563
|
+
"action": "venda",
|
|
564
|
+
"attributes": [ { "name": "preço final", "type": "Integer" } ],
|
|
565
|
+
"linkedActions": [ { "action": "bonus", "playerAttributeName": "indicado" } ],
|
|
566
|
+
"limit": { "total": 3, "per": "team", "every": "1h" }
|
|
567
|
+
}
|
|
568
|
+
```
|
|
122
569
|
|
|
123
|
-
|
|
570
|
+
Por que está errado:
|
|
124
571
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
572
|
+
- `_id` com espaços/acentos/símbolos → será sanitizado para `Vender_Acai_1` (não será o que você espera referenciar).
|
|
573
|
+
- `attributes[].name` `"preço final"` → vira `preo_final`; e `type: "Integer"` é inválido → tratado como `String` (sem coerção numérica).
|
|
574
|
+
- `linkedActions` → **inerte**, nunca executado.
|
|
575
|
+
- `limit.per: "team"` → **não bloqueia nada** (branch morto); o limite é ignorado.
|
|
128
576
|
|
|
129
|
-
|
|
577
|
+
---
|
|
130
578
|
|
|
131
|
-
## Checklist
|
|
579
|
+
## Checklist de Configuração
|
|
132
580
|
|
|
133
|
-
- [ ] `_id`
|
|
134
|
-
- [ ] `action` (nome amigável) preenchido
|
|
135
|
-
- [ ] `active: true`
|
|
136
|
-
- [ ]
|
|
137
|
-
- [ ]
|
|
581
|
+
- [ ] `_id` em minúsculas, sem espaços/acentos (`watch_video`, não `Watch Video`) — caso contrário será sanitizado.
|
|
582
|
+
- [ ] `action` (nome amigável) preenchido.
|
|
583
|
+
- [ ] `active: true` (senão logs serão rejeitados).
|
|
584
|
+
- [ ] `attributes[].type` ∈ `String` | `Number` | `Boolean` (qualquer outro vira `String` silenciosamente).
|
|
585
|
+
- [ ] Se usar `limit`: `per` ∈ `player` | `gamification` (**nunca** `team` — não funciona).
|
|
586
|
+
- [ ] Se usar `points`: `category` aponta para um `point_category` existente; lembre que só jogadores recebem.
|
|
587
|
+
- [ ] Antes de `DELETE`: ciente de que rules de challenges e referências em trigger_html serão removidas em cascata.
|
|
588
|
+
- [ ] Não conte com `linkedActions` (campo morto).
|
|
589
|
+
- [ ] Validado com `GET /v3/action/<id>` ou `GET /v3/action?action=<nome>`.
|