funifier-mcp 0.2.25 → 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 +5 -2
- 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 +1011 -77
- 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/core/api-client.d.ts +21 -1
- package/dist/core/api-client.d.ts.map +1 -1
- package/dist/core/api-client.js +154 -1
- package/dist/core/api-client.js.map +1 -1
- package/dist/core/constants.d.ts +14 -0
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +14 -0
- package/dist/core/constants.js.map +1 -1
- package/dist/core/types/Folder.d.ts +16 -0
- package/dist/core/types/Folder.d.ts.map +1 -0
- package/dist/core/types/Folder.js +3 -0
- package/dist/core/types/Folder.js.map +1 -0
- package/dist/core/types/FolderContent.d.ts +10 -0
- package/dist/core/types/FolderContent.d.ts.map +1 -0
- package/dist/core/types/FolderContent.js +3 -0
- package/dist/core/types/FolderContent.js.map +1 -0
- package/dist/core/types/FolderContentType.d.ts +10 -0
- package/dist/core/types/FolderContentType.d.ts.map +1 -0
- package/dist/core/types/FolderContentType.js +3 -0
- package/dist/core/types/FolderContentType.js.map +1 -0
- package/dist/core/types/FolderLog.d.ts +11 -0
- package/dist/core/types/FolderLog.d.ts.map +1 -0
- package/dist/core/types/FolderLog.js +3 -0
- package/dist/core/types/FolderLog.js.map +1 -0
- package/dist/core/types/index.d.ts +4 -0
- package/dist/core/types/index.d.ts.map +1 -1
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/index.js.map +1 -1
- package/dist/mcp/bundle.js +121 -87
- package/dist/mcp/check-update.d.ts +2 -0
- package/dist/mcp/check-update.d.ts.map +1 -0
- package/dist/mcp/check-update.js +44 -0
- package/dist/mcp/check-update.js.map +1 -0
- package/dist/mcp/index.js +5 -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/_char-guard.js +1 -1
- package/dist/mcp/tools/_char-guard.js.map +1 -1
- package/dist/mcp/tools/_fetch-current.d.ts +1 -1
- package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
- package/dist/mcp/tools/_fetch-current.js +12 -0
- package/dist/mcp/tools/_fetch-current.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 +33 -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 +4 -0
- package/dist/mcp/tools/folder.d.ts.map +1 -0
- package/dist/mcp/tools/folder.js +68 -0
- package/dist/mcp/tools/folder.js.map +1 -0
- 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 +5 -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 +26 -4
- package/dist/mcp/tools/save.js.map +1 -1
- package/dist/mcp/tools/save.test.js +192 -1
- 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/datasource-funifier-docs/.search-index.json +0 -17318
- package/datasource-funifier-docs/.skills-map.json +0 -73
- 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,42 +1,628 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `widget`
|
|
2
2
|
|
|
3
3
|
**Acesso Studio:** `/studio/widget`
|
|
4
4
|
**API Endpoint:** `/v3/widget`
|
|
5
|
+
**Coleção MongoDB:** `widget_v3` (instâncias e catálogo) + `widget_log` (registro de consumo)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
> Documentação de engenharia reversa baseada no código real de `funifier-service`. O módulo `/v3/widget` é implementado por `Widget_V3` / `WidgetManagerV3` / `WidgetManagerSystem` (pacote `com.funifier.engine.octalysis`). **Não confundir** com o subsistema legado de widgets (`engine.widget.Widget`, `engine.integration.html.WidgetHtml`, `engine.studio.widget`) que serve os endpoints legados de injeção HTML inline — esse é outro módulo, descrito na seção 7.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
---
|
|
9
10
|
|
|
10
|
-
##
|
|
11
|
+
## 1. Visão Geral
|
|
11
12
|
|
|
12
|
-
-
|
|
13
|
-
- Para mostrar lista de desafios ativos
|
|
14
|
-
- Para exibir status do jogador
|
|
15
|
-
- Para integrar gamificação visualmente em sistemas existentes
|
|
13
|
+
O módulo `widget` armazena **componentes de UI da gamificação**: blocos auto-contidos de HTML + CSS + JavaScript, com internacionalização (`i18n`) e recursos externos (`resources`), que um frontend consome para renderizar uma técnica de jogo (ranking, status do jogador, lista de desafios, loja, etc.).
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
Cada documento `Widget_V3` carrega quatro tipos de informação:
|
|
18
16
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
17
|
+
- **Apresentação:** `html`, `css`, `script`, `resources` (URIs de JS/CSS externos), `i18n`.
|
|
18
|
+
- **Metadados de catálogo:** `title`, `description`, `image` (ícone), `screenshot`.
|
|
19
|
+
- **Vínculo de gamificação:** `techniques` (códigos de técnica de jogo "GT…") e `references` (ligações a outros componentes da estratégia).
|
|
20
|
+
- **Estilo de grafo:** `style` (`background`, `visible`) — usado na visualização de relacionamento de componentes.
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
Papel arquitetural:
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
**
|
|
28
|
-
**
|
|
24
|
+
- Existe em **dois escopos**, ambos na coleção `widget_v3`, mas em bancos diferentes:
|
|
25
|
+
- **Catálogo do sistema** (global, singleton `SystemFactory` → `WidgetManagerSystem`): widgets prontos, instaláveis por qualquer gamificação.
|
|
26
|
+
- **Instâncias do tenant** (por `api_key`, `ManagerFactory` → `WidgetManagerV3`): a cópia editável de cada gamificação.
|
|
27
|
+
- `POST /v3/widget/install/{id}` copia um widget do catálogo do sistema para o tenant.
|
|
28
|
+
- É um **repositório de conteúdo passivo**: o save é um upsert de substituição total por `_id`; o módulo **não dispara triggers, webhooks ou notificações** em nenhuma operação de escrita ou exclusão.
|
|
29
|
+
- Registra consumo em `widget_log` (`GET /v3/widget/{id}?player=…`), agregado por jogador + widget + **dia**.
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
**Método:** POST
|
|
32
|
-
**Endpoint:** `/v3/widget`
|
|
31
|
+
Relação com outros módulos:
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
**
|
|
36
|
-
|
|
33
|
+
- `inline` (`InlineRest`) — serve `widget.script` e `widget.css` como arquivos `.js`/`.css` para embute em páginas externas.
|
|
34
|
+
- `game-technique` (`GameTechniqueManager`) — lê `widget_log` + `widget_v3.techniques` para calcular o **uso de cada técnica de jogo**; e usa `widget.style` / `widget.references` para montar o grafo de relacionamento de componentes (`findComponentsRelationship`).
|
|
35
|
+
- `analytics` / `statistic` — `widget_log` entra na contagem de **jogadores ativos** (`StatisticManager`) e na contagem bruta de eventos (`AnalyticsRest`).
|
|
36
|
+
- `packages` (`PackageManager`) — widgets são exportados/importados como componentes (`type = widget_v3`).
|
|
37
|
+
- `player` (`PlayerDaoMongo`) — exclusão de um jogador remove seus registros em `widget_log`.
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
---
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
## 2. Arquitetura e Fluxos
|
|
42
|
+
|
|
43
|
+
### 2.1 Classes envolvidas
|
|
44
|
+
|
|
45
|
+
| Classe | Papel |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `com.funifier.engine.octalysis.Widget_V3` | Entidade/POJO — documento raiz da coleção `widget_v3` |
|
|
48
|
+
| `com.funifier.engine.octalysis.WidgetManagerV3` | Manager do **tenant**: insert, find, findAll, delete, findAllAggregate, **saveLog** |
|
|
49
|
+
| `com.funifier.engine.octalysis.WidgetManagerSystem` | Manager do **sistema** (catálogo global): insert, find, findAll, delete (sem saveLog, sem aggregate) |
|
|
50
|
+
| `com.funifier.engine.octalysis.WidgetLog` | Entidade de consumo — coleção `widget_log` |
|
|
51
|
+
| `com.funifier.rest.v3.rest.WidgetRest` | Controller REST v3 (`/v3/widget`) |
|
|
52
|
+
| `com.funifier.rest.v3.rest.InlineRest` | Serve `script`/`css` do widget como arquivos estáticos (`/v3/inline/widget-…`) |
|
|
53
|
+
| `com.funifier.engine.integration.trigger.ObjectStyle` | Sub-objeto `style` (`background`, `visible`) |
|
|
54
|
+
| `com.funifier.engine.integration.trigger.Reference` | Sub-objeto de `references` (ligação a outro componente) |
|
|
55
|
+
|
|
56
|
+
Não há `Repository`/`Dao` dedicado para `Widget_V3` — a manipulação MongoDB é feita diretamente via `Jongo` dentro dos managers. (As classes `WidgetDaoMongo` que existem no projeto pertencem ao subsistema **legado**, não ao `/v3/widget` — ver seção 7.)
|
|
57
|
+
|
|
58
|
+
### 2.2 Pipeline de escrita — `WidgetManagerV3.insert(Widget_V3)`
|
|
59
|
+
|
|
60
|
+
Caminho de `POST /v3/widget`, `POST /v3/widget/install/{id}`, import de package e `POST /v3/widget/system` (via `WidgetManagerSystem.insert`, que é idêntico):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[1] Se widget != null:
|
|
64
|
+
[1.1] se widget.id == null → widget.id = Guid.newShortGuid() (SHORT GUID, não ObjectId)
|
|
65
|
+
[1.2] se widget.created == null → widget.created = new Date()
|
|
66
|
+
[1.3] widget.updated = new Date() (SEMPRE sobrescreve)
|
|
67
|
+
[2] Resolve a coleção Entity.WIDGET_V3.collection ("widget_v3")
|
|
68
|
+
[3] Normalização de i18n: se widget.i18n.size() > 0 → recria o mapa com TODAS as chaves de locale em minúsculas
|
|
69
|
+
[4] c.save(widget) → upsert por _id (SUBSTITUIÇÃO TOTAL do documento)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Comportamento real / armadilhas:**
|
|
73
|
+
|
|
74
|
+
- O passo `[3]` lê `widget.i18n.size()` **fora** do guard `if (widget != null)`. Se `widget` for `null` (corpo vazio) ou se o cliente enviar `"i18n": null` explicitamente, ocorre **`NullPointerException` → HTTP 500**. Ver seção 9.
|
|
75
|
+
- `c.save()` é **substituição total**: campos omitidos no corpo passam a ter o valor default da classe (não há merge/patch). Não existe `PUT`.
|
|
76
|
+
- Nenhuma trigger, webhook ou notificação é disparada. A escrita é silenciosa.
|
|
77
|
+
- `created` é preservado apenas se já vier preenchido no corpo; como o save é full-replace, um update que **não** reenvie `created` recebe `new Date()` (perde a data de criação original).
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
flowchart LR
|
|
81
|
+
A[POST /v3/widget] --> B{widget != null?}
|
|
82
|
+
B -- não --> N[i18n.size NPE → 500]
|
|
83
|
+
B -- sim --> C{id == null?}
|
|
84
|
+
C -- sim --> D[id = ShortGuid]
|
|
85
|
+
C -- não --> E[mantém id]
|
|
86
|
+
D --> F{created == null?}
|
|
87
|
+
E --> F
|
|
88
|
+
F -- sim --> G[created = now]
|
|
89
|
+
F -- não --> H[mantém created]
|
|
90
|
+
G --> I[updated = now]
|
|
91
|
+
H --> I
|
|
92
|
+
I --> J{i18n vazio?}
|
|
93
|
+
J -- não --> K[chaves de locale → minúsculas]
|
|
94
|
+
J -- sim --> L[save upsert por _id]
|
|
95
|
+
K --> L
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2.3 Leitura + registro de consumo — `GET /v3/widget/{id}` e `saveLog`
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
[1] Resolve player: se ausente, em branco ou "me" → authBean.getPlayerFromTokenIfExist()
|
|
102
|
+
[2] manager.find(id) → Widget_V3 (ou null se não existir)
|
|
103
|
+
[3] Se player != null E widget != null → manager.saveLog(id, player)
|
|
104
|
+
[4] Retorna o widget com campos null removidos (JsonUtil.toJsonRemoveNullFields)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`WidgetManagerV3.saveLog(widget, player)` mantém um **contador diário idempotente** por par jogador+widget:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
time = trunca(hoje, "yyyy-MM-dd") // zera hora/min/seg via reparse do formato de data
|
|
111
|
+
current = widget_log.findOne({ player, widget, time })
|
|
112
|
+
se current existe:
|
|
113
|
+
log._id = current._id
|
|
114
|
+
log.total = current.total + 1 // incrementa
|
|
115
|
+
senão:
|
|
116
|
+
log._id = Guid.newShortGuid()
|
|
117
|
+
log.total = 1 // primeiro acesso do dia
|
|
118
|
+
widget_log.save(log) // upsert por _id
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```mermaid
|
|
122
|
+
sequenceDiagram
|
|
123
|
+
participant Client
|
|
124
|
+
participant WidgetRest
|
|
125
|
+
participant WidgetManagerV3
|
|
126
|
+
participant widget_v3 as widget_v3 (Mongo)
|
|
127
|
+
participant widget_log as widget_log (Mongo)
|
|
128
|
+
|
|
129
|
+
Client->>WidgetRest: GET /v3/widget/{id}?player=me
|
|
130
|
+
WidgetRest->>WidgetRest: resolve player (token se "me"/vazio)
|
|
131
|
+
WidgetRest->>WidgetManagerV3: find(id)
|
|
132
|
+
WidgetManagerV3->>widget_v3: findOne({_id:#})
|
|
133
|
+
widget_v3-->>WidgetManagerV3: Widget_V3 | null
|
|
134
|
+
alt player != null E widget != null
|
|
135
|
+
WidgetRest->>WidgetManagerV3: saveLog(id, player)
|
|
136
|
+
WidgetManagerV3->>widget_log: findOne({player, widget, time=hoje})
|
|
137
|
+
widget_log-->>WidgetManagerV3: doc | null
|
|
138
|
+
WidgetManagerV3->>widget_log: save (total+1 ou total=1)
|
|
139
|
+
end
|
|
140
|
+
WidgetRest-->>Client: 200 + widget (campos null removidos)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2.4 Instalação — `POST /v3/widget/install/{id}`
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
[1] widget = SystemFactory.getInstance().getWidgetV3Manager().find(id) // catálogo do sistema
|
|
147
|
+
[2] manager.getWidgetV3Manager().insert(widget) // tenant atual
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- O widget é inserido no tenant **com o mesmo `_id`** do widget de sistema (o `insert` não regenera `id` porque já está preenchido).
|
|
151
|
+
- Como `insert` faz upsert por `_id`, instalar de novo **sobrescreve** silenciosamente a instância local — incluindo edições que o tenant tenha feito.
|
|
152
|
+
- Se `id` **não existe** no catálogo do sistema → `find` retorna `null` → `insert(null)` → **NPE (500)** no passo `[3]` da seção 2.2.
|
|
153
|
+
|
|
154
|
+
### 2.5 Listagem agregada — `WidgetManagerV3.findAllAggregate(aggregations, range)`
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
StringBuilder query = new StringBuilder(); // NUNCA recebe append em nenhum ponto
|
|
158
|
+
...
|
|
159
|
+
se query.length() > 0: → RAMO MORTO (sempre falso)
|
|
160
|
+
senão se aggregations não vazio: → aggregate(aggregations[0]).and(aggregations[1..n])
|
|
161
|
+
senão: → aggregate("{$match:{ }}") // coleção inteira
|
|
162
|
+
return PaginationUtil.getPageResultThrowsException(a, range, 0, 100)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- O primeiro ramo (`query.toString().length() > 0`) é **código morto** — `query` jamais é preenchido.
|
|
166
|
+
- Sem `aggregations` no corpo → roda `{$match:{}}`, ou seja, **todos os widgets do tenant** paginados pelo header `Range` (default `items=0-100`).
|
|
167
|
+
- Os estágios do corpo são repassados **crus** ao MongoDB (mesma superfície de injeção de pipeline da seção 8).
|
|
168
|
+
|
|
169
|
+
### 2.6 Consumo de `widget_log` por outros módulos
|
|
170
|
+
|
|
171
|
+
| Consumidor | O que faz com `widget_log` |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `GameTechniqueManager` (linhas ~822-864) | Agrega `widget_log` por `widget`, faz `$lookup` em `widget_v3` para obter `techniques`, e soma o total por **técnica de jogo** → relatório de uso de técnicas (opcionalmente filtrado por `player`). É a realização concreta do comentário "calcular as motivações que mais influenciam este jogador" do `WidgetRest.find`. |
|
|
174
|
+
| `StatisticManager` (linha ~233) | `$lookup` de `widget_log` unido a `action_log` para o conjunto de **jogadores ativos** (`$setUnion`). |
|
|
175
|
+
| `AnalyticsRest` (linha ~256) | `count()` bruto de documentos em `widget_log`. |
|
|
176
|
+
| `PlayerDaoMongo` (linha ~106) | Ao excluir um jogador, remove `widget_log.remove({player:#})`. |
|
|
177
|
+
| `ConnectionPool` (linha ~149) | Cria índices em `widget_log`: `player`, `widget`, `time`. |
|
|
178
|
+
|
|
179
|
+
> O contador `widget_log` **é lido** (não é dado órfão). Porém, nenhum desses consumidores produz uma "lista de motivações por jogador" pronta — o que existe é a agregação de uso por técnica (`GameTechniqueManager`) e a contribuição para jogadores ativos.
|
|
180
|
+
|
|
181
|
+
### 2.7 Conteúdo servido inline — `InlineRest`
|
|
182
|
+
|
|
183
|
+
| Endpoint | Origem |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `GET /v3/inline/widget-{apikey}-{widget}.js` | `Widget_V3.script` (string crua, `Content-Type: text/javascript`) |
|
|
186
|
+
| `GET /v3/inline/widget-{apikey}-{widget}.css` | `Widget_V3.css` (string crua, `Content-Type: text/css`) |
|
|
187
|
+
|
|
188
|
+
Ambos retornam corpo vazio se o widget não existir ou se o campo estiver vazio/nulo. **Não há autenticação por token** nesses endpoints — o `api_key` vai no path (ver seção 8).
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 3. Estrutura dos Objetos
|
|
193
|
+
|
|
194
|
+
### 3.1 `Widget_V3` — documento raiz (coleção `widget_v3`)
|
|
195
|
+
|
|
196
|
+
`@JsonIgnoreProperties(ignoreUnknown=true)` — campos não mapeados enviados no corpo são **descartados silenciosamente**.
|
|
197
|
+
|
|
198
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
199
|
+
|---|---|---|---|---|
|
|
200
|
+
| `_id` | String | `Guid.newShortGuid()` se ausente | Não (auto) | SHORT GUID interno do Funifier. **Não** é ObjectId do Mongo. Pode ser definido pelo cliente |
|
|
201
|
+
| `title` | String | — | Comentado como "required", **não validado** | Título do widget |
|
|
202
|
+
| `image` | String | — | Não | URL do ícone |
|
|
203
|
+
| `screenshot` | String | — | Não | URL da captura de tela |
|
|
204
|
+
| `description` | String | — | Comentado como "required", **não validado** | Descrição |
|
|
205
|
+
| `extra` | Map<String,Object> | `{}` | Não | Atributos arbitrários. Persistido, mas **não consumido** por nenhuma regra do core (passthrough). Substitui o antigo `tags` (ver abaixo) |
|
|
206
|
+
| `techniques` | List<String> | `[]` | Não | Códigos de técnica de jogo ("GT…") associados. Lista livre, **sem validação** (ver 3.5) |
|
|
207
|
+
| `style` | `ObjectStyle` | `null` | Não | Estilo do nó no grafo de componentes (ver 3.3) |
|
|
208
|
+
| `references` | List<`Reference`> | `null` | Não | Ligações a outros componentes da gamificação (ver 3.4) |
|
|
209
|
+
| `script` | String | — | Não | JavaScript do widget. Servido cru via `/v3/inline/…js` |
|
|
210
|
+
| `html` | String | — | Não | HTML do widget |
|
|
211
|
+
| `css` | String | — | Não | CSS do widget. Servido cru via `/v3/inline/…css` |
|
|
212
|
+
| `resources` | List<String> | `[]` | Não | URIs externos de JS/CSS (ex.: `https://code.angularjs.org/angular-1.0.1.js`) |
|
|
213
|
+
| `i18n` | Map<String, Map<String,String>> | `{}` | Não | Internacionalização. **Chaves de locale são forçadas a minúsculas no save.** Enviar `null` causa NPE (seção 2.2) |
|
|
214
|
+
| `created` | Date | `new Date()` se ausente no insert | Não (auto) | Data de criação. Serializada como epoch ms |
|
|
215
|
+
| `updated` | Date | `new Date()` **sempre** no insert | Não (auto) | Última atualização. Sempre sobrescrita no save |
|
|
216
|
+
|
|
217
|
+
#### Campos computados / não persistidos
|
|
218
|
+
|
|
219
|
+
Nenhum. Todos os campos da classe são persistidos.
|
|
220
|
+
|
|
221
|
+
#### Campos removidos / sanitizados silenciosamente no save
|
|
222
|
+
|
|
223
|
+
- **Chaves de `i18n`** → convertidas para minúsculas (`pt_BR` vira `pt_br`). O cliente não é avisado.
|
|
224
|
+
- **`updated`** → sempre substituído por `new Date()`, ignorando qualquer valor enviado.
|
|
225
|
+
- Campos fora do schema → descartados por `@JsonIgnoreProperties(ignoreUnknown=true)`.
|
|
226
|
+
|
|
227
|
+
#### Campo legado / deprecated
|
|
228
|
+
|
|
229
|
+
```java
|
|
230
|
+
/** Tags related to this widget */
|
|
231
|
+
//public List<String> tags = new ArrayList<>();
|
|
232
|
+
public Map<String, Object> extra = new HashMap<>();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
O campo **`tags`** está **comentado** na entidade (linha 34) e foi substituído por `extra`. Enviar `"tags": [...]` no corpo é aceito pela requisição mas **silenciosamente descartado** (não mapeado → `ignoreUnknown`). Documentos antigos com `tags` no Mongo mantêm o campo, mas o runtime não o lê.
|
|
236
|
+
|
|
237
|
+
### 3.2 `WidgetLog` — registro de consumo (coleção `widget_log`)
|
|
238
|
+
|
|
239
|
+
`@JsonIgnoreProperties(ignoreUnknown=true)`.
|
|
240
|
+
|
|
241
|
+
| Campo | Tipo | Padrão | Descrição |
|
|
242
|
+
|---|---|---|---|
|
|
243
|
+
| `_id` | String | `Guid.newShortGuid()` no primeiro acesso do dia | Identificador |
|
|
244
|
+
| `widget` | String | — | Id do widget consumido |
|
|
245
|
+
| `player` | String | — | Jogador que consumiu |
|
|
246
|
+
| `total` | double | 1 no primeiro acesso, incrementa +1 | Quantidade de acessos **no dia** |
|
|
247
|
+
| `time` | Date | hoje truncado a `yyyy-MM-dd` | Bucket diário (hora zerada) |
|
|
248
|
+
|
|
249
|
+
Um documento por (`player`, `widget`, `dia`). Não há histórico por requisição — apenas o agregado diário. Índices: `player`, `widget`, `time`.
|
|
250
|
+
|
|
251
|
+
### 3.3 `ObjectStyle` — sub-objeto `style`
|
|
252
|
+
|
|
253
|
+
| Campo | Tipo | Descrição |
|
|
254
|
+
|---|---|---|
|
|
255
|
+
| `background` | String | Cor de fundo do nó no grafo de componentes |
|
|
256
|
+
| `visible` | Boolean | Se o nó é exibido no grafo |
|
|
257
|
+
|
|
258
|
+
Usado exclusivamente por `GameTechniqueManager.findComponentsRelationship` para desenhar o widget no grafo de relacionamento. Não afeta a renderização do widget em si.
|
|
259
|
+
|
|
260
|
+
### 3.4 `Reference` — sub-objeto de `references`
|
|
261
|
+
|
|
262
|
+
| Campo | Tipo | Descrição |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `way` | String | `"from"` (`WAY_FROM`) ou `"to"` (`WAY_TO`) — direção da ligação |
|
|
265
|
+
| `linkLabel` | String | Texto sobre a seta de conexão (vazio se ausente) |
|
|
266
|
+
| `_id` | String | Id do componente referenciado |
|
|
267
|
+
| `type` | String | Tipo/coleção do componente referenciado (ex.: `point_category`) |
|
|
268
|
+
| `title` | String | Título do componente referenciado |
|
|
269
|
+
|
|
270
|
+
No grafo: `way=from` → seta do componente **para** o widget (consumo); `way=to` → seta do widget **para** o componente (produção).
|
|
271
|
+
|
|
272
|
+
### 3.5 Técnicas de jogo (`techniques`)
|
|
273
|
+
|
|
274
|
+
`techniques` é uma `List<String>` livre de códigos "GT…". **Não há validação** contra um catálogo — qualquer string é aceita e persistida. Os códigos canônicos vivem no módulo `game-technique` (`com.funifier.engine.system.technique.GameTechnique`), não no widget.
|
|
275
|
+
|
|
276
|
+
Códigos observados no código (`GameTechniqueManager`, atribuídos a outros componentes; servem de referência para `techniques`):
|
|
277
|
+
|
|
278
|
+
| Código | Significado operacional |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `GT01` | Pontos (point_category) |
|
|
281
|
+
| `GT03` | Leaderboard |
|
|
282
|
+
| `GT05` | Blank fills (question) |
|
|
283
|
+
| `GT08` | Virtual good (catalog_item) |
|
|
284
|
+
| `GT13` | Widget (exemplo do apidoc de `WidgetRest`) |
|
|
285
|
+
| `GT26` | Elitism (competition) |
|
|
286
|
+
| `GT35` | Challenge |
|
|
287
|
+
| `GT53` | Last mile |
|
|
288
|
+
| `GT74` | Lottery |
|
|
289
|
+
| `GT85` | Level |
|
|
290
|
+
|
|
291
|
+
> O `$lookup` de `widget_log` → `widget_v3.techniques` (seção 2.6) só contabiliza uso de técnica para widgets cujo `techniques` esteja preenchido. Widget sem `techniques` não aparece no relatório de uso de técnicas.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 4. Endpoints
|
|
296
|
+
|
|
297
|
+
Todos sob `@Path("v3/widget")`, `Produces: application/json; charset=UTF-8`. Autenticação: **Bearer token** (OAuth 2.0 `client_credentials`), exceto os endpoints de `InlineRest` (path com `api_key`).
|
|
298
|
+
|
|
299
|
+
### 4.1 `GET /v3/widget/{id}`
|
|
300
|
+
|
|
301
|
+
| Aspecto | Detalhe |
|
|
302
|
+
|---|---|
|
|
303
|
+
| Finalidade | Buscar um widget e **registrar consumo** |
|
|
304
|
+
| Autenticação | Bearer token |
|
|
305
|
+
| Patch ou full | Leitura |
|
|
306
|
+
|
|
307
|
+
**Path / Query params:**
|
|
308
|
+
|
|
309
|
+
| Param | Tipo | Descrição |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `id` | path | Id do widget |
|
|
312
|
+
| `player` | query | Jogador que consumiu. `me`, vazio ou ausente → resolvido pelo token. Se resolver para não-nulo, dispara `saveLog` |
|
|
313
|
+
|
|
314
|
+
**Comportamento real:** retorna o widget com campos `null` removidos. Se o id não existir, retorna `200` com corpo `null` (não 404). `saveLog` só ocorre se `player` e widget forem não-nulos.
|
|
315
|
+
|
|
316
|
+
### 4.2 `GET /v3/widget`
|
|
317
|
+
|
|
318
|
+
| Aspecto | Detalhe |
|
|
319
|
+
|---|---|
|
|
320
|
+
| Finalidade | Listar **todos** os widgets do tenant |
|
|
321
|
+
| Autenticação | Bearer token |
|
|
322
|
+
|
|
323
|
+
Sem paginação e sem filtros — `findAll()` materializa a coleção inteira em memória (`IteratorUtils.toList`). Campos `null` removidos na resposta.
|
|
324
|
+
|
|
325
|
+
### 4.3 `POST /v3/widget/aggregate`
|
|
326
|
+
|
|
327
|
+
| Aspecto | Detalhe |
|
|
328
|
+
|---|---|
|
|
329
|
+
| Finalidade | Aggregation pipeline arbitrário sobre `widget_v3` |
|
|
330
|
+
| Autenticação | Bearer token |
|
|
331
|
+
| Body | `List<Object>` — estágios do pipeline |
|
|
332
|
+
| Paginação | Header `Range` (default `items=0-100`, máx. 100) |
|
|
333
|
+
|
|
334
|
+
Corpo vazio → `{$match:{}}` (coleção inteira). Ver ramo morto na seção 2.5 e injeção na seção 8.
|
|
335
|
+
|
|
336
|
+
### 4.4 `POST /v3/widget`
|
|
337
|
+
|
|
338
|
+
| Aspecto | Detalhe |
|
|
339
|
+
|---|---|
|
|
340
|
+
| Finalidade | Criar/substituir um widget do tenant |
|
|
341
|
+
| Autenticação | Bearer token |
|
|
342
|
+
| Patch ou full | **Full replace** (`save` upsert por `_id`) |
|
|
343
|
+
|
|
344
|
+
**Comportamento real:** gera `_id` se ausente; define `created`/`updated`; força chaves de `i18n` a minúsculas; **NPE 500 se o corpo for vazio ou `i18n: null`**; **não** dispara triggers/webhooks; **não** valida `title`/`description`. Retorna `201 Created` com o widget serializado.
|
|
345
|
+
|
|
346
|
+
### 4.5 `POST /v3/widget/install/{id}`
|
|
347
|
+
|
|
348
|
+
| Aspecto | Detalhe |
|
|
349
|
+
|---|---|
|
|
350
|
+
| Finalidade | Copiar um widget do **catálogo do sistema** para o tenant |
|
|
351
|
+
| Autenticação | Bearer token |
|
|
352
|
+
|
|
353
|
+
Reusa o `_id` de origem → **sobrescreve** instância local de mesmo id. Id inexistente no sistema → **NPE 500**. Retorna `201` com o widget instalado.
|
|
354
|
+
|
|
355
|
+
### 4.6 `DELETE /v3/widget/{id}`
|
|
356
|
+
|
|
357
|
+
| Aspecto | Detalhe |
|
|
358
|
+
|---|---|
|
|
359
|
+
| Finalidade | Remover um widget do tenant |
|
|
360
|
+
| Autenticação | Bearer token |
|
|
361
|
+
|
|
362
|
+
`remove({_id:#})`. Retorna `200` **sempre**, inclusive quando o id não existe (cliente não distingue "removi" de "não havia nada"). Não remove `widget_log` associado nem dispara triggers.
|
|
363
|
+
|
|
364
|
+
### 4.7 Endpoints de sistema (catálogo global)
|
|
365
|
+
|
|
366
|
+
| Método | Path | Comportamento |
|
|
367
|
+
|---|---|---|
|
|
368
|
+
| `GET` | `/v3/widget/system/{id}` | Busca no catálogo do sistema |
|
|
369
|
+
| `GET` | `/v3/widget/system` | Lista todo o catálogo do sistema |
|
|
370
|
+
| `POST` | `/v3/widget/system` | Cria/substitui no catálogo do sistema |
|
|
371
|
+
| `DELETE` | `/v3/widget/system/{id}` | Remove do catálogo do sistema |
|
|
372
|
+
|
|
373
|
+
Operam via `SystemFactory.getInstance().getWidgetV3Manager()` (`WidgetManagerSystem`) → banco **global**, compartilhado por todos os tenants. **Só exigem autenticação (Bearer), sem guarda de papel/permissão** — ver seção 8.
|
|
374
|
+
|
|
375
|
+
### 4.8 Endpoints inline (`InlineRest`)
|
|
376
|
+
|
|
377
|
+
| Método | Path | Produz |
|
|
378
|
+
|---|---|---|
|
|
379
|
+
| `GET` | `/v3/inline/widget-{apikey}-{widget}.js` | `text/javascript` (campo `script`) |
|
|
380
|
+
| `GET` | `/v3/inline/widget-{apikey}-{widget}.css` | `text/css` (campo `css`) |
|
|
381
|
+
|
|
382
|
+
Sem token; `api_key` no path. Corpo vazio se widget/campo ausente.
|
|
383
|
+
|
|
384
|
+
### Exemplo de request/response
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
POST /v3/widget
|
|
388
|
+
Authorization: Bearer eyJhbGciOi...
|
|
389
|
+
Content-Type: application/json
|
|
390
|
+
{
|
|
391
|
+
"_id": "MY",
|
|
392
|
+
"title": "My Widget",
|
|
393
|
+
"description": "Display my widget",
|
|
394
|
+
"techniques": ["GT13"],
|
|
395
|
+
"html": "<div>My Widget</div>",
|
|
396
|
+
"css": "",
|
|
397
|
+
"script": "",
|
|
398
|
+
"resources": [],
|
|
399
|
+
"i18n": {}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
```json
|
|
404
|
+
HTTP/1.1 201 Created
|
|
405
|
+
{
|
|
406
|
+
"_id": "MY",
|
|
407
|
+
"title": "My Widget",
|
|
408
|
+
"description": "Display my widget",
|
|
409
|
+
"techniques": ["GT13"],
|
|
410
|
+
"html": "<div>My Widget</div>",
|
|
411
|
+
"resources": [],
|
|
412
|
+
"i18n": {},
|
|
413
|
+
"extra": {},
|
|
414
|
+
"created": 1685011053000,
|
|
415
|
+
"updated": 1685011053000
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 5. Regras de Negócio
|
|
422
|
+
|
|
423
|
+
Regras presentes no código mas **ausentes do schema**:
|
|
424
|
+
|
|
425
|
+
### 5.1 Multi-tenant por conexão, não por campo
|
|
426
|
+
|
|
427
|
+
Não há `apiKey` no documento. O isolamento é **inteiramente** dado pela conexão Jongo (`manager.getJongoConnection()`). Tenant ↔ banco. O catálogo do **sistema** é um banco à parte (singleton `SystemFactory`), comum a todos.
|
|
428
|
+
|
|
429
|
+
### 5.2 Save é substituição total (não há PUT/patch)
|
|
430
|
+
|
|
431
|
+
Toda escrita é `c.save()` (upsert por `_id`). Atualizar = reenviar o documento inteiro. Campos omitidos voltam ao default — inclusive `created`, que é regravado com a data atual quando não reenviado.
|
|
432
|
+
|
|
433
|
+
### 5.3 Sem validação de campos "obrigatórios"
|
|
434
|
+
|
|
435
|
+
`title` e `description` têm comentário "(required)" no código, mas **não há validação**. É possível criar um widget vazio (desde que o corpo não seja `null` e `i18n` não seja `null`).
|
|
436
|
+
|
|
437
|
+
### 5.4 Normalização de locale
|
|
438
|
+
|
|
439
|
+
Chaves de `i18n` são forçadas a minúsculas no save. `pt_BR` e `pt_br` colidem no mesmo bucket.
|
|
440
|
+
|
|
441
|
+
### 5.5 Contador de consumo é diário e idempotente
|
|
442
|
+
|
|
443
|
+
`saveLog` não cria um registro por acesso — incrementa o `total` do bucket diário (`yyyy-MM-dd`) do par (player, widget). Acessos no mesmo dia somam no mesmo documento.
|
|
444
|
+
|
|
445
|
+
### 5.6 Instalação reusa o id de origem
|
|
446
|
+
|
|
447
|
+
`install` mantém o `_id` do widget de sistema, então reinstalar **sobrescreve** a instância editada do tenant.
|
|
448
|
+
|
|
449
|
+
### 5.7 Nenhum efeito colateral de gamificação na escrita
|
|
450
|
+
|
|
451
|
+
Criar/editar/excluir widget **não** dispara triggers (`before_create`/`after_create`/`before_save`/`after_save`), webhooks nem notificações — diferentemente de `achievement`/`action`. Widget é conteúdo passivo.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 6. Comportamentos Automáticos
|
|
456
|
+
|
|
457
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
458
|
+
|---|---|---|---|
|
|
459
|
+
| Atribuição de `_id` (ShortGuid) | `insert` se `_id` é null | Cria id curto interno | Sim |
|
|
460
|
+
| `created = new Date()` | `insert` se `created` é null | Data de criação automática | Sim |
|
|
461
|
+
| `updated = new Date()` | `insert` (sempre) | Sobrescreve qualquer `updated` enviado | Sim |
|
|
462
|
+
| Locale → minúsculas | `insert` se `i18n` não vazio | Reescreve chaves de `i18n` | Sim |
|
|
463
|
+
| Incremento de `widget_log` | `GET /v3/widget/{id}` com player resolvido | Cria/incrementa bucket diário | Sim |
|
|
464
|
+
| Sobrescrita na instalação | `POST /install/{id}` | Substitui instância de mesmo id | Sim |
|
|
465
|
+
| Criação de índices `widget_log` | Bootstrap (`ConnectionPool`) | Índices em player/widget/time | Sim |
|
|
466
|
+
| Exclusão de `widget_log` ao remover player | `PlayerDaoMongo` ao excluir jogador | Limpa consumo do jogador | Sim |
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 7. Suportado vs NÃO Suportado
|
|
471
|
+
|
|
472
|
+
### ✅ Suportado
|
|
473
|
+
|
|
474
|
+
- CRUD por tenant (`GET /{id}`, `GET`, `POST`, `DELETE /{id}`).
|
|
475
|
+
- Catálogo de sistema (`/v3/widget/system…`) e instalação (`/install/{id}`).
|
|
476
|
+
- Aggregation pipeline arbitrário (`POST /aggregate`) com paginação por `Range`.
|
|
477
|
+
- Registro de consumo diário em `widget_log` e seu uso em uso-de-técnica e jogadores-ativos.
|
|
478
|
+
- Conteúdo `script`/`css` servido inline (`InlineRest`).
|
|
479
|
+
- `i18n`, `resources`, `techniques`, `style`, `references`.
|
|
480
|
+
- Export/import via `packages`.
|
|
481
|
+
|
|
482
|
+
### ❌ NÃO Suportado
|
|
483
|
+
|
|
484
|
+
- **Atualização parcial (`PUT`/patch)** — só full replace via `POST`.
|
|
485
|
+
- **Validação de `title`/`description`** — comentados como required, nunca validados.
|
|
486
|
+
- **Triggers/webhooks/notificações** no ciclo de vida do widget — nenhum é disparado.
|
|
487
|
+
- **Guarda de papel nos endpoints de sistema** — qualquer token válido escreve/remove no catálogo global (seção 8).
|
|
488
|
+
- **Campo `tags`** — comentado/removido; enviado é descartado. Use `extra`.
|
|
489
|
+
- **Uso do `extra` pelo core** — é persistido, mas nenhuma regra o lê (passthrough puro).
|
|
490
|
+
- **404 em recurso inexistente** — `GET` retorna `200` com `null`; `DELETE` retorna `200` sempre.
|
|
491
|
+
- **Filtro/paginação no `GET /v3/widget`** — devolve a coleção inteira em memória.
|
|
492
|
+
- **Corpo vazio / `i18n: null`** — provoca `NullPointerException` (500), não erro de validação.
|
|
493
|
+
- **Histórico de consumo por requisição** — `widget_log` agrega por dia; não há granularidade por acesso.
|
|
494
|
+
|
|
495
|
+
#### Subsistema legado de widgets (não é o `/v3/widget`)
|
|
496
|
+
|
|
497
|
+
Existem três artefatos com "Widget" no nome que **não** pertencem a este módulo e atendem os endpoints legados `/2.0.0` de injeção HTML inline:
|
|
498
|
+
|
|
499
|
+
- `com.funifier.engine.widget.Widget` — POJO com `category`/`path`/`image`/`active` (catálogo legado de widgets web/mobile). Gerido por `engine.studio.widget.WidgetManager` (sistema) via `SystemFactory.getWidgetManager()`. Usado por `MobileRest`, `StudioRest` e `InlineRest` legados.
|
|
500
|
+
- `com.funifier.engine.integration.html.WidgetHtml` (implementa `Config`) — vínculo de injeção HTML (`selector`/`page`/`bind`) em páginas externas. Gerido por `engine.widget.WidgetManager` (tenant) via `ManagerFactory.getWidgetManager()`.
|
|
501
|
+
- `WidgetDaoMongo` (em `engine.widget` e `engine.studio.widget`) — DAOs desse subsistema legado.
|
|
502
|
+
|
|
503
|
+
Esses componentes são independentes de `Widget_V3` e da coleção `widget_v3`. Não são manipulados por `/v3/widget`.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## 8. Segurança e Permissões
|
|
508
|
+
|
|
509
|
+
- **Autenticação:** Bearer token (OAuth 2.0 `client_credentials`) em todos os endpoints de `WidgetRest`. `WidgetRest` **não** declara `@RolesAllowed` nem chama `authBean.checkSystemPermission(...)`.
|
|
510
|
+
- **Isolamento por tenant:** garantido pela conexão Jongo por `api_key`. Sem campo `apiKey` no documento.
|
|
511
|
+
- **Catálogo de sistema sem guarda de papel (achado real):** `POST /v3/widget/system` e `DELETE /v3/widget/system/{id}` escrevem no banco **global** (`SystemFactory`) exigindo apenas um token válido — **sem verificação de admin/permissão**. Qualquer cliente autenticado pode criar/sobrescrever/remover widgets do catálogo compartilhado por todos os tenants.
|
|
512
|
+
- **Injeção de pipeline de aggregation:** `POST /v3/widget/aggregate` repassa os estágios do corpo crus ao MongoDB. Restrito ao banco do tenant, mas permite consultas arbitrárias (inclusive `$lookup` em outras coleções do mesmo tenant).
|
|
513
|
+
- **XSS armazenado / execução de JS arbitrário (achado real):** `script`, `html` e `css` são armazenados **sem sanitização** e servidos **verbatim** — `script` como `text/javascript` e `css` como `text/css` via `InlineRest`. Qualquer frontend que injete esse conteúdo numa página executa código sob controle de quem criou o widget. Os endpoints inline (`/v3/inline/widget-{apikey}-{widget}.js|.css`) **não exigem token** (o `api_key` vai no path), expondo o `script`/`css` publicamente a quem conhecer a URL.
|
|
514
|
+
- **Robustez:** corpo nulo ou `i18n: null` derruba o handler com NPE (500) em vez de retornar erro de validação (negação de serviço trivial por entrada malformada).
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## 9. Observabilidade e Troubleshooting
|
|
519
|
+
|
|
520
|
+
### Diagnóstico
|
|
521
|
+
|
|
522
|
+
```
|
|
523
|
+
GET /v3/widget # lista todos os widgets do tenant
|
|
524
|
+
GET /v3/widget/{id} # confere um widget (cuidado: registra consumo se houver player)
|
|
525
|
+
GET /v3/widget/system # confere o catálogo de sistema
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Queries úteis (Mongo, banco do tenant)
|
|
529
|
+
|
|
530
|
+
```js
|
|
531
|
+
// Existe o widget?
|
|
532
|
+
db.widget_v3.findOne({ _id: "MY" })
|
|
533
|
+
|
|
534
|
+
// Consumo de um widget por dia
|
|
535
|
+
db.widget_log.find({ widget: "MY" }).sort({ time: -1 })
|
|
536
|
+
|
|
537
|
+
// Consumo de um jogador hoje
|
|
538
|
+
db.widget_log.find({ player: "joao@empresa.com", time: ISODate("2026-05-20T00:00:00Z") })
|
|
539
|
+
|
|
540
|
+
// Uso por técnica (o que GameTechniqueManager calcula)
|
|
541
|
+
db.widget_log.aggregate([
|
|
542
|
+
{ $group: { _id: "$widget", total: { $sum: "$total" } } },
|
|
543
|
+
{ $lookup: { from: "widget_v3", localField: "_id", foreignField: "_id", as: "p" } },
|
|
544
|
+
{ $unwind: "$p" }, { $unwind: "$p.techniques" },
|
|
545
|
+
{ $group: { _id: "$p.techniques", total: { $sum: "$total" } } }
|
|
546
|
+
])
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Erros comuns e causas
|
|
550
|
+
|
|
551
|
+
| Sintoma | Causa provável |
|
|
552
|
+
|---|---|
|
|
553
|
+
| `500` ao criar widget | Corpo vazio/`null` ou `"i18n": null` → NPE em `insert` (seção 2.2) |
|
|
554
|
+
| `500` ao instalar | `id` não existe no catálogo de sistema → `insert(null)` → NPE |
|
|
555
|
+
| `GET` retorna `null` com `200` | Widget não existe — não há 404 |
|
|
556
|
+
| `DELETE` "funciona" mas nada muda | `remove` retorna `200` mesmo sem documento |
|
|
557
|
+
| Locale do `i18n` "sumiu" | Chave foi convertida para minúsculas no save |
|
|
558
|
+
| `tags` não persistiu | Campo removido/legado — descartado por `ignoreUnknown`. Use `extra` |
|
|
559
|
+
| Widget editado voltou ao padrão | Reinstalação (`/install/{id}`) sobrescreveu a instância |
|
|
560
|
+
| `created` mudou após editar | Update full-replace sem reenviar `created` regrava a data |
|
|
561
|
+
| Widget não aparece no relatório de técnicas | `techniques` vazio, ou nenhum acesso registrado em `widget_log` |
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
## 10. Exemplos Práticos
|
|
566
|
+
|
|
567
|
+
### Mínimo funcional
|
|
568
|
+
|
|
569
|
+
```json
|
|
570
|
+
POST /v3/widget
|
|
571
|
+
{
|
|
572
|
+
"title": "Ranking",
|
|
573
|
+
"html": "<div id='rk'></div>"
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Gera `_id` automático, `created`/`updated` automáticos. (Atenção: **não** envie corpo vazio nem `"i18n": null`.)
|
|
578
|
+
|
|
579
|
+
### Avançado (todos os campos relevantes)
|
|
580
|
+
|
|
581
|
+
```json
|
|
582
|
+
POST /v3/widget
|
|
583
|
+
{
|
|
584
|
+
"_id": "leaderboard_widget",
|
|
585
|
+
"title": "Leaderboard",
|
|
586
|
+
"description": "Top 10 jogadores",
|
|
587
|
+
"image": "https://cdn.exemplo.com/icon.png",
|
|
588
|
+
"screenshot": "https://cdn.exemplo.com/shot.png",
|
|
589
|
+
"techniques": ["GT03"],
|
|
590
|
+
"style": { "background": "#1e293b", "visible": true },
|
|
591
|
+
"references": [
|
|
592
|
+
{ "way": "from", "_id": "lb_principal", "type": "leaderboard", "title": "Ranking Geral", "linkLabel": "exibe" }
|
|
593
|
+
],
|
|
594
|
+
"resources": ["https://code.jquery.com/jquery-3.7.1.min.js"],
|
|
595
|
+
"script": "fetch('/v3/leaderboard/lb_principal/leader/aggregate').then(...)",
|
|
596
|
+
"css": "#rk{font-family:sans-serif}",
|
|
597
|
+
"html": "<div id='rk'></div>",
|
|
598
|
+
"i18n": { "pt_br": { "title": "Ranking" }, "en": { "title": "Leaderboard" } }
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Anti-pattern (o que NÃO fazer)
|
|
603
|
+
|
|
604
|
+
```json
|
|
605
|
+
POST /v3/widget
|
|
606
|
+
{
|
|
607
|
+
"title": "Quebrado",
|
|
608
|
+
"tags": ["ranking", "top"], // ❌ descartado: 'tags' é legado, use 'extra'
|
|
609
|
+
"i18n": null // ❌ NPE 500: i18n nunca pode ser null
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
- Esperar **patch**: reenviar só `{ "title": "Novo" }` num widget existente **apaga** os demais campos (full replace).
|
|
614
|
+
- Confiar em `extra` como dado funcional: ele é persistido mas **nenhuma regra do core o lê**.
|
|
615
|
+
- Usar `POST /v3/widget/system` como conveniência: escreve no catálogo **global** de todos os tenants, sem guarda de papel.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Checklist de Configuração
|
|
620
|
+
|
|
621
|
+
- [ ] Corpo do `POST` **nunca** vazio e `i18n` **nunca** `null` (evita NPE 500).
|
|
622
|
+
- [ ] `title` e `description` preenchidos (o código não valida, mas a UI espera).
|
|
623
|
+
- [ ] Locales de `i18n` já em minúsculas (serão convertidos de qualquer forma).
|
|
624
|
+
- [ ] Para atualizar, reenviar o **documento inteiro** (não há patch); reenviar `created` para preservá-lo.
|
|
625
|
+
- [ ] `techniques` preenchido com códigos GT se o widget deve aparecer no relatório de uso de técnicas.
|
|
626
|
+
- [ ] Conteúdo de `script`/`html`/`css` é confiável — é servido verbatim e executável (XSS).
|
|
627
|
+
- [ ] Escrita em `/v3/widget/system` é intencional (catálogo global, sem guarda de papel).
|
|
628
|
+
- [ ] Reinstalar via `/install/{id}` ciente de que sobrescreve a instância local de mesmo id.
|
package/dist/cli/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AA2LA,wBAA8B,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CzF"}
|