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,88 +1,495 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `action-log`
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Acesso Studio:** sem tela dedicada no backend; os logs são consumidos via API e aparecem indiretamente no frontend do Studio (atividade do jogador / módulo Action). Nenhuma rota `/studio/...` é definida neste serviço.
|
|
4
|
+
**API Endpoint:** `/v3/action/log` (gamificação) · `/v3/system/action/log` (sistema) · `/2.0.0/action/log` (legado)
|
|
5
|
+
**Coleção MongoDB:** `action_log`
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
---
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## 1. Visão Geral
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
O módulo `action-log` é o **ponto de entrada de eventos de jogador** na gamificação Funifier. Cada documento da coleção `action_log` representa uma ocorrência de uma `Action` previamente cadastrada — quem fez (`userId`), o que fez (`actionId`), quando (`time`) e com quais dados (`attributes`).
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- Para alimentar desafios, rankings e recompensas
|
|
13
|
-
- Para importar ações em lote de sistemas externos
|
|
14
|
-
- Para rastrear comportamento dos jogadores
|
|
13
|
+
Papel arquitetural:
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
- É o **gatilho universal do motor de gamificação**: registrar um action log é o que dispara a avaliação de desafios, pontuação, níveis, triggers e webhooks. O processamento real acontece em `AchievementManager.fireAction(ActionLog)` (ver `achievement.md` §2.2) — este módulo apenas valida, persiste e enfileira.
|
|
16
|
+
- É também a **fonte de verdade histórica para contadores baseados em frequência**: desafios, KPIs e regras de elegibilidade contam/agrupam documentos de `action_log` por jogador, por time e por janela de tempo (métodos `countActionLogsRegistered*`).
|
|
17
|
+
- A escrita é **assíncrona por padrão** — a chamada HTTP retorna imediatamente e o processamento ocorre em uma fila em memória por gamificação.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
- **Player**: o jogador referenciado deve existir
|
|
19
|
+
Problemas que resolve:
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
- Desacopla a captura do evento (rápida, sob alta carga) do seu processamento (caro: avaliação de desafios, agregações).
|
|
22
|
+
- Mantém um log auditável de tudo que cada jogador executou, com atributos arbitrários por evento.
|
|
23
|
+
- Permite contagens e agregações temporais (frequência, somatórios de atributos) diretamente sobre o histórico bruto.
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
**Método:** GET
|
|
25
|
-
**Endpoint:** `/v3/action/log`
|
|
25
|
+
Relação direta com outros módulos:
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
- `action` — toda action log referencia uma `Action` (`actionId`). A `Action` define limites (`limit`) e atributos esperados. Sem action ativa correspondente, o log é rejeitado (na via de gamificação).
|
|
28
|
+
- `achievement` — `trackSynchonous` chama `AchievementManager.fireAction` ao final; é onde desafios são avaliados e recompensas geradas.
|
|
29
|
+
- `trigger` — `before_win` e `after_win` da entidade `action` são disparados em torno da persistência.
|
|
30
|
+
- `challenge`, `level`, `kpi-formulas`, `crossword`, etc. — consomem os contadores de action log (`countActionLogsRegisteredByUser/Team`, `findLatestActionLogDate...`) para avaliar regras.
|
|
31
|
+
- `csv-data` — `CsvManager.importActionLogRows` converte linhas de CSV em action logs.
|
|
32
|
+
- `question`, `quiz`, `account`, `arduino/SensorManager` — constroem `ActionLog` internamente e o injetam no pipeline.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Arquitetura e Fluxos
|
|
37
|
+
|
|
38
|
+
### 2.1 Classes envolvidas
|
|
39
|
+
|
|
40
|
+
| Classe | Arquivo | Papel |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `com.funifier.engine.action.ActionLog` | `engine/action/ActionLog.java` (8-77) | Entidade/POJO — documento raiz |
|
|
43
|
+
| `com.funifier.engine.action.ActionManager` | `engine/action/ActionManager.java` (35-839) | Manager de gamificação: track, validação de limites, queries/agregações, contadores |
|
|
44
|
+
| `com.funifier.engine.action.ActionDaoMongo` | `engine/action/ActionDaoMongo.java` | Acesso MongoDB (`action_log`, `action`, `trigger_html`) |
|
|
45
|
+
| `com.funifier.engine.action.ActionAsyncProcessor` | `engine/action/ActionAsyncProcessor.java` | Thread + fila em memória (`LinkedBlockingQueue`) |
|
|
46
|
+
| `com.funifier.engine.action.Limit` | `engine/action/Limit.java` | Constantes e config de limite por player/team/game |
|
|
47
|
+
| `com.funifier.rest.v3.rest.ActionRest` | `rest/v3/rest/ActionRest.java` | Controller REST v3 (`/v3/action`) |
|
|
48
|
+
| `com.funifier.rest.engine.ActionRest` | `rest/engine/ActionRest.java` | Controller legado (`/2.0.0/action`) |
|
|
49
|
+
| `com.funifier.rest.v3.rest.system.ActionRest` | `rest/v3/rest/system/ActionRest.java` | Controller de sistema (`/v3/system/action`) |
|
|
50
|
+
| `com.funifier.engine.system.action.ActionManager` | `engine/system/action/ActionManager.java` | Manager de sistema — escrita direta, **sem pipeline** |
|
|
51
|
+
|
|
52
|
+
> Não há `Repository` para a entidade `ActionLog` além do `ActionDaoMongo`; várias queries de `action_log` são montadas diretamente no `ActionManager`.
|
|
53
|
+
|
|
54
|
+
### 2.2 Pipeline principal — `POST /v3/action/log`
|
|
55
|
+
|
|
56
|
+
Sequência real (método `ActionRest.insertLog`, `rest/v3/rest/ActionRest.java:408`):
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
[1] Resolve a Action → ActionManager.findActionByIdOrName(log.actionId)
|
|
60
|
+
query: {$or:[{_id:#},{action:#}]} (aceita id OU nome)
|
|
61
|
+
Se null → 400 "action ... does not exist"
|
|
62
|
+
Se a.active==false → 400 "action ... is not active"
|
|
63
|
+
|
|
64
|
+
[2] Defaults silenciosos
|
|
65
|
+
- log.time = new Date() se null
|
|
66
|
+
- log.id = Guid.newShortGuid() se null
|
|
67
|
+
- log.actionId = a.getId() ← NORMALIZA para o _id canônico da Action
|
|
68
|
+
|
|
69
|
+
[3] Resolve o principal (jogador OU time)
|
|
70
|
+
- userId null/vazio/"me" → usa player do token; senão 400 "player is required"
|
|
71
|
+
- findPrincipalByUserOrTeamId(userId)≠null → ok (jogador ou time existente)
|
|
72
|
+
- findByAlternativeLogin(userId)≠null → userId = player.getId()
|
|
73
|
+
- senão → tenta auto-criar (ver §5); se não puder → 400
|
|
74
|
+
|
|
75
|
+
[4] Restrição de token de jogador
|
|
76
|
+
Se autenticado como jogador e player != log.userId → 400
|
|
77
|
+
(só pode registrar a própria ação)
|
|
78
|
+
|
|
79
|
+
[5] Avaliação de limite (a.limit), se a.limit.total != null → ver §5
|
|
80
|
+
Se count >= total → 400 "you have exced the limit ..."
|
|
81
|
+
|
|
82
|
+
[6] Persistência
|
|
83
|
+
- async != "false" → ActionManager.track(log) (enfileira, retorna já)
|
|
84
|
+
- async == "false" → ActionManager.trackSynchonous(log) (processa e retorna achievements)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Fluxo de registro — `action-log`
|
|
88
|
+
|
|
89
|
+
```mermaid
|
|
90
|
+
flowchart TD
|
|
91
|
+
A["POST /v3/action/log"] --> B{"Action existe<br/>e ativa?"}
|
|
92
|
+
B -- não --> E1["400 Bad Request"]
|
|
93
|
+
B -- sim --> C["Defaults: time, _id<br/>actionId = id canônico"]
|
|
94
|
+
C --> D{"Principal<br/>resolvido?"}
|
|
95
|
+
D -- não --> E2["400 / auto-create"]
|
|
96
|
+
D -- sim --> F{"Token de jogador<br/>= userId?"}
|
|
97
|
+
F -- não --> E3["400"]
|
|
98
|
+
F -- sim --> G{"Limite excedido?"}
|
|
99
|
+
G -- sim --> E4["400 limit exceeded"]
|
|
100
|
+
G -- não --> H{"async == 'false'?"}
|
|
101
|
+
H -- não --> Q["track() → fila em memória"]
|
|
102
|
+
H -- sim --> S["trackSynchonous()"]
|
|
103
|
+
Q -.->|thread async| S
|
|
104
|
+
S --> T1["trigger before_win"]
|
|
105
|
+
T1 --> P["addLog → save em action_log"]
|
|
106
|
+
P --> T2["trigger after_win"]
|
|
107
|
+
T2 --> FA["AchievementManager.fireAction()"]
|
|
108
|
+
FA --> R["achievements retornados"]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 2.3 Núcleo de processamento — `trackSynchonous(ActionLog)`
|
|
112
|
+
|
|
113
|
+
`ActionManager.trackSynchonous` (`engine/action/ActionManager.java:322`) é o coração do pipeline. Ordem **exata** de execução:
|
|
114
|
+
|
|
115
|
+
1. Cria `TriggerContext` e injeta uma lista `achievements` em `context.extra`.
|
|
116
|
+
2. `triggerManager.execute(actionId, action, Trigger.ENTITY_ACTION, Trigger.EVENT_BEFORE_WIN, userId, context)` — dispara triggers `before_win` da entidade `action`. **Ocorre antes da persistência**, então um trigger pode mutar `attributes` antes do save.
|
|
117
|
+
3. `addLog(action)` → `ActionDaoMongo.addLog` → `c.save(action)` na coleção `action_log`.
|
|
118
|
+
4. `triggerManager.execute(... Trigger.EVENT_AFTER_WIN ...)` — dispara triggers `after_win`. **Ocorre após o save**; mutações aqui não re-persistem o log automaticamente.
|
|
119
|
+
5. `AchievementManager.fireAction(action)` — avalia desafios, pontos, níveis, etc. (ver `achievement.md` §2.2).
|
|
120
|
+
6. Retorna `fireAction(...)` + os achievements acumulados pelos triggers em `context.extra`.
|
|
121
|
+
|
|
122
|
+
> A entidade de trigger é `Trigger.ENTITY_ACTION = "action"`. Não existe entidade de trigger separada para action log — `ENTITY_ACTIONLOG = "action_log"` está **comentada** no código (`Trigger.java:13`).
|
|
123
|
+
|
|
124
|
+
### 2.4 Sequência síncrona (`async=false`)
|
|
125
|
+
|
|
126
|
+
```mermaid
|
|
127
|
+
sequenceDiagram
|
|
128
|
+
participant C as Cliente
|
|
129
|
+
participant R as ActionRest
|
|
130
|
+
participant AM as ActionManager
|
|
131
|
+
participant TM as TriggerManager
|
|
132
|
+
participant DB as Mongo (action_log)
|
|
133
|
+
participant ACH as AchievementManager
|
|
134
|
+
|
|
135
|
+
C->>R: POST /v3/action/log?async=false
|
|
136
|
+
R->>R: valida action, principal, limite
|
|
137
|
+
R->>AM: trackSynchonous(log)
|
|
138
|
+
AM->>TM: execute(action, before_win)
|
|
139
|
+
AM->>DB: save(log)
|
|
140
|
+
AM->>TM: execute(action, after_win)
|
|
141
|
+
AM->>ACH: fireAction(log)
|
|
142
|
+
ACH-->>AM: List<Achievement>
|
|
143
|
+
AM-->>R: achievements
|
|
144
|
+
R-->>C: 201 { action, achievements }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2.5 Processamento assíncrono — `ActionAsyncProcessor`
|
|
148
|
+
|
|
149
|
+
- No construtor de `ActionManager` (`:41-46`) é iniciada **uma thread por gamificação**, nomeada `Funifier-ActionLog-Async-<apiKey>`, rodando `ActionAsyncProcessor.run()`.
|
|
150
|
+
- `track(log)` apenas faz `queue.offer(log)` em um `LinkedBlockingQueue<ActionLog>` **em memória**.
|
|
151
|
+
- O loop (`run()`, `:32-61`): `poll()` retira da fila; **se** `actionId != null && userId != null` → `trackSynchonous`; senão a entrada é **descartada silenciosamente** (já foi removida pelo `poll`). `Thread.sleep(100)` só ocorre quando a fila está vazia.
|
|
152
|
+
- A fila **não é persistida**: reinício/crash do processo perde tudo que estava enfileirado e não processado. Há um paliativo manual: `PUT /v3/action/queue/save` despeja a fila atual na coleção `action_log_memory`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 3. Estrutura dos Objetos
|
|
157
|
+
|
|
158
|
+
### 3.1 `ActionLog` — documento raiz (coleção `action_log`)
|
|
159
|
+
|
|
160
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
161
|
+
|---|---|---|---|---|
|
|
162
|
+
| `_id` | String | `Guid.newShortGuid()` se omitido | Não (gerado) | Short GUID alfanumérico (**não** é ObjectId). Mapeado de `id` via `@JsonProperty("_id")`. |
|
|
163
|
+
| `actionId` | String | — | Sim | Id da `Action`. Na via de gamificação é **normalizado** para o `_id` canônico da Action (aceita nome no input). |
|
|
164
|
+
| `userId` | String | token / auto | Sim | Id do jogador OU id do time (principal). Aceita `"me"` (resolve via token). |
|
|
165
|
+
| `time` | Date (BSON date, UTC) | `new Date()` se omitido | Não | Momento da ação. Mongo armazena em GMT0. |
|
|
166
|
+
| `attributes` | Map<String,Object> | `null` | Não | Dados livres do evento (ex: `{product, price, location}`). Tipos arbitrários. |
|
|
167
|
+
|
|
168
|
+
`@JsonIgnoreProperties(ignoreUnknown=true)` na classe: **qualquer campo extra enviado no JSON é ignorado silenciosamente** (não persiste e não gera erro).
|
|
169
|
+
|
|
170
|
+
**Campos legados/comentados (não existem em runtime):**
|
|
171
|
+
|
|
172
|
+
- `configId` — comentado (`ActionLog.java:19`); resquício de uma versão que ligava o log a uma "action config".
|
|
173
|
+
- `entity` (JsonElement) e `attributes` como `DBObject` — comentados (`:25-26`); substituídos por `Map<String,Object>`.
|
|
174
|
+
- Construtor `ActionLog(configId, actionId, userId, time)` — comentado (`:48-53`).
|
|
175
|
+
|
|
176
|
+
**Não há** campos `extra`, `origin`, `created`, `updated` ou índice de tenant no documento. O isolamento entre gamificações é por **banco/conexão** (ver §8), não por campo.
|
|
177
|
+
|
|
178
|
+
### 3.2 `Limit` — configuração de limite (vive na `Action`, não no log)
|
|
179
|
+
|
|
180
|
+
Define quantas vezes a action pode ser registrada num período. Avaliado em `insertLog` / `trackWithRestrictions` / `evaluateLimit`.
|
|
181
|
+
|
|
182
|
+
| Campo | Tipo | Descrição |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `total` | Object (Number **ou** String) | Limite numérico, ou expressão Mustache+exp4j (ex: `{{player.extra.max_actions}}`). |
|
|
185
|
+
| `per` | String | Escopo do limite: ver constantes abaixo. |
|
|
186
|
+
| `every` | String | Janela móvel (ex: `5h`, `1d`). Default `-10y` (praticamente sem limite temporal). |
|
|
187
|
+
| `query` | String | Filtro Mongo adicional, Mustache-parsed (mín. 3 chars). Ex: `attributes.video:'{{action_log.attributes.video}}'`. |
|
|
188
|
+
|
|
189
|
+
Constantes de `Limit.per`:
|
|
190
|
+
|
|
191
|
+
| Valor | Constante | Comportamento na contagem |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `player` | `PER_PLAYER` | Conta `{userId, actionId, time>=from [, query]}` |
|
|
194
|
+
| `gamification` | `PER_GAME` | Conta `{actionId, time>=from [, query]}` (toda a gamificação) |
|
|
195
|
+
| `team` | `PER_TEAM` | **Declarado mas NÃO tratado** nas verificações de limite (ver §7) |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 4. Endpoints
|
|
200
|
+
|
|
201
|
+
### `GET /v3/action/log` — consultar action logs
|
|
202
|
+
|
|
203
|
+
| Aspecto | Detalhe |
|
|
204
|
+
|---|---|
|
|
205
|
+
| Finalidade | Listar/paginar action logs |
|
|
206
|
+
| Método | `ActionManager.findAllActionLogs` (`:534`) |
|
|
207
|
+
| Autenticação | Bearer token |
|
|
208
|
+
| Paginação | Header `Range: items=0-100` (default 100) |
|
|
209
|
+
|
|
210
|
+
**Query params:** `player` (`"me"` resolve via token), `action`, `q` (critério Mongo bruto), `published_min`/`published_max` (RFC 3339 ou keyword `-1d`,`-30m`...), `orderby` (`actionId`/`userId`/`time`), `reverse`, `max_results` (default 100).
|
|
211
|
+
|
|
212
|
+
**Comportamento real:** monta um pipeline `$match` + `$sort` + `$limit`. `q` e `orderby` são **concatenados crus** na query (ver §8). Sempre limita a 100 se `max_results <= 0`.
|
|
213
|
+
|
|
214
|
+
### `GET /v3/action/log/frequency` — frequência agrupada por tempo
|
|
215
|
+
|
|
216
|
+
| Aspecto | Detalhe |
|
|
217
|
+
|---|---|
|
|
218
|
+
| Finalidade | Agrupar ações por bucket temporal |
|
|
219
|
+
| Método | `ActionManager.findAllActionLogsFrequency` (`:629`) |
|
|
220
|
+
| Autenticação | Bearer token |
|
|
221
|
+
|
|
222
|
+
**Query params:** `player`, `action`, `q`, `published_min/max`, `every` (ex: `1d`, `3M`), `time_zone` (ex: `America/Sao_Paulo`), `sum_field` (soma `attributes.<sum_field>` em vez de contar ocorrências).
|
|
223
|
+
|
|
224
|
+
**Comportamento real:** Mongo grava datas em GMT0; `time_zone` aplica um offset via `$add`/`$subtract` antes do agrupamento. Sem `sum_field`, usa `total: {$sum: 1}`. O agrupamento usa `$dayOfYear`/`$week`/`$month`/`$year` + `$mod` para janelas múltiplas (`everyAmount`).
|
|
225
|
+
|
|
226
|
+
### `POST /v3/action/log/aggregate` — agregação livre
|
|
227
|
+
|
|
228
|
+
| Aspecto | Detalhe |
|
|
229
|
+
|---|---|
|
|
230
|
+
| Finalidade | Rodar pipeline de agregação arbitrário |
|
|
231
|
+
| Método | `ActionManager.findAllActionLogsAggregate` (`:581`) |
|
|
232
|
+
| Autenticação | Bearer token |
|
|
233
|
+
| Body | Lista de stages (strings) de aggregation Mongo |
|
|
234
|
+
|
|
235
|
+
**Comportamento real:** aplica um `$match` (filtros player/action/q/time) e **acrescenta os stages do body** após ele. Default (body vazio) sugerido: `["{$group:{_id:\"$userId\",total:{$sum:1},start:{$min:\"$time\"},end:{$max:\"$time\"}}}"]`. Superfície de injeção (§8).
|
|
236
|
+
|
|
237
|
+
### `POST /v3/action/log` — registrar action log
|
|
238
|
+
|
|
239
|
+
| Aspecto | Detalhe |
|
|
240
|
+
|---|---|
|
|
241
|
+
| Finalidade | Registrar ocorrência de uma action |
|
|
242
|
+
| Método | `ActionRest.insertLog` (`:408`) |
|
|
243
|
+
| Autenticação | Bearer token |
|
|
244
|
+
| Full replace ou patch | Insert; com `_id` reutilizado vira **replace + reprocessamento** (§5) |
|
|
245
|
+
|
|
246
|
+
**Query param `async`:** **somente a string literal `"false"`** ativa o modo síncrono (`if(async != null && "false".equals(async))`). Qualquer outro valor (`"true"`, `"sync"`, `"0"`, ausência) → assíncrono. No modo síncrono a resposta inclui os achievements gerados.
|
|
247
|
+
|
|
248
|
+
**Exemplo de request (síncrono):**
|
|
249
|
+
```
|
|
250
|
+
POST /v3/action/log?async=false
|
|
251
|
+
{ "actionId": "buy", "userId": "player@x.com", "attributes": { "product": "book", "price": 86.5 } }
|
|
252
|
+
```
|
|
253
|
+
**Response 201:**
|
|
28
254
|
```json
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
"_id": "6509e4e28325771ffaa4506e",
|
|
32
|
-
"actionId": "sell",
|
|
33
|
-
"userId": "jerry",
|
|
34
|
-
"time": 1695147234712,
|
|
35
|
-
"attributes": {
|
|
36
|
-
"product": "bike",
|
|
37
|
-
"price": 1200
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
]
|
|
255
|
+
{ "action": { "_id": "aB3xQ", "actionId": "buy", "userId": "player@x.com", "time": 1737300000000, "attributes": { "product": "book", "price": 86.5 } },
|
|
256
|
+
"achievements": [ { "type": 0, "total": 10, "point": "xp" } ] }
|
|
41
257
|
```
|
|
42
258
|
|
|
43
|
-
###
|
|
44
|
-
|
|
45
|
-
|
|
259
|
+
### `POST /v3/action/log/bulk` — registro em lote
|
|
260
|
+
|
|
261
|
+
| Aspecto | Detalhe |
|
|
262
|
+
|---|---|
|
|
263
|
+
| Finalidade | Registrar múltiplas action logs |
|
|
264
|
+
| Método | `ActionRest.insertLogBulk` (`:564`) |
|
|
265
|
+
| Autenticação | Bearer token |
|
|
266
|
+
| Modo | **Sempre assíncrono** (não há opção `async`) |
|
|
267
|
+
|
|
268
|
+
**Comportamento real:** valida item a item; falhas **não interrompem** o lote — itens inválidos vão para `content_with_not_allowed_actions` / `content_with_not_allowed_players` e contam em `total_ignored`. **A restrição de "jogador só registra a própria ação" NÃO existe nesta rota** (ver §7/§8).
|
|
46
269
|
|
|
47
|
-
**
|
|
270
|
+
**Response 201:**
|
|
48
271
|
```json
|
|
272
|
+
{ "total_registered": 2, "content_size": 3, "total_ignored": 1, "content": [ ... ],
|
|
273
|
+
"content_with_not_allowed_players": [ { "actionId": "buy", "userId": "inexistente" } ] }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### `DELETE /v3/action/log/{id}` — remover action log
|
|
277
|
+
|
|
278
|
+
`ActionRest.deleteLog` → `ActionManager.deleteActionLog` (`:824`): `log.remove("{_id:#}", id)`. **Não estorna** pontos/achievements já gerados pelo log.
|
|
279
|
+
|
|
280
|
+
### `GET /v3/action/log/attribute/{attribute}/index/create` — criar índice geoespacial
|
|
281
|
+
|
|
282
|
+
`create2dAttributeIndex` (`:765`): cria índice `2dsphere` em `attributes.<attribute>` (para queries `$near`/geo em checkins). Disponível a qualquer Bearer token.
|
|
283
|
+
|
|
284
|
+
### `GET /v3/action/log/attribute/{attribute}/index/drop` — remover índice geoespacial
|
|
285
|
+
|
|
286
|
+
`drop2dAttributeIndex` (`:779`): **a resposta `indexes` é montada ANTES do `dropIndex`** — portanto reflete o estado pré-remoção, incluindo o índice que está sendo removido (ver §9).
|
|
287
|
+
|
|
288
|
+
### Endpoints de fila assíncrona
|
|
289
|
+
|
|
290
|
+
| Endpoint | Método | Ação |
|
|
291
|
+
|---|---|---|
|
|
292
|
+
| `/v3/action/queue/info` | GET | Estatísticas da fila (`size`, `remaining_capacity`, durações, maior log) |
|
|
293
|
+
| `/v3/action/queue/info` | DELETE | Zera as estatísticas |
|
|
294
|
+
| `/v3/action/queue/clear` | DELETE | **Esvazia a fila** (descarta logs não processados) |
|
|
295
|
+
| `/v3/action/queue/first` | GET | Primeiros 100 itens da fila (sem remover) |
|
|
296
|
+
| `/v3/action/queue/last` | GET | Últimos 100 itens da fila (sem remover) |
|
|
297
|
+
| `/v3/action/queue/save` | PUT | Despeja a fila em `action_log_memory` |
|
|
298
|
+
|
|
299
|
+
### `POST /v3/system/action/log` — registro de sistema (sem pipeline)
|
|
300
|
+
|
|
301
|
+
`system.ActionRest.insertLog` → `system.ActionManager.insertLog` (`engine/system/action/ActionManager.java:27`). Grava direto em `action_log` (`c.save`), com apenas defaults de `time`/`_id`. **NÃO valida action, NÃO valida player, NÃO dispara triggers, NÃO chama fireAction.** Também expõe `GET /v3/system/action/log/{id}`, `DELETE /v3/system/action/log/{id}` e `POST /v3/system/action/log/aggregate`. Use apenas para inserir registros brutos que **não devem** acionar gamificação.
|
|
302
|
+
|
|
303
|
+
### `GET|POST /2.0.0/action/log` — endpoint legado
|
|
304
|
+
|
|
305
|
+
`rest/engine/ActionRest.java`. Autenticação por `?api_key=` (query param). O `POST` apenas seta `time` e chama `track(log)` — **sem validar action, player ou limite**. Mantido por compatibilidade.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## 5. Regras de Negócio
|
|
310
|
+
|
|
311
|
+
Regras que existem no código mas não no schema do documento:
|
|
312
|
+
|
|
313
|
+
- **Normalização de `actionId` (gamificação):** `findActionByIdOrName` casa por `_id` **ou** por `action` (nome); em seguida `log.actionId` é sobrescrito pelo `_id` canônico. Postar pelo nome funciona, mas o persistido é sempre o id. As vias legado (`/2.0.0`) e sistema (`/v3/system`) **não** normalizam — persistem `actionId` verbatim.
|
|
314
|
+
- **Auto-criação de jogador:** se `userId` não existe como principal nem como login alternativo, o sistema tenta `AuthenticationManager.createIfDontExist`, respeitando `Account.players_allowed`. Se o limite da conta foi atingido → 400; se a gamificação não permite auto-criação → 400.
|
|
315
|
+
- **`"me"` como `userId`:** resolvido para o jogador do token.
|
|
316
|
+
- **Restrição de token de jogador:** em `POST /v3/action/log`, um token de **jogador** só registra ações do próprio `userId`. Esta checagem **não** existe em `/bulk`, está **comentada** em `trackWithRestrictions` (`ActionManager.java:167-169`, usado pelo CSV) e inexiste no legado/sistema.
|
|
317
|
+
- **Limite (`a.limit`):** quando `a.limit.total != null` e `> 0`, conta documentos em `action_log` na janela `time >= from` (`from = now - every`, ou `-10y`). `PER_PLAYER` filtra por `userId`; `PER_GAME` por toda a gamificação; `PER_TEAM` **não tem branch** (não limita nada). `total` pode ser expressão (`{{...}}` + exp4j); falha de parse → 400.
|
|
318
|
+
- **Idempotência / risco de upsert:** `addLog` usa `c.save(action)`, que é **upsert por `_id`**. Reenviar um `_id` já existente **substitui** o documento **e dispara `fireAction` novamente** — podendo recontabilizar recompensas. Não há proteção contra reprocessamento por id repetido.
|
|
319
|
+
- **Multi-tenant:** toda operação resolve a conexão via `FrontController.getInstance(apiKey).getManagerFactory().getJongoConnection()` — cada gamificação tem seu próprio contexto Mongo. O documento `action_log` não carrega campo de tenant.
|
|
320
|
+
- **Consistência eventual:** no modo assíncrono (default), o retorno 201 **não garante** que o log foi processado nem persistido — apenas enfileirado.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## 6. Comportamentos Automáticos
|
|
325
|
+
|
|
326
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
327
|
+
|---|---|---|---|
|
|
328
|
+
| Default de `time` | `time == null` no insert | `time = new Date()` | Sim |
|
|
329
|
+
| Default de `_id` | `id == null` no insert | `id = Guid.newShortGuid()` | Sim |
|
|
330
|
+
| Normalização de `actionId` | via gamificação | `actionId = action._id` | Sim |
|
|
331
|
+
| Trigger `before_win` | antes do save | Pode mutar `attributes`, gerar achievements | Mutação persiste (save ocorre depois) |
|
|
332
|
+
| Persistência | `addLog` | `save` em `action_log` | Sim |
|
|
333
|
+
| Trigger `after_win` | após o save | Efeitos colaterais; não re-salva o log | Não (salvo re-save explícito) |
|
|
334
|
+
| `fireAction` | fim de `trackSynchonous` | Avalia desafios/pontos/níveis/webhooks | Gera docs em `achievement` etc. |
|
|
335
|
+
| Auto-criação de jogador | `userId` inexistente | Cria `Player` (se permitido) | Sim |
|
|
336
|
+
| Descarte de log inválido | `actionId`/`userId` null na fila | Item removido sem aviso | Não |
|
|
337
|
+
|
|
338
|
+
```mermaid
|
|
339
|
+
flowchart LR
|
|
340
|
+
IN["track(log)"] --> Q["fila em memória"]
|
|
341
|
+
Q --> CK{"actionId e userId<br/>não nulos?"}
|
|
342
|
+
CK -- não --> DROP["descarta (sem log)"]
|
|
343
|
+
CK -- sim --> BW["before_win"]
|
|
344
|
+
BW --> SV["save action_log"]
|
|
345
|
+
SV --> AW["after_win"]
|
|
346
|
+
AW --> FA["fireAction → achievements / level / webhook"]
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 7. Suportado vs NÃO Suportado
|
|
352
|
+
|
|
353
|
+
### ✅ Suportado
|
|
354
|
+
|
|
355
|
+
- Registro síncrono e assíncrono (`/v3/action/log`).
|
|
356
|
+
- Registro em lote tolerante a falhas (`/v3/action/log/bulk`).
|
|
357
|
+
- Consulta paginada, por frequência temporal e por agregação livre.
|
|
358
|
+
- Limite `per player` e `per gamification`, com janela `every` e filtro `query` dinâmico.
|
|
359
|
+
- Atributos arbitrários por evento, incluindo geolocalização (`location` + índice `2dsphere`).
|
|
360
|
+
- Contadores reutilizáveis por player e por time (usados por challenge/level/kpi).
|
|
361
|
+
- Importação via CSV (`CsvManager.importActionLogRows` → `trackWithRestrictions`).
|
|
362
|
+
- Inserção de sistema sem gamificação (`/v3/system/action/log`).
|
|
363
|
+
|
|
364
|
+
### ❌ NÃO Suportado / armadilhas
|
|
365
|
+
|
|
366
|
+
- **Limite `per team`:** `Limit.PER_TEAM` (`"team"`) é declarado mas **nenhuma verificação de limite o trata** (`insertLog`, `insertLogBulk`, `trackWithRestrictions`, `evaluateLimit` só tratam `PER_PLAYER` e `PER_GAME`). Configurar `per: "team"` → sem limite efetivo.
|
|
367
|
+
- **Entidade de trigger `action_log`:** `Trigger.ENTITY_ACTIONLOG = "action_log"` está **comentada**. Triggers disparam só na entidade `action`.
|
|
368
|
+
- **Durabilidade da fila assíncrona:** a fila é `LinkedBlockingQueue` em memória; **reinício/crash perde** logs enfileirados e não processados. O retorno 201 assíncrono não é garantia de processamento.
|
|
369
|
+
- **Estorno ao deletar:** `DELETE /v3/action/log/{id}` remove o documento mas **não reverte** pontos/achievements/níveis já concedidos.
|
|
370
|
+
- **Idempotência:** reenviar `_id` existente **reprocessa e pode duplicar** recompensas (upsert via `save`).
|
|
371
|
+
- **Restrição de jogador no `/bulk`, no legado e no sistema:** ausente — ver §8.
|
|
372
|
+
- **Resposta de `index/drop`:** mostra os índices **antes** da remoção (não reflete o pós-estado).
|
|
373
|
+
- **Campos legados `configId`, `entity`:** aceitos no JSON mas ignorados (`@JsonIgnoreProperties`); não persistem.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 8. Segurança e Permissões
|
|
378
|
+
|
|
379
|
+
- **Autenticação:** Bearer token (v3 e sistema); `?api_key=` na via legada `/2.0.0`.
|
|
380
|
+
- **Isolamento multi-tenant:** por conexão Mongo derivada do `apiKey` (`FrontController.getInstance(apiKey)`), não por campo no documento.
|
|
381
|
+
- **Restrição de jogador (parcial):** apenas `POST /v3/action/log` impede um token de jogador de registrar ação de outro `userId`. **Lacunas:**
|
|
382
|
+
- `POST /v3/action/log/bulk` — sem a checagem; um token de jogador pode, em lote, enviar logs com `userId` de terceiros (desde que o principal exista).
|
|
383
|
+
- `trackWithRestrictions` (CSV) — a checagem está **comentada** (`ActionManager.java:167-169`).
|
|
384
|
+
- `/2.0.0/action/log` e `/v3/system/action/log` — sem checagem.
|
|
385
|
+
- **Injeção em queries (raw):** parâmetros concatenados sem whitelist/sanitização no pipeline de agregação:
|
|
386
|
+
- `q` em `findAllActionLogs`/`Aggregate`/`Frequency` (`query.append(", " + q)`).
|
|
387
|
+
- `orderby` em `findAllActionLogs` (`"{$sort:{" + orderby + " : #}}"`).
|
|
388
|
+
- `aggregations` (body) em `/log/aggregate` — stages crus.
|
|
389
|
+
Mitigado por serem de leitura/agregação, mas permitem expressões arbitrárias de Mongo sobre `action_log` do tenant.
|
|
390
|
+
- **Operações de índice abertas:** qualquer Bearer token pode criar/remover índices `2dsphere` (`/log/attribute/{attribute}/index/...`), impactando performance.
|
|
391
|
+
- **Endpoints de fila** (`/v3/action/queue/*`) expõem e permitem **limpar** a fila (DELETE `/queue/clear`) — descarta logs não processados.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 9. Observabilidade e Troubleshooting
|
|
396
|
+
|
|
397
|
+
**Diagnóstico — o módulo está funcionando?**
|
|
398
|
+
- `GET /v3/action/log?player=<id>&orderby=time&reverse=true&max_results=10` — confirma persistência.
|
|
399
|
+
- `GET /v3/action/queue/info` — tamanho da fila e durações; fila crescente indica processamento lento/travado.
|
|
400
|
+
|
|
401
|
+
**Queries úteis (Mongo / aggregate):**
|
|
402
|
+
- Total por jogador na última semana:
|
|
403
|
+
```
|
|
404
|
+
POST /v3/action/log/aggregate?published_min=-7d
|
|
405
|
+
[ "{$group:{_id:\"$userId\", total:{$sum:1}}}" ]
|
|
406
|
+
```
|
|
407
|
+
- Frequência diária de uma action:
|
|
408
|
+
```
|
|
409
|
+
GET /v3/action/log/frequency?action=buy&every=1d&time_zone=America/Sao_Paulo
|
|
410
|
+
```
|
|
411
|
+
- Buscar um log específico: `GET /v3/system/action/log/{id}`.
|
|
412
|
+
|
|
413
|
+
**Erros comuns e causas:**
|
|
414
|
+
|
|
415
|
+
| Sintoma | Causa provável |
|
|
416
|
+
|---|---|
|
|
417
|
+
| `400 action ... does not exist` | `actionId` não casa por `_id` nem por nome; ou action de outro tenant |
|
|
418
|
+
| `400 action ... is not active` | `Action.active == false` |
|
|
419
|
+
| `400 player is required` | `userId` ausente e sem player no token |
|
|
420
|
+
| `400 ... not allowed to create new players` | `Account.players_allowed` atingido na auto-criação |
|
|
421
|
+
| `400 you have exced the limit ...` | `a.limit` atingido na janela `every` |
|
|
422
|
+
| Log não aparece / sem achievements | Modo assíncrono: ainda na fila, descartado (campos null) ou perdido em reinício |
|
|
423
|
+
| Recompensa duplicada | Reuso de `_id` (upsert reprocessa via `fireAction`) |
|
|
424
|
+
| Limite `per team` ignorado | `PER_TEAM` não é tratado (§7) |
|
|
425
|
+
|
|
426
|
+
**O que verificar quando algo não funciona:**
|
|
427
|
+
1. A `Action` existe e está ativa nesta gamificação?
|
|
428
|
+
2. O `userId` é um principal válido (jogador ou time)?
|
|
429
|
+
3. Foi enviado com `_id` repetido?
|
|
430
|
+
4. O modo é assíncrono? Cheque `/v3/action/queue/info`.
|
|
431
|
+
5. Os triggers `before_win`/`after_win` da action lançam exceção (interrompem o pipeline)?
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## 10. Exemplos Práticos
|
|
436
|
+
|
|
437
|
+
**Mínimo funcional:**
|
|
438
|
+
```
|
|
439
|
+
POST /v3/action/log
|
|
440
|
+
{ "actionId": "tweet", "userId": "player@funifier.com" }
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Avançado (síncrono, com atributos e geolocalização):**
|
|
444
|
+
```
|
|
445
|
+
POST /v3/action/log?async=false
|
|
49
446
|
{
|
|
50
|
-
"actionId": "
|
|
51
|
-
"userId": "
|
|
447
|
+
"actionId": "checkin",
|
|
448
|
+
"userId": "player@funifier.com",
|
|
449
|
+
"time": "2026-05-20T10:00:00-03:00",
|
|
52
450
|
"attributes": {
|
|
53
|
-
"
|
|
54
|
-
"price": 1200
|
|
451
|
+
"company": "coca-cola",
|
|
452
|
+
"price": 1200,
|
|
453
|
+
"location": { "type": "Point", "coordinates": [-15.797922, -47.887791] }
|
|
55
454
|
}
|
|
56
455
|
}
|
|
57
456
|
```
|
|
457
|
+
Retorna `{ action, achievements }` — útil para ver imediatamente o que a ação desencadeou.
|
|
458
|
+
|
|
459
|
+
**Lote:**
|
|
460
|
+
```
|
|
461
|
+
POST /v3/action/log/bulk
|
|
462
|
+
[ { "actionId": "sell", "userId": "jerry", "attributes": { "price": 1200 } },
|
|
463
|
+
{ "actionId": "sell", "userId": "tom", "attributes": { "price": 25 } } ]
|
|
464
|
+
```
|
|
58
465
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
466
|
+
**Anti-pattern — reenviar com `_id` fixo "para atualizar":**
|
|
467
|
+
```
|
|
468
|
+
POST /v3/action/log
|
|
469
|
+
{ "_id": "fixed-123", "actionId": "buy", "userId": "ana", "attributes": { "price": 10 } }
|
|
470
|
+
```
|
|
471
|
+
❌ Por quê: `save` faz upsert e `fireAction` roda de novo → recompensas podem ser **duplicadas**. Action log é um evento imutável; nunca reutilize `_id` para "editar".
|
|
62
472
|
|
|
63
|
-
**
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"actionId": "sell",
|
|
68
|
-
"userId": "jerry",
|
|
69
|
-
"attributes": { "product": "bike", "price": 1200 }
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
"actionId": "sell",
|
|
73
|
-
"userId": "tom",
|
|
74
|
-
"attributes": { "product": "book", "price": 25 }
|
|
75
|
-
}
|
|
76
|
-
]
|
|
473
|
+
**Anti-pattern — confiar no 201 assíncrono como confirmação:**
|
|
474
|
+
```
|
|
475
|
+
POST /v3/action/log // async (default)
|
|
476
|
+
→ 201, mas o log pode falhar/ser descartado/perder-se na fila
|
|
77
477
|
```
|
|
478
|
+
❌ Para confirmação de processamento, use `async=false` ou consulte depois via `GET /v3/action/log`.
|
|
479
|
+
|
|
480
|
+
**Anti-pattern — usar `/v3/system/action/log` esperando gamificação:**
|
|
481
|
+
❌ Esse endpoint grava o documento mas **não** dispara desafios/triggers/achievements. Para gamificar, use `/v3/action/log`.
|
|
78
482
|
|
|
79
|
-
|
|
80
|
-
**Método:** DELETE
|
|
81
|
-
**Endpoint:** `/v3/action/log/:id`
|
|
483
|
+
---
|
|
82
484
|
|
|
83
|
-
##
|
|
485
|
+
## Checklist de Configuração
|
|
84
486
|
|
|
85
|
-
- [ ]
|
|
86
|
-
- [ ]
|
|
87
|
-
- [ ]
|
|
88
|
-
- [ ]
|
|
487
|
+
- [ ] A `Action` referenciada existe e está **ativa** antes de registrar o log.
|
|
488
|
+
- [ ] `userId` é um principal válido (jogador ou time), ou auto-criação está habilitada e dentro de `players_allowed`.
|
|
489
|
+
- [ ] Atributos numéricos usados em limite/regra estão no tipo correto (Number, não String).
|
|
490
|
+
- [ ] Se precisa do resultado imediato (achievements), use `async=false` — lembre que **só a string `"false"`** ativa o modo síncrono.
|
|
491
|
+
- [ ] **Nunca** reutilize `_id` (armadilha: upsert reprocessa e duplica recompensas).
|
|
492
|
+
- [ ] Para limite por time: lembre que `per: "team"` **não é aplicado** — use `per: "player"`/`"gamification"` ou valide fora.
|
|
493
|
+
- [ ] Para queries geoespaciais, crie o índice `2dsphere` via `/v3/action/log/attribute/{attr}/index/create` antes.
|
|
494
|
+
- [ ] Não dependa do retorno 201 assíncrono como garantia de processamento; monitore `/v3/action/queue/info`.
|
|
495
|
+
- [ ] Para inserir log bruto sem gamificação, use `/v3/system/action/log` conscientemente (sem triggers/fireAction).
|