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,180 +1,506 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `studio-page`
|
|
2
2
|
|
|
3
3
|
**Acesso Studio:** `/studio/page`
|
|
4
|
+
**API Endpoint:** `/v3/database/studio_page` (CRUD genérico — **não há** endpoint dedicado `/v3/studio_page`)
|
|
5
|
+
**Coleção MongoDB:** `studio_page`
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
---
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## 1. Visão Geral
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
O módulo `studio-page` representa **páginas customizadas** criadas dentro do Funifier Studio: blocos de `html` + `script` (JavaScript) servidos sob um `slug`, usados por administradores para montar dashboards, CRUDs, gráficos e relatórios próprios sobre os dados da gamificação.
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- `_id` — Identificador único (auto-gerado se omitido)
|
|
13
|
-
- `display` — `true` para aparecer no menu do Studio, `false` para páginas internas
|
|
14
|
-
- `title` — Nome exibido no menu
|
|
15
|
-
- `slug` — Caminho relativo (ex: `studio/custom/hello`). Suporta parâmetros: `studio/custom/car/form/:id`
|
|
16
|
-
- `html` — Código HTML (usa Bootstrap CSS)
|
|
17
|
-
- `script` — Código JavaScript (roda no contexto AngularJS)
|
|
13
|
+
Papel arquitetural — três fatos centrais que governam todo o resto:
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
1. **Não existe Resource/Service/Manager/Repository/DAO dedicado.** A persistência é feita inteiramente pelo CRUD **genérico** `DatabaseRest` (`com.funifier.rest.v3.rest.DatabaseRest`, `@Path("v3/database")`) sobre a coleção `studio_page`. Toda a semântica REST de `studio_page` é, portanto, a semântica genérica de `/v3/database/{collection}`.
|
|
16
|
+
2. **O `DatabaseRest` opera sobre `HashMap`/`BasicDBObject`, nunca sobre a classe `StudioPage`.** A entidade `com.funifier.engine.studio.page.StudioPage` é apenas um **contrato de leitura** — ela só é desserializada num único ponto do servidor (`GameTechniqueManager.findComponentsRelationship`). Na escrita, a coleção aceita **qualquer campo** sem validação de schema.
|
|
17
|
+
3. **O backend trata `html`, `script` e `slug` como strings opacas.** `funifier-service` não interpreta, valida ou renderiza nenhum desses campos. Toda a semântica de AngularJS, do objeto `Marketplace`, das diretivas (`<image-picker>`, etc.) e do roteamento por `slug` é responsabilidade do **frontend do Studio** — ver seção 2.5 e 7.
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
- `$scope` — Scope do AngularJS
|
|
23
|
-
- `$http` — Para requisições HTTP
|
|
24
|
-
- `$location` — Para navegação entre páginas (`$location.path("/studio/custom/...")`)
|
|
25
|
-
- `$routeParams` — Para ler parâmetros do slug (ex: `$routeParams.id`)
|
|
26
|
-
- `Marketplace` — Objeto utilitário (ver abaixo)
|
|
19
|
+
Relação com outros módulos:
|
|
27
20
|
|
|
28
|
-
|
|
21
|
+
- **`database`** — único caminho de CRUD (`DatabaseRest`). Tudo que vale para `/v3/database/{collection}` vale aqui.
|
|
22
|
+
- **`trigger`** — toda escrita em `studio_page` dispara o pipeline genérico de triggers (`before_create`/`after_create`/`before_update`/`after_update`/`before_delete`/`after_delete`).
|
|
23
|
+
- **`audit`** — toda mutação é registrada pelo `AuditManager`.
|
|
24
|
+
- **`technique` (mapa de estratégia)** — `GameTechniqueManager.findComponentsRelationship` (exposto em `GET /v3/technique/relationship`) lê todas as páginas como `StudioPage` e as transforma em nós/arestas do grafo de estratégia, usando `display`, `style`, `title`, `id` e `references`.
|
|
25
|
+
- **`folder`** — o campo `folder` é uma referência organizacional consumida pelo módulo Folder; o código de `studio_page` não o processa.
|
|
26
|
+
- **`ai`** — `AIRest.buildStudioPage` (`POST /v3/ai/build/studio_page`) gera `html`/`script`/`title`/`slug` via OpenAI, mas **não persiste** — apenas devolve o JSON gerado.
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|--------|-----------|-------------------|
|
|
32
|
-
| `Marketplace.auth.getService()` | URL da API Funifier | `https://service2.funifier.com` |
|
|
33
|
-
| `Marketplace.auth.getAuthorization()` | Token de acesso do usuário logado no Studio | Bearer token |
|
|
34
|
-
| `Marketplace.range.parse(content_range)` | Analisa header `content-range` para paginação | `{page, pages, count, ...}` |
|
|
35
|
-
| `Marketplace.range.paginate(page, content_range)` | Gera header `Range` para ir a uma página | `items=0-100` |
|
|
28
|
+
Problemas que resolve: permitir telas administrativas arbitrárias dentro do Studio sem alterar o produto, reutilizando o CRUD genérico de banco como mecanismo de armazenamento.
|
|
36
29
|
|
|
37
|
-
|
|
30
|
+
---
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
- `<principal-picker>` — Seletor de jogadores/equipes. Atributos: `title`, `model`, `show-picker-player`, `black-list`, `max`
|
|
41
|
-
- `<input-extra>` — Campos extras dinâmicos. Atributos: `title`, `show-inline`, `model`
|
|
32
|
+
## 2. Arquitetura e Fluxos
|
|
42
33
|
|
|
43
|
-
|
|
34
|
+
### 2.1 Classes envolvidas
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
| Classe | Papel |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `com.funifier.engine.studio.page.StudioPage` | POJO/contrato de leitura — documento raiz. **Não** é usado na escrita |
|
|
39
|
+
| `com.funifier.engine.util.Entity` | Registro `STUDIO_PAGE("studio_page", StudioPage.class)` (linha 240) — mapeia tipo → coleção/classe |
|
|
40
|
+
| `com.funifier.rest.v3.rest.DatabaseRest` | Controller REST genérico (`/v3/database/{collection}`) que serve **toda** a CRUD de `studio_page` |
|
|
41
|
+
| `com.funifier.engine.technique.GameTechniqueManager` | Único consumidor da classe `StudioPage` no servidor — monta o mapa de relacionamento (`findComponentsRelationship`) |
|
|
42
|
+
| `com.funifier.rest.v3.rest.AIRest` | `buildStudioPage` — geração assistida por IA (`POST /v3/ai/build/studio_page`) |
|
|
43
|
+
| `com.funifier.engine.integration.trigger.ObjectStyle` | Sub-objeto `style` (`background`, `visible`) — estilização de nó no mapa de estratégia |
|
|
44
|
+
| `com.funifier.engine.integration.trigger.Reference` | Item de `references` — aresta no mapa de estratégia |
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
**Não há `Manager`/`Service`/`Dao`/`Repository` dedicado a `studio_page`.** A manipulação do MongoDB é feita diretamente via `Jongo` dentro de `DatabaseRest`.
|
|
47
|
+
|
|
48
|
+
### 2.2 Pipeline de escrita (POST / PUT) — `DatabaseRest.insert` / `DatabaseRest.update`
|
|
49
|
+
|
|
50
|
+
Sequência real para `PUT /v3/database/studio_page` (`update`, linhas 290-388) e `POST` (`insert`, linhas 169-247):
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
[Auth] → verifica authBean.getScope() contém SCOPE_DATABASE ("database")
|
|
54
|
+
(sem scope → retorna 200 com corpo vazio, NÃO 403)
|
|
55
|
+
[Guard] → collection.trim().length() > 3 ("studio_page" = 11, passa)
|
|
56
|
+
[_id] → se "_id" ausente ou null → object.put("_id", Guid.newShortGuid())
|
|
57
|
+
[Trigger BEFORE]→ triggerManager.execute(BEFORE_CREATE | BEFORE_UPDATE, "studio_page", player)
|
|
58
|
+
[Crypt] → se CryptObjectField configurado p/ studio_page → criptografa campos
|
|
59
|
+
[Marshal] → JacksonMapper.marshall(object) → BasicDBObject (modo BSON tipado)
|
|
60
|
+
[Persist] → POST: collection("studio_page").insert(o)
|
|
61
|
+
PUT : collection("studio_page").save(o) ← SUBSTITUI o documento inteiro (upsert por _id)
|
|
62
|
+
[Trigger AFTER] → triggerManager.execute(AFTER_CREATE | AFTER_UPDATE, "studio_page", player)
|
|
63
|
+
[Audit] → auditManager.log("studio_page", EVENT_CREATE | EVENT_UPDATE, authBean, o)
|
|
64
|
+
[Response] → 201 CREATED. strict=true → BSON strict; senão → JSON sem campos null
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pontos não-triviais (todos confirmados no código):
|
|
68
|
+
|
|
69
|
+
- **PUT é _full replace_, não _patch_.** `jongo.save(o)` substitui o documento inteiro. Campos omitidos no corpo **somem** do documento.
|
|
70
|
+
- **PUT sem `_id` cria um documento novo.** Como `_id` é auto-gerado quando ausente, um `PUT` que pretendia atualizar mas esqueceu o `_id` resulta em **duplicata** com id aleatório — não em `404`.
|
|
71
|
+
- **Sem `_id` no POST** → também é auto-gerado via `Guid.newShortGuid()` (SHORT GUID, não ObjectId do Mongo).
|
|
72
|
+
- O fluxo é **síncrono** e **sem transação** entre persistência, triggers e auditoria.
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
sequenceDiagram
|
|
76
|
+
participant C as Cliente (Studio)
|
|
77
|
+
participant R as DatabaseRest
|
|
78
|
+
participant T as TriggerManager
|
|
79
|
+
participant M as MongoDB (Jongo)
|
|
80
|
+
participant A as AuditManager
|
|
81
|
+
C->>R: PUT /v3/database/studio_page {json}
|
|
82
|
+
R->>R: scope contém SCOPE_DATABASE? len(collection) > 3?
|
|
83
|
+
R->>R: _id ausente/null? → Guid.newShortGuid()
|
|
84
|
+
R->>T: execute(BEFORE_UPDATE, "studio_page", player)
|
|
85
|
+
R->>M: collection("studio_page").save(o) // full replace / upsert
|
|
86
|
+
R->>T: execute(AFTER_UPDATE, "studio_page", player)
|
|
87
|
+
R->>A: log("studio_page", EVENT_UPDATE, authBean, o)
|
|
88
|
+
R-->>C: 201 Created {documento}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2.3 Pipeline de leitura
|
|
92
|
+
|
|
93
|
+
- **`GET /v3/database/studio_page/{id}`** (`find`, linhas 59-118): `findOne("{_id:#}", id)`. **Quirk:** se `id == "me"`, o id é resolvido para o jogador do token (`authBean.getPlayerFromTokenIfExist()`) — comportamento herdado do CRUD genérico, sem sentido para páginas. `strict=true` → saída BSON strict (preserva `$date`, `$oid`); senão remove campos null.
|
|
94
|
+
- **`GET /v3/database/studio_page?q=...`** (`findAll`, linhas 120-160): monta um pipeline `{$match:{ <q> }}`, pagina via header `Range` (padrão página de 100; `max_results` default 100) e devolve resultado paginado com header `Content-Range`.
|
|
95
|
+
|
|
96
|
+
### 2.4 Participação no mapa de estratégia — `GameTechniqueManager.findComponentsRelationship`
|
|
97
|
+
|
|
98
|
+
Exposto em **`GET /v3/technique/relationship`** (`GameTechniqueRest`, `@Path("v3/technique")` + `/relationship`). É o **único** lugar do servidor que desserializa `StudioPage`:
|
|
99
|
+
|
|
100
|
+
1. Carrega **todas** as páginas: `jongo.getCollection("studio_page").find().as(StudioPage.class)` (linha 225).
|
|
101
|
+
2. **Nós** (linhas 276-280): para cada página, `if(page.display)` → `addNode(page.id, page.title, page.style)`. `addNode` (linha 667) omite o nó se `style.visible == false`; usa `style.background` como cor.
|
|
102
|
+
3. **Arestas** (linhas 565-588): para cada página com `display == true` e `references` não-vazio, cada `Reference` vira uma aresta:
|
|
103
|
+
- `reference.type == "player"` → liga ao nó `player`; `== "notification"` → nó `notification`; senão usa `reference.id`.
|
|
104
|
+
- `reference.way == "from"` → aresta `referência → página` (consome);
|
|
105
|
+
- `reference.way == "to"` → aresta `página → referência` (produz);
|
|
106
|
+
- rótulo da aresta = `reference.getLinkLabel()`.
|
|
107
|
+
|
|
108
|
+
> ⚠️ **Bug confirmado:** `if(page.display)` (linhas 277 e 566) faz _unboxing_ de `Boolean`. Uma página persistida **sem** o campo `display` (valor `null`) lança `NullPointerException` e **derruba o endpoint inteiro** `GET /v3/technique/relationship` para o tenant — não apenas aquela página. Ver seção 5 e 7.
|
|
109
|
+
|
|
110
|
+
```mermaid
|
|
111
|
+
flowchart LR
|
|
112
|
+
DOCS[coleção studio_page] -->|find as StudioPage| GTM[findComponentsRelationship]
|
|
113
|
+
GTM -->|page.display == true| NODE["addNode(id, title, style)"]
|
|
114
|
+
GTM -->|reference.way = from| LFROM[aresta ref → página]
|
|
115
|
+
GTM -->|reference.way = to| LTO[aresta página → ref]
|
|
116
|
+
NODE --> OUT[nodes + links]
|
|
117
|
+
LFROM --> OUT
|
|
118
|
+
LTO --> OUT
|
|
119
|
+
GTM -.->|display == null| NPE[NullPointerException → 500]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2.5 Geração assistida por IA — `AIRest.buildStudioPage`
|
|
123
|
+
|
|
124
|
+
`POST /v3/ai/build/studio_page` (`AIRest`, `@Path("v3/ai")` + `/build/studio_page`, linha 1130):
|
|
125
|
+
|
|
126
|
+
- Recebe `{ "content": "<pedido>", "script"?, "html"? }`.
|
|
127
|
+
- Garante a existência do prompt `intro_build_studio_page` (cria via `IAManager.insertPrompt` se ausente — o texto do prompt é a própria documentação do recurso embutida no código, linha 1140).
|
|
128
|
+
- Chama OpenAI (modelo `gpt-3.5`) com a function `build_studio_page` que retorna `script`, `html`, `title`, `slug` (todos obrigatórios).
|
|
129
|
+
- **Devolve o JSON gerado e não persiste.** A gravação efetiva continua sendo um `PUT /v3/database/studio_page` feito pelo frontend.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 3. Estrutura dos Objetos
|
|
134
|
+
|
|
135
|
+
### 3.1 `StudioPage` — documento raiz (coleção `studio_page`)
|
|
136
|
+
|
|
137
|
+
`@JsonIgnoreProperties(ignoreUnknown=true)`. **Importante:** essa anotação só atua na **leitura** por `GameTechniqueManager`. Na escrita via `DatabaseRest` (que usa `HashMap`), **campos desconhecidos são persistidos**.
|
|
138
|
+
|
|
139
|
+
| Campo | Tipo | Padrão | Obrigatório | Descrição |
|
|
140
|
+
|---|---|---|---|---|
|
|
141
|
+
| `_id` | String | `Guid.newShortGuid()` se ausente/null | Não (auto) | Identificador SHORT GUID. **Não** é ObjectId do Mongo |
|
|
142
|
+
| `title` | String | — | Não (em runtime) | Nome exibido no menu do Studio e como rótulo do nó no mapa de estratégia |
|
|
143
|
+
| `slug` | String | — | Não (em runtime) | Caminho relativo da página no Studio (ex.: `studio/custom/hello`). String opaca para o backend; suporta parâmetros (`:id`) interpretados **pelo frontend** |
|
|
144
|
+
| `script` | String | — | Não | Código JavaScript da página. **String opaca** — armazenado e executado pelo frontend (AngularJS), nunca pelo backend |
|
|
145
|
+
| `html` | String | — | Não | Código HTML da página. **String opaca** — renderizado pelo frontend (Bootstrap) |
|
|
146
|
+
| `display` | Boolean | — | **Sim, na prática** | `true` = aparece no menu do Studio e vira nó no mapa de estratégia. **Se `null` quebra `GET /v3/technique/relationship`** (ver seção 5) |
|
|
147
|
+
| `created` | Date | — | Não | **Nunca preenchido automaticamente** (ver abaixo) |
|
|
148
|
+
| `updated` | Date | — | Não | **Nunca preenchido automaticamente** (ver abaixo) |
|
|
149
|
+
| `folder` | String | — | Não | Id de pasta (módulo Folder). Referência organizacional; não processado pelo código de `studio_page` |
|
|
150
|
+
| `extra` | Map<String,Object> | `{}` | Não | Atributos arbitrários. Persistido; não consumido pelo backend de `studio_page` (usado pela diretiva frontend `<input-extra>`) |
|
|
151
|
+
| `techniques` | List<String> | `[]` | Não | Códigos de técnica de jogo (GT…). Ver 3.4 |
|
|
152
|
+
| `style` | ObjectStyle | `null` | Não | Estilização do nó no mapa de estratégia (ver 3.2) |
|
|
153
|
+
| `references` | List<Reference> | `null` | Não | Arestas do mapa de estratégia (ver 3.3) |
|
|
154
|
+
|
|
155
|
+
#### Campos computados / não persistidos
|
|
156
|
+
|
|
157
|
+
Nenhum. Todos os campos da entidade são persistidos.
|
|
158
|
+
|
|
159
|
+
#### Campos preenchidos automaticamente
|
|
160
|
+
|
|
161
|
+
**Nenhum** — exceto `_id` (no `DatabaseRest`). Em particular, `created` e `updated` **não são setados por nenhum código** (confirmado por busca em `src/main/java/com/funifier/engine/studio/`). Eles existem na entidade mas só serão gravados se o **cliente** os enviar; caso contrário permanecem `null` para sempre.
|
|
162
|
+
|
|
163
|
+
#### Campos removidos silenciosamente
|
|
164
|
+
|
|
165
|
+
Nenhum na escrita — o `DatabaseRest` persiste o documento como recebido (`HashMap`). Não há sanitização de campos.
|
|
166
|
+
|
|
167
|
+
#### Campos legados / código comentado
|
|
168
|
+
|
|
169
|
+
```java
|
|
170
|
+
// public List<String> tags = new ArrayList<>(); // StudioPage.java linha 45
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- O campo **`tags`** está **comentado** na entidade — não existe em runtime na leitura. Como a escrita é via `HashMap`, um `tags` enviado pelo cliente **é gravado no Mongo**, mas é **silenciosamente ignorado** por `GameTechniqueManager` (não está mapeado no POJO + `ignoreUnknown=true`).
|
|
174
|
+
- Os Javadocs da entidade referem-se a **"widget"** ("*Tags related to this widget*" linha 44; "*All game techniques related to this widget*" linha 48), indicando que `StudioPage` foi **clonada de `Widget_V3`**. `style`, `references`, `techniques` e `extra` são herança desse molde e só fazem sentido no contexto do mapa de estratégia / técnicas de jogo.
|
|
175
|
+
|
|
176
|
+
### 3.2 `ObjectStyle` — sub-objeto `style`
|
|
177
|
+
|
|
178
|
+
`com.funifier.engine.integration.trigger.ObjectStyle`, `@JsonIgnoreProperties(ignoreUnknown=true)`.
|
|
179
|
+
|
|
180
|
+
| Campo | Tipo | Descrição |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| `background` | String | Cor de fundo do nó no mapa de estratégia (`GameTechniqueManager.node`, linha 677) |
|
|
183
|
+
| `visible` | Boolean | `false` → o nó é **omitido** do mapa de estratégia (`addNode`, linha 668). `null`/`true` → visível |
|
|
184
|
+
|
|
185
|
+
### 3.3 `Reference` — item de `references`
|
|
186
|
+
|
|
187
|
+
`com.funifier.engine.integration.trigger.Reference`, `@JsonIgnoreProperties(ignoreUnknown=true)`.
|
|
188
|
+
|
|
189
|
+
| Campo | Tipo | Obrigatório | Descrição |
|
|
190
|
+
|---|---|---|---|
|
|
191
|
+
| `_id` | String | Sim | Id do componente referenciado |
|
|
192
|
+
| `type` | String | Sim | Coleção/tipo do alvo (ex.: `point`, `player`, `notification`, `challenge`). `player`/`notification` recebem tratamento especial nos links |
|
|
193
|
+
| `title` | String | Sim | Título do alvo |
|
|
194
|
+
| `way` | String | — | `"from"` (consome: alvo → página) ou `"to"` (produz: página → alvo). Constantes `Reference.WAY_FROM`/`WAY_TO` |
|
|
195
|
+
| `linkLabel` | String | — | Texto sobre a aresta. `getLinkLabel()` retorna `""` quando vazio |
|
|
196
|
+
|
|
197
|
+
### 3.4 Técnicas de jogo (`techniques`)
|
|
198
|
+
|
|
199
|
+
`techniques` é uma `List<String>` de **códigos de técnica de jogo** (GT…). O backend de `studio_page` **não** define nem consome esses códigos diretamente. Eles só passam a ter efeito **se** existir um `GameTechniqueField` mapeando `entity=studio_page` / `field=techniques` — nesse caso `GameTechniqueManager.findConfiguredTechniques` (linha 701) os agrega na contagem de uso por técnica (com teto de 20 itens por técnica, linha 718).
|
|
200
|
+
|
|
201
|
+
> O catálogo operacional de cada código GT vive na coleção `technique` (`Entity.GAME_TECHNIQUE`), **não** é hard-coded para `studio_page`. Esta documentação não enumera os códigos para não inventar comportamento.
|
|
202
|
+
|
|
203
|
+
### 3.5 Relações entre objetos
|
|
204
|
+
|
|
205
|
+
```mermaid
|
|
206
|
+
erDiagram
|
|
207
|
+
STUDIO_PAGE ||--o| OBJECT_STYLE : "style"
|
|
208
|
+
STUDIO_PAGE ||--o{ REFERENCE : "references"
|
|
209
|
+
REFERENCE }o--|| COMPONENTE : "type + _id (player/notification/...)"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 4. Endpoints
|
|
215
|
+
|
|
216
|
+
Todos abaixo são do CRUD genérico `DatabaseRest` com `{collection}` = `studio_page`. **Autenticação:** `Authorization: Bearer <token>`; o token precisa do scope **`database`** (`Application.SCOPE_DATABASE`). Sem o scope, o endpoint retorna **`200` com corpo vazio/null** (silencioso) — não `403`.
|
|
217
|
+
|
|
218
|
+
### 4.1 `POST /v3/database/studio_page` — criar
|
|
219
|
+
|
|
220
|
+
| Aspecto | Detalhe |
|
|
221
|
+
|---|---|
|
|
222
|
+
| Finalidade | Inserir nova página |
|
|
223
|
+
| Full replace ou patch | Insert direto |
|
|
224
|
+
| `_id` | Auto-gerado se ausente/null (`Guid.newShortGuid()`) |
|
|
225
|
+
| Side effects | Triggers `before_create`/`after_create`; auditoria `EVENT_CREATE` |
|
|
226
|
+
| Resposta | `201 CREATED` com o documento |
|
|
227
|
+
| Query params | `strict=true` → BSON strict na resposta |
|
|
228
|
+
|
|
229
|
+
### 4.2 `PUT /v3/database/studio_page` — criar ou substituir
|
|
230
|
+
|
|
231
|
+
| Aspecto | Detalhe |
|
|
232
|
+
|---|---|
|
|
233
|
+
| Finalidade | Substituir página existente (upsert por `_id`) |
|
|
234
|
+
| Full replace ou patch | **FULL REPLACE** (`jongo.save`) — campos omitidos são perdidos |
|
|
235
|
+
| `_id` | Auto-gerado se ausente → **cria duplicata** em vez de atualizar |
|
|
236
|
+
| Side effects | Triggers `before_update`/`after_update`; auditoria `EVENT_UPDATE` |
|
|
237
|
+
| Resposta | `201 CREATED` com o documento |
|
|
238
|
+
|
|
239
|
+
### 4.3 `GET /v3/database/studio_page/{id}` — buscar por id
|
|
240
|
+
|
|
241
|
+
| Aspecto | Detalhe |
|
|
242
|
+
|---|---|
|
|
243
|
+
| Finalidade | `findOne({_id:id})` |
|
|
244
|
+
| Quirk | `id == "me"` → resolve para o jogador do token |
|
|
245
|
+
| Query params | `strict=true` → BSON strict; senão remove campos null |
|
|
246
|
+
| Guard | `collection.length > 3` e `id` presente |
|
|
247
|
+
|
|
248
|
+
### 4.4 `GET /v3/database/studio_page` — listar / consultar
|
|
249
|
+
|
|
250
|
+
| Aspecto | Detalhe |
|
|
251
|
+
|---|---|
|
|
252
|
+
| Finalidade | `{$match:{ <q> }}` paginado |
|
|
253
|
+
| `q` (query) | Filtro MongoDB cru (ex.: `display:true`) — concatenado direto (ver seção 8) |
|
|
254
|
+
| `Range` (header) | Paginação (ex.: `items=0-100`); padrão página de 100 |
|
|
255
|
+
| `max_results` | Default 100 |
|
|
256
|
+
| `strict` | `true` → BSON strict |
|
|
257
|
+
| Resposta | Lista paginada + header `Content-Range` |
|
|
258
|
+
|
|
259
|
+
### 4.5 `DELETE /v3/database/studio_page?q=...` — excluir
|
|
260
|
+
|
|
261
|
+
| Aspecto | Detalhe |
|
|
262
|
+
|---|---|
|
|
263
|
+
| Finalidade | `remove({ <q> })` |
|
|
264
|
+
| Guard | `q.length >= 3` (sem `q` válido, **não apaga nada**) |
|
|
265
|
+
| Side effects | Coleta ids (se `count <= 20000`) e dispara triggers `before_delete`/`after_delete`; auditoria `EVENT_DELETE` |
|
|
266
|
+
| Resposta | `200 OK`, corpo null |
|
|
267
|
+
|
|
268
|
+
### 4.6 Demais operações genéricas (aplicam-se à coleção)
|
|
269
|
+
|
|
270
|
+
`POST /v3/database/studio_page/bulk` (insert em lote, `async=true` opcional), `POST /v3/database/studio_page/aggregate` (pipeline de agregação), `POST|DELETE|GET /v3/database/studio_page/index` (índices), `GET /v3/database/count?collection=studio_page&q=...`, `DELETE /v3/database/studio_page/drop`.
|
|
271
|
+
|
|
272
|
+
### 4.7 `POST /v3/ai/build/studio_page` — geração por IA
|
|
273
|
+
|
|
274
|
+
Gera `script`/`html`/`title`/`slug` via OpenAI. **Não persiste** (ver 2.5).
|
|
275
|
+
|
|
276
|
+
**Exemplo de request (PUT):**
|
|
51
277
|
|
|
52
|
-
### 1. Hello World
|
|
53
278
|
```json
|
|
54
279
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
280
|
+
"_id": "minha_pagina",
|
|
281
|
+
"display": true,
|
|
282
|
+
"title": "Minha Página",
|
|
283
|
+
"slug": "studio/custom/minha-pagina",
|
|
284
|
+
"html": "<h1>Oi</h1>",
|
|
285
|
+
"script": "$scope.x = 1;"
|
|
61
286
|
}
|
|
62
287
|
```
|
|
63
288
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
289
|
+
**Exemplo de response (201):** o próprio documento, com `_id` preenchido.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 5. Regras de Negócio
|
|
294
|
+
|
|
295
|
+
Regras que existem **no código** e não no schema:
|
|
296
|
+
|
|
297
|
+
- **Sem validação de schema na escrita.** `DatabaseRest` grava o `HashMap` recebido como veio. Qualquer campo extra é persistido; nenhum campo é obrigatório do ponto de vista do backend (o `StudioPage` POJO só restringe a **leitura** em `GameTechniqueManager`).
|
|
298
|
+
- **`display` deve ser sempre booleano.** Páginas com `display == null` provocam `NullPointerException` em `findComponentsRelationship` (linhas 277 e 566), derrubando `GET /v3/technique/relationship` para **todo o tenant**. Sempre envie `display: true` ou `display: false`.
|
|
299
|
+
- **`PUT` é full-replace e upsert.** Omitir um campo o remove; omitir `_id` cria um novo registro.
|
|
300
|
+
- **`created`/`updated` não são gerenciados.** Se você precisa de timestamps, é responsabilidade do cliente preenchê-los — o servidor nunca o faz.
|
|
301
|
+
- **Scope obrigatório `database`.** Sem ele, leituras e escritas retornam vazio/`null` com `200`, sem erro explícito.
|
|
302
|
+
- **`DELETE` exige `q` com `length >= 3`.** Um delete sem filtro válido é silenciosamente um no-op.
|
|
303
|
+
- **Multi-tenant físico.** A conexão Mongo é resolvida por `FrontController.getInstance(apiKey)`. Não há campo de tenant no documento `studio_page`; o isolamento é por base de dados.
|
|
304
|
+
- **`techniques`/`style`/`references`/`extra`** só têm efeito no contexto do mapa de estratégia (e técnicas de jogo, quando configuradas). Não afetam a renderização da página.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 6. Comportamentos Automáticos
|
|
309
|
+
|
|
310
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
311
|
+
|---|---|---|---|
|
|
312
|
+
| Auto `_id` | POST/PUT sem `_id`/`null` | `Guid.newShortGuid()` | Sim |
|
|
313
|
+
| Triggers de escrita | POST → `before/after_create`; PUT → `before/after_update`; DELETE → `before/after_delete` | Executa triggers configuradas na coleção `studio_page` | Conforme a trigger |
|
|
314
|
+
| Auditoria | Toda criação/atualização/exclusão | `AuditManager.log(EVENT_CREATE/UPDATE/DELETE)` com o usuário logado no Studio | `audit`/`audit_log` |
|
|
315
|
+
| Criptografia de campos | Se `CryptObjectField` existir p/ `studio_page` e token autorizado | Campos cifrados/decifrados | Sim |
|
|
316
|
+
| Full replace | PUT (`jongo.save`) | Documento inteiro substituído | Sim |
|
|
317
|
+
| Nó/arestas no mapa de estratégia | Leitura em `GET /v3/technique/relationship` | `display`/`style`/`references` viram nós e arestas | Não (computado) |
|
|
318
|
+
| Auto-criação do prompt de IA | 1º `POST /v3/ai/build/studio_page` sem prompt | Cria `intro_build_studio_page` | `ai_prompt` |
|
|
319
|
+
|
|
320
|
+
```mermaid
|
|
321
|
+
flowchart LR
|
|
322
|
+
W[POST/PUT/DELETE studio_page] --> SC{scope database?}
|
|
323
|
+
SC -->|não| EMPTY[200 vazio]
|
|
324
|
+
SC -->|sim| ID[auto _id se ausente]
|
|
325
|
+
ID --> BEF[trigger BEFORE_*]
|
|
326
|
+
BEF --> DB[(insert/save/remove)]
|
|
327
|
+
DB --> AFT[trigger AFTER_*]
|
|
328
|
+
AFT --> AUD[AuditManager.log]
|
|
329
|
+
AUD --> RESP[201/200]
|
|
76
330
|
```
|
|
77
331
|
|
|
78
|
-
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## 7. Suportado vs NÃO Suportado
|
|
335
|
+
|
|
336
|
+
### ✅ Suportado
|
|
337
|
+
|
|
338
|
+
- CRUD completo via `/v3/database/studio_page` (POST, PUT, GET por id, GET lista, DELETE, bulk, aggregate, índices, count, drop).
|
|
339
|
+
- Persistência de `html`/`script`/`slug`/`title`/`display`/`folder`/`extra`/`techniques`/`style`/`references` e de **quaisquer campos extras** enviados.
|
|
340
|
+
- Disparo de triggers e registro de auditoria em toda mutação.
|
|
341
|
+
- Participação no mapa de estratégia (`GET /v3/technique/relationship`) via `display`/`style`/`references`.
|
|
342
|
+
- Geração assistida por IA do conteúdo (`POST /v3/ai/build/studio_page`) — sem persistência.
|
|
343
|
+
- Paginação por header `Range` e modo BSON strict (`?strict=true`).
|
|
344
|
+
|
|
345
|
+
### ❌ NÃO Suportado
|
|
346
|
+
|
|
347
|
+
- **Endpoint REST dedicado** (`/v3/studio_page`, `/v3/studio/page`): **não existe**. Só o CRUD genérico de `database`.
|
|
348
|
+
- **Validação de schema na escrita**: inexistente — qualquer payload é gravado.
|
|
349
|
+
- **Rejeição de campos desconhecidos na escrita**: não ocorre (apenas na leitura pelo POJO). Ex.: `tags` é gravado mas ignorado.
|
|
350
|
+
- **Atualização parcial (PATCH)**: não há — `PUT` sempre substitui o documento inteiro.
|
|
351
|
+
- **Timestamps automáticos** (`created`/`updated`): nunca preenchidos pelo servidor.
|
|
352
|
+
- **Campo `tags`**: comentado na entidade (`StudioPage.java:45`) — sem efeito em runtime.
|
|
353
|
+
- **Interpretação de `html`/`script`/`slug` pelo backend**: o `funifier-service` não parseia, valida nem executa nada disso. AngularJS, `Marketplace`, diretivas (`<image-picker>`, `<principal-picker>`, `<input-extra>`), Highcharts, Bootstrap e roteamento por `slug` são **contrato do frontend do Studio** — só aparecem no servidor como texto dentro do prompt de IA `intro_build_studio_page` (`AIRest:1140`), não como código executável.
|
|
354
|
+
- **Tratamento de `display == null`**: não tratado — provoca `NullPointerException` no mapa de estratégia (limitação confirmada).
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 8. Segurança e Permissões
|
|
359
|
+
|
|
360
|
+
### Autenticação e autorização
|
|
361
|
+
|
|
362
|
+
- Exige `Authorization: Bearer <token>` resolvido em `AuthBean`.
|
|
363
|
+
- Exige o scope **`database`** (`Application.SCOPE_DATABASE`). Não há granularidade por página: qualquer token com scope `database` lista, cria, edita e apaga **qualquer** `studio_page` do tenant.
|
|
364
|
+
- Falta de scope **não** gera `403` — retorna `200` vazio (falha silenciosa).
|
|
365
|
+
|
|
366
|
+
### Isolamento multi-tenant
|
|
367
|
+
|
|
368
|
+
Conexão Mongo própria por tenant via `FrontController.getInstance(apiKey)`. Sem campo de tenant no documento — isolamento físico por base.
|
|
369
|
+
|
|
370
|
+
### Superfície de XSS / execução de código (por design)
|
|
371
|
+
|
|
372
|
+
`html` e `script` são strings arbitrárias armazenadas e posteriormente **executadas pelo frontend do Studio** (AngularJS). Portanto, qualquer usuário com scope `database` que escreva em `studio_page` consegue **injetar JavaScript que roda no contexto administrativo do Studio** de outros operadores que abrirem a página. É um vetor de **stored XSS / execução de código** inerente ao recurso — o backend não sanitiza nem poderia, pois não interpreta o conteúdo.
|
|
373
|
+
|
|
374
|
+
### Injeção MongoDB
|
|
375
|
+
|
|
376
|
+
`GET /v3/database/studio_page` e `/aggregate` concatenam o parâmetro `q` e o array `aggregations` diretamente no pipeline (`s.append(q)`, `DatabaseRest` linhas 144, 516-533). Um cliente autenticado pode injetar `$where`, `$lookup` para outras coleções, etc. **Não há whitelist nem sanitização** — o modelo de segurança assume que tokens autenticados são confiáveis.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 9. Observabilidade e Troubleshooting
|
|
381
|
+
|
|
382
|
+
### Diagnóstico básico
|
|
383
|
+
|
|
384
|
+
| Sintoma | Verificar |
|
|
385
|
+
|---|---|
|
|
386
|
+
| Página não aparece no menu do Studio | `display` é `true`? `db.studio_page.findOne({_id:"X"}).display` |
|
|
387
|
+
| `GET /v3/technique/relationship` retornando 500 | Alguma página com `display` ausente/`null` → `db.studio_page.find({display:{$exists:false}})` ou `{display:null}` |
|
|
388
|
+
| "Atualizei mas criou outra página" | `PUT` enviado **sem** `_id` → duplicata. Conferir `db.studio_page.find({slug:"..."})` |
|
|
389
|
+
| Campos "somem" após salvar | `PUT` é full-replace — campos omitidos são removidos |
|
|
390
|
+
| `created`/`updated` sempre null | Esperado — não são automáticos |
|
|
391
|
+
| Página não salva / lista vazia inesperada | Token tem scope `database`? (sem ele, `200` vazio) |
|
|
392
|
+
|
|
393
|
+
### Queries úteis (mongosh)
|
|
394
|
+
|
|
79
395
|
```javascript
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
$scope.all = [];
|
|
92
|
-
$scope.list = function () {
|
|
93
|
-
$http({method: 'GET', url:Marketplace.auth.getService() + '/v3/database/player',
|
|
94
|
-
headers: {"Authorization": Marketplace.auth.getAuthorization(), "Range": $scope.range.request, "content-type": "application/json"}
|
|
95
|
-
}).then(function(data){
|
|
96
|
-
$scope.all = data.data;
|
|
97
|
-
$scope.range = Marketplace.range.parse(data.headers(["content-range"]));
|
|
98
|
-
});
|
|
99
|
-
};
|
|
100
|
-
$scope.list();
|
|
101
|
-
```
|
|
102
|
-
HTML de paginação:
|
|
103
|
-
```html
|
|
104
|
-
<div>
|
|
105
|
-
Page <input type="number" ng-model="range.page" ng-change="gotopage(range.page)" style="width:40px;" /> of {{range.pages}}
|
|
106
|
-
<button ng-click="paginate(-1)" class="btn btn-default"><span class="glyphicon glyphicon-chevron-left"></span></button>
|
|
107
|
-
<button ng-click="paginate(+1)" class="btn btn-default"><span class="glyphicon glyphicon-chevron-right"></span></button>
|
|
108
|
-
</div>
|
|
396
|
+
// Páginas que quebram o mapa de estratégia (display null/ausente)
|
|
397
|
+
db.studio_page.find({ $or: [ {display: null}, {display: {$exists: false}} ] }, {title:1, slug:1})
|
|
398
|
+
|
|
399
|
+
// Corrigir em massa páginas sem display
|
|
400
|
+
db.studio_page.updateMany({ display: {$exists: false} }, { $set: {display: false} })
|
|
401
|
+
|
|
402
|
+
// Listar páginas visíveis no menu
|
|
403
|
+
db.studio_page.find({ display: true }, { title:1, slug:1 })
|
|
404
|
+
|
|
405
|
+
// Detectar slugs duplicados (sintoma de PUT sem _id)
|
|
406
|
+
db.studio_page.aggregate([{ $group: { _id:"$slug", n:{$sum:1} } }, { $match: { n:{$gt:1} } }])
|
|
109
407
|
```
|
|
110
408
|
|
|
111
|
-
###
|
|
112
|
-
**Lista** (`display: true`, slug: `studio/custom/car/list`): Lista + busca + paginação + botões editar/excluir + botão "NEW"
|
|
113
|
-
- DELETE: `$http({method: 'DELETE', url: API + "/v3/database/car__c?q=_id:'" + id + "'", ...})`
|
|
114
|
-
- Navegação: `$location.path("/studio/custom/car/form/" + id)` ou `"/studio/custom/car/form/new"`
|
|
409
|
+
### Comandos HTTP úteis
|
|
115
410
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
- Usa `$routeParams.id` para saber se é edição ou criação (`"new"` = novo)
|
|
120
|
-
- `<image-picker on-change="setImage" ...>` para upload de imagem
|
|
411
|
+
```bash
|
|
412
|
+
# Buscar por id (BSON strict para preservar tipos)
|
|
413
|
+
GET /v3/database/studio_page/minha_pagina?strict=true
|
|
121
414
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
method: 'POST', url: Marketplace.auth.getService() + '/v3/database/action_log/aggregate',
|
|
128
|
-
headers: { Authorization: Marketplace.auth.getAuthorization(), 'content-type': 'application/json'},
|
|
129
|
-
data: [
|
|
130
|
-
{"$match": { "time": { "$gte": { "$date": "-1y" } } } },
|
|
131
|
-
{"$addFields": { "day": { "$dayOfYear": "$time" } } },
|
|
132
|
-
{"$group": { "_id": { "player": "$userId", "day": "$day" }, "time": { "$max": "$time" }}},
|
|
133
|
-
{"$group": { "_id": "$_id.day", "total_users": { "$sum": 1 }, "time": { "$max": "$time" }}},
|
|
134
|
-
{"$project": { "_id": 0, "day_of_year": '$_id', "total": '$total_users', "time": 1}},
|
|
135
|
-
{"$sort": { "day_of_year": 1 } }
|
|
136
|
-
]
|
|
137
|
-
}).then(function (data) {
|
|
138
|
-
var transformedData = data.data.map(function (d) { return [d.time, d.total]; });
|
|
139
|
-
new Highcharts.Chart({
|
|
140
|
-
chart: { renderTo: 'chart', type: 'line' },
|
|
141
|
-
title: { text: 'Daily Active Players' },
|
|
142
|
-
xAxis: { type: 'datetime' },
|
|
143
|
-
yAxis: { title: { text: 'Total' } },
|
|
144
|
-
series: [{ name: 'Total over time', data: transformedData }],
|
|
145
|
-
credits: { enabled: false }
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
};
|
|
149
|
-
$scope.load();
|
|
415
|
+
# Listar páginas exibidas no menu
|
|
416
|
+
GET /v3/database/studio_page?q=display:true
|
|
417
|
+
|
|
418
|
+
# Excluir (q obrigatório, length >= 3)
|
|
419
|
+
DELETE /v3/database/studio_page?q=_id:'minha_pagina'
|
|
150
420
|
```
|
|
151
421
|
|
|
152
|
-
|
|
422
|
+
### Erros comuns e causas
|
|
423
|
+
|
|
424
|
+
- **`500` em `/v3/technique/relationship`** → `display` null em alguma página (NPE).
|
|
425
|
+
- **Duplicatas** → `PUT` sem `_id`.
|
|
426
|
+
- **Perda de campos** → confiar em `PUT` como patch.
|
|
427
|
+
- **`200` sem dados** → token sem scope `database`.
|
|
428
|
+
- **`DELETE` não apaga** → `q` ausente ou com menos de 3 caracteres.
|
|
153
429
|
|
|
154
|
-
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## 10. Exemplos Práticos
|
|
433
|
+
|
|
434
|
+
### 10.1 Exemplo mínimo funcional
|
|
155
435
|
|
|
156
436
|
```bash
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-H "Authorization: Bearer TOKEN"
|
|
437
|
+
PUT /v3/database/studio_page
|
|
438
|
+
Authorization: Bearer <token-com-scope-database>
|
|
439
|
+
Content-Type: application/json
|
|
440
|
+
|
|
441
|
+
{
|
|
442
|
+
"_id": "hello1",
|
|
443
|
+
"display": true,
|
|
444
|
+
"title": "Hello World!",
|
|
445
|
+
"slug": "studio/custom/hello",
|
|
446
|
+
"html": "<button ng-click=\"hello()\" class=\"btn btn-warning\">Hello</button>",
|
|
447
|
+
"script": "$scope.hello = function(){ alert('Hello World!'); }"
|
|
448
|
+
}
|
|
170
449
|
```
|
|
171
450
|
|
|
172
|
-
|
|
451
|
+
### 10.2 Exemplo avançado — com estilização e referências (mapa de estratégia)
|
|
452
|
+
|
|
453
|
+
```json
|
|
454
|
+
{
|
|
455
|
+
"_id": "kpi_vendas",
|
|
456
|
+
"display": true,
|
|
457
|
+
"title": "KPI de Vendas",
|
|
458
|
+
"slug": "studio/custom/kpi/vendas",
|
|
459
|
+
"html": "<div id=\"chart\"></div>",
|
|
460
|
+
"script": "/* monta Highcharts via /v3/database/action_log/aggregate */",
|
|
461
|
+
"folder": "dashboards",
|
|
462
|
+
"style": { "background": "#1565C0", "visible": true },
|
|
463
|
+
"techniques": [],
|
|
464
|
+
"references": [
|
|
465
|
+
{ "_id": "vendas", "type": "action", "title": "Venda", "way": "from", "linkLabel": "consome ação" },
|
|
466
|
+
{ "_id": "notification", "type": "notification", "title": "Aviso", "way": "to", "linkLabel": "gera aviso" }
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Isso cria um nó azul "KPI de Vendas" no mapa de estratégia (`GET /v3/technique/relationship`), com uma aresta entrando da ação `vendas` e outra saindo para `notification`.
|
|
472
|
+
|
|
473
|
+
### 10.3 Anti-pattern — `PUT` sem `_id` para "atualizar"
|
|
474
|
+
|
|
475
|
+
```json
|
|
476
|
+
// ❌ NÃO faça isso esperando atualizar a página "hello1"
|
|
477
|
+
PUT /v3/database/studio_page
|
|
478
|
+
{ "display": true, "title": "Hello v2", "slug": "studio/custom/hello", "html": "...", "script": "..." }
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Sem `_id`, o servidor gera um id novo e **cria uma segunda página** com o mesmo `slug` — não atualiza a original. Sempre inclua `_id` no `PUT`.
|
|
482
|
+
|
|
483
|
+
### 10.4 Anti-pattern — omitir `display`
|
|
484
|
+
|
|
485
|
+
```json
|
|
486
|
+
// ❌ Página sem display
|
|
487
|
+
PUT /v3/database/studio_page
|
|
488
|
+
{ "_id": "p1", "title": "Painel", "slug": "studio/custom/p1", "html": "...", "script": "..." }
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
`display` fica `null` e a próxima chamada a `GET /v3/technique/relationship` lança `NullPointerException`, derrubando o mapa de estratégia do tenant inteiro. Sempre envie `display` explicitamente.
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Checklist de Configuração
|
|
173
496
|
|
|
174
|
-
- [ ]
|
|
175
|
-
- [ ]
|
|
176
|
-
- [ ]
|
|
177
|
-
- [ ]
|
|
178
|
-
- [ ]
|
|
179
|
-
- [ ]
|
|
180
|
-
- [ ]
|
|
497
|
+
- [ ] `_id` **sempre** presente em `PUT` (senão cria duplicata)
|
|
498
|
+
- [ ] `display` **sempre** booleano explícito (`true`/`false`) — nunca omitir (NPE no mapa de estratégia)
|
|
499
|
+
- [ ] `title` e `slug` definidos
|
|
500
|
+
- [ ] `html` e `script` enviados como strings escapadas (o backend não os valida)
|
|
501
|
+
- [ ] Token com scope `database` (senão `200` vazio silencioso)
|
|
502
|
+
- [ ] Ciente de que `PUT` é full-replace: enviar o documento **completo**, não só os campos alterados
|
|
503
|
+
- [ ] `created`/`updated` preenchidos pelo cliente, se necessários (não são automáticos)
|
|
504
|
+
- [ ] `DELETE` sempre com `q` válido (`length >= 3`)
|
|
505
|
+
- [ ] Armadilha: campos extras (ex.: `tags`) são gravados mas ignorados pelo backend
|
|
506
|
+
- [ ] Segurança: `script`/`html` executam no contexto admin do Studio — trate `studio_page` como superfície de execução de código
|