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.
Files changed (170) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +4 -1
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/mcp/bundle.js +119 -93
  66. package/dist/mcp/index.js +2 -2
  67. package/dist/mcp/index.js.map +1 -1
  68. package/dist/mcp/resources/documentation.d.ts +1 -1
  69. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  70. package/dist/mcp/resources/documentation.js +39 -3
  71. package/dist/mcp/resources/documentation.js.map +1 -1
  72. package/dist/mcp/tools/connect.d.ts.map +1 -1
  73. package/dist/mcp/tools/connect.js +18 -8
  74. package/dist/mcp/tools/connect.js.map +1 -1
  75. package/dist/mcp/tools/database.d.ts.map +1 -1
  76. package/dist/mcp/tools/database.js +59 -47
  77. package/dist/mcp/tools/database.js.map +1 -1
  78. package/dist/mcp/tools/database.test.js +2 -2
  79. package/dist/mcp/tools/database.test.js.map +1 -1
  80. package/dist/mcp/tools/delete.d.ts.map +1 -1
  81. package/dist/mcp/tools/delete.js +13 -3
  82. package/dist/mcp/tools/delete.js.map +1 -1
  83. package/dist/mcp/tools/execute.d.ts.map +1 -1
  84. package/dist/mcp/tools/execute.js +20 -9
  85. package/dist/mcp/tools/execute.js.map +1 -1
  86. package/dist/mcp/tools/folder.d.ts.map +1 -1
  87. package/dist/mcp/tools/folder.js +22 -12
  88. package/dist/mcp/tools/folder.js.map +1 -1
  89. package/dist/mcp/tools/get.d.ts.map +1 -1
  90. package/dist/mcp/tools/get.js +16 -6
  91. package/dist/mcp/tools/get.js.map +1 -1
  92. package/dist/mcp/tools/index.d.ts +1 -1
  93. package/dist/mcp/tools/index.d.ts.map +1 -1
  94. package/dist/mcp/tools/index.js +3 -1
  95. package/dist/mcp/tools/index.js.map +1 -1
  96. package/dist/mcp/tools/list.d.ts.map +1 -1
  97. package/dist/mcp/tools/list.js +38 -14
  98. package/dist/mcp/tools/list.js.map +1 -1
  99. package/dist/mcp/tools/logs.d.ts.map +1 -1
  100. package/dist/mcp/tools/logs.js +15 -5
  101. package/dist/mcp/tools/logs.js.map +1 -1
  102. package/dist/mcp/tools/save.d.ts.map +1 -1
  103. package/dist/mcp/tools/save.js +14 -4
  104. package/dist/mcp/tools/save.js.map +1 -1
  105. package/dist/mcp/tools/save.test.js +3 -3
  106. package/dist/mcp/tools/save.test.js.map +1 -1
  107. package/dist/mcp/tools/search-docs.d.ts +3 -0
  108. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  109. package/dist/mcp/tools/search-docs.js +102 -0
  110. package/dist/mcp/tools/search-docs.js.map +1 -0
  111. package/package.json +6 -2
  112. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  113. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  114. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  115. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  116. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  117. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  118. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  119. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  120. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  121. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  122. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  123. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  124. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  125. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  126. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  127. package/skills/funifier/SKILL.md +88 -0
  128. package/skills/funifier/references/configure-security.md +96 -0
  129. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  130. package/skills/funifier/references/create-aggregate.md +144 -0
  131. package/skills/funifier/references/create-challenge.md +116 -0
  132. package/skills/funifier/references/create-competition.md +98 -0
  133. package/skills/funifier/references/create-crossword.md +574 -0
  134. package/skills/funifier/references/create-custom-object.md +91 -0
  135. package/skills/funifier/references/create-custom-page.md +135 -0
  136. package/skills/funifier/references/create-folder.md +104 -0
  137. package/skills/funifier/references/create-lastmile.md +643 -0
  138. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  139. package/skills/funifier/references/create-level.md +94 -0
  140. package/skills/funifier/references/create-lottery.md +913 -0
  141. package/skills/funifier/references/create-mystery.md +769 -0
  142. package/skills/funifier/references/create-notification.md +75 -0
  143. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  144. package/skills/funifier/references/create-quiz.md +98 -0
  145. package/skills/funifier/references/create-scheduler.md +141 -0
  146. package/skills/funifier/references/create-story.md +636 -0
  147. package/skills/funifier/references/create-swap.md +95 -0
  148. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  149. package/skills/funifier/references/create-virtual-good.md +96 -0
  150. package/skills/funifier/references/create-webhook.md +72 -0
  151. package/skills/funifier/references/create-websocket.md +71 -0
  152. package/skills/funifier/references/create-widget.md +76 -0
  153. package/skills/funifier/references/debug.md +87 -0
  154. package/skills/funifier/references/help.md +81 -0
  155. package/skills/funifier/references/implement-frontend.md +106 -0
  156. package/skills/funifier/references/import-csv.md +75 -0
  157. package/skills/funifier/references/manage-player.md +82 -0
  158. package/skills/funifier/references/manage-team.md +76 -0
  159. package/skills/funifier/references/upload-file.md +91 -0
  160. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  161. package/skills/funifier-create-challenge/SKILL.md +0 -88
  162. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  163. package/skills/funifier-create-level/SKILL.md +0 -87
  164. package/skills/funifier-create-quiz/SKILL.md +0 -87
  165. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  166. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  167. package/skills/funifier-debug/SKILL.md +0 -92
  168. package/skills/funifier-help/SKILL.md +0 -86
  169. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  170. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,180 +1,506 @@
1
- # Studio Page (Página Customizada do Studio)
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
- ## O que é
7
+ ---
6
8
 
7
- Páginas customizadas dentro do Funifier Studio para atender necessidades específicas dos administradores. Permitem criar dashboards, CRUDs, gráficos, relatórios — tudo com AngularJS + Bootstrap.
9
+ ## 1. Visão Geral
8
10
 
9
- ## Configuração
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
- Cada página é um JSON com:
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
- ## Contexto JavaScript
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
- Dentro do `script`, você tem acesso a:
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
- ### Marketplace API
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
- | Método | Descrição | Exemplo de retorno |
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
- ## Diretivas Disponíveis
30
+ ---
38
31
 
39
- - `<image-picker>` — Seletor de imagem (URL, local, galeria). Atributos: `on-change`, `show-picker-url`, `show-picker-local`, `show-picker-gallery`, `upload-max-size`, `transform`
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
- ## Bibliotecas Disponíveis
34
+ ### 2.1 Classes envolvidas
44
35
 
45
- - **Highcharts** Gráficos (pizza, barra, linhas, etc.)
46
- - **Bootstrap** — CSS framework (classes `btn`, `table`, `form-control`, etc.)
47
- - **Glyphicons** Ícones via `glyphicon glyphicon-*`
48
- - Traduções: `{{'SAVE'|translate}}`, `{{'CANCEL'|translate}}`, `{{'BACK'|translate}}`, `{{'NEW'|translate}}`, `{{'SEARCH_FOR'|translate}}`
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
- ## Exemplos
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
- "_id": "hello1",
56
- "display": true,
57
- "title": "Hello World!",
58
- "slug": "studio/custom/hello",
59
- "html": "<button ng-click=\"hello()\" class=\"btn btn-warning\">Hello</button>",
60
- "script": "$scope.hello = function(){ alert('Hello World!'); }"
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
- ### 2. Lista Simples (Desafios)
65
- ```javascript
66
- /* "display": true, "title": "Quest List", "slug": "studio/custom/challenge" */
67
- $scope.all = [];
68
- $scope.list = function () {
69
- $http({method: 'GET', url:Marketplace.auth.getService() + '/v3/database/challenge',
70
- headers: {"Authorization": Marketplace.auth.getAuthorization(), "content-type": "application/json"}
71
- }).then(function(data){
72
- $scope.all = data.data;
73
- });
74
- };
75
- $scope.list();
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
- ### 3. Lista Paginada (Players)
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
- /* "display": true, "title": "Player List", "slug": "studio/custom/player" */
81
- $scope.range = { request: "items=0-100" };
82
- $scope.gotopage = function(page){
83
- $scope.range.request = Marketplace.range.paginate(page, $scope.range.response);
84
- $scope.list();
85
- };
86
- $scope.paginate = function (total) {
87
- var to = $scope.range.page + total;
88
- $scope.range.request = Marketplace.range.paginate(to, $scope.range.response);
89
- $scope.list();
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
- ### 4. CRUD Completo (Carros)
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
- **Formulário** (`display: false`, slug: `studio/custom/car/form/:id`):
117
- - Carrega via GET: `/v3/database/car__c?strict=true&q=_id:'ID'`
118
- - Salva via PUT: `/v3/database/car__c`
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
- ### 5. Gráfico Highcharts (Active Players)
123
- ```javascript
124
- /* "display": true, "title": "Daily Active Players", "slug": "studio/custom/kpi/players" */
125
- $scope.load = function () {
126
- $http({
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
- ## API REST para Gerenciar Páginas
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
- **IMPORTANTE:** As páginas customizadas ficam na collection `studio_page` (NÃO `page`).
430
+ ---
431
+
432
+ ## 10. Exemplos Práticos
433
+
434
+ ### 10.1 Exemplo mínimo funcional
155
435
 
156
436
  ```bash
157
- # Criar/atualizar página
158
- curl -X PUT "https://service2.funifier.com/v3/database/studio_page" \
159
- -H "Authorization: Bearer TOKEN" \
160
- -H "Content-Type: application/json" \
161
- -d '{"_id":"minha_pagina", "display":true, "title":"Minha Página", "slug":"studio/custom/minha-pagina", "html":"<h1>Oi</h1>", "script":"/* js */"}'
162
-
163
- # Listar páginas
164
- curl "https://service2.funifier.com/v3/database/studio_page" \
165
- -H "Authorization: Bearer TOKEN"
166
-
167
- # Deletar página
168
- curl -X DELETE "https://service2.funifier.com/v3/database/studio_page?q=_id:'ID'" \
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
- ## Checklist
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
- - [ ] Definir `_id`, `title`, `slug`, `display`
175
- - [ ] Escrever `html` com Bootstrap
176
- - [ ] Escrever `script` com AngularJS
177
- - [ ] Usar `Marketplace.auth.*` para autenticação
178
- - [ ] Usar `?strict=true` em GETs para preservar tipos BSON
179
- - [ ] Testar no Studio (navegar até a URL do slug)
180
- - [ ] Para CRUDs: criar página de lista (display:true) + formulário (display:false)
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