funifier-mcp 0.2.0 → 0.2.3

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 (64) hide show
  1. package/.cursor/rules/funifier.mdc +91 -0
  2. package/.github/copilot-instructions.md +83 -0
  3. package/AGENTS.md +97 -0
  4. package/README.md +247 -78
  5. package/datasource-funifier-docs/knowledge/guides/aggregates.md +152 -152
  6. package/datasource-funifier-docs/knowledge/guides/database-access.md +132 -132
  7. package/datasource-funifier-docs/knowledge/guides/java-entities.md +373 -373
  8. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +330 -330
  9. package/datasource-funifier-docs/knowledge/guides/java-managers.md +509 -509
  10. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +271 -271
  11. package/datasource-funifier-docs/knowledge/index.md +121 -121
  12. package/datasource-funifier-docs/knowledge/modules/achievement.md +46 -46
  13. package/datasource-funifier-docs/knowledge/modules/action-log.md +88 -88
  14. package/datasource-funifier-docs/knowledge/modules/action.md +80 -80
  15. package/datasource-funifier-docs/knowledge/modules/auth.md +104 -104
  16. package/datasource-funifier-docs/knowledge/modules/avatar.md +28 -28
  17. package/datasource-funifier-docs/knowledge/modules/backup.md +40 -40
  18. package/datasource-funifier-docs/knowledge/modules/challenge.md +91 -91
  19. package/datasource-funifier-docs/knowledge/modules/compact.md +40 -40
  20. package/datasource-funifier-docs/knowledge/modules/competition.md +149 -149
  21. package/datasource-funifier-docs/knowledge/modules/crossword.md +41 -41
  22. package/datasource-funifier-docs/knowledge/modules/csv-data.md +30 -30
  23. package/datasource-funifier-docs/knowledge/modules/custom-object.md +53 -53
  24. package/datasource-funifier-docs/knowledge/modules/database.md +241 -241
  25. package/datasource-funifier-docs/knowledge/modules/folder.md +111 -111
  26. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +23 -23
  27. package/datasource-funifier-docs/knowledge/modules/lastmile.md +45 -45
  28. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +98 -98
  29. package/datasource-funifier-docs/knowledge/modules/level.md +83 -83
  30. package/datasource-funifier-docs/knowledge/modules/lottery.md +112 -112
  31. package/datasource-funifier-docs/knowledge/modules/marketplace.md +27 -27
  32. package/datasource-funifier-docs/knowledge/modules/mystery.md +82 -82
  33. package/datasource-funifier-docs/knowledge/modules/notification.md +40 -40
  34. package/datasource-funifier-docs/knowledge/modules/patterns.md +1096 -1096
  35. package/datasource-funifier-docs/knowledge/modules/player.md +101 -101
  36. package/datasource-funifier-docs/knowledge/modules/point.md +67 -67
  37. package/datasource-funifier-docs/knowledge/modules/public.md +253 -253
  38. package/datasource-funifier-docs/knowledge/modules/question.md +136 -136
  39. package/datasource-funifier-docs/knowledge/modules/quiz.md +163 -163
  40. package/datasource-funifier-docs/knowledge/modules/scheduler.md +58 -58
  41. package/datasource-funifier-docs/knowledge/modules/security.md +169 -169
  42. package/datasource-funifier-docs/knowledge/modules/staging.md +28 -28
  43. package/datasource-funifier-docs/knowledge/modules/static-repo.md +41 -41
  44. package/datasource-funifier-docs/knowledge/modules/story.md +42 -42
  45. package/datasource-funifier-docs/knowledge/modules/studio-page.md +180 -180
  46. package/datasource-funifier-docs/knowledge/modules/swap.md +132 -132
  47. package/datasource-funifier-docs/knowledge/modules/team.md +75 -75
  48. package/datasource-funifier-docs/knowledge/modules/trigger.md +189 -189
  49. package/datasource-funifier-docs/knowledge/modules/upload.md +155 -155
  50. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +99 -99
  51. package/datasource-funifier-docs/knowledge/modules/webhook.md +41 -41
  52. package/datasource-funifier-docs/knowledge/modules/websocket.md +41 -41
  53. package/datasource-funifier-docs/knowledge/modules/widget.md +42 -42
  54. package/datasource-funifier-docs/process-gtm-saas.md +143 -143
  55. package/datasource-funifier-docs/process-instagram.md +88 -88
  56. package/datasource-funifier-docs/process.md +1826 -1826
  57. package/datasource-funifier-docs/readme.md +132 -132
  58. package/dist/cli/config-writers.js +11 -11
  59. package/dist/mcp/bundle.js +55 -52
  60. package/package.json +70 -67
  61. package/skills/funifier-create-aggregate/SKILL.md +126 -126
  62. package/skills/funifier-create-custom-page/SKILL.md +126 -126
  63. package/skills/funifier-create-scheduler/SKILL.md +126 -126
  64. package/skills/funifier-create-trigger/SKILL.md +127 -127
@@ -1,253 +1,253 @@
1
- # Public (Endpoints Públicos)
2
-
3
- **Acesso Studio:** `/studio/public`
4
- **API Endpoint:** `/v3/public`
5
-
6
- ## O que é
7
-
8
- Criação e gerenciamento de endpoints públicos personalizados. Esses endpoints são ideais para integração com plataformas externas (como Circle, Zapier, Typeform), pois não exigem autenticação via token. A autenticação é feita via `apikey` incluída na URL pública no formato: `/v3/pub/{apikey}/{slug}`.
9
-
10
- ## Quando usar
11
-
12
- - Para receber dados de formulários externos (Typeform, Google Forms)
13
- - Para integrar com automações (Zapier, Make)
14
- - Para criar webhooks de recebimento
15
- - Para endpoints que não requerem autenticação por token
16
-
17
- ## Checklist de Configuração no Studio
18
-
19
- - [ ] Definir _id (slug) do endpoint
20
- - [ ] Definir título e descrição
21
- - [ ] Definir método HTTP (GET, POST, etc.)
22
- - [ ] Escrever script Java com método `public Object handle(Object payload)`
23
- - [ ] Ativar endpoint (active: true)
24
- - [ ] Testar via URL pública
25
-
26
- ## API Endpoints
27
-
28
- ### Listar Public Endpoints
29
- **Método:** GET
30
- **Endpoint:** `/v3/database/public_endpoint`
31
-
32
- **Exemplo de Resposta:**
33
- ```json
34
- [
35
- {
36
- "_id": "recebe_email",
37
- "title": "Recebe Email",
38
- "description": "Recebe email e registra na colecao email__c",
39
- "active": true,
40
- "method": "POST",
41
- "script": "public Object handle(Object payload) { payload._id = payload.email; manager.getJongoConnection().getCollection(\"email__c\").save(payload); Map<String, Object> response = new HashMap<>(); response.put(\"email\", payload.email); return response; }",
42
- "sample": "{ \"email\": \"user@email.com\" }",
43
- "extra": {}
44
- }
45
- ]
46
- ```
47
-
48
- ### Criar Public Endpoint
49
- **Método:** POST
50
- **Endpoint:** `/v3/public`
51
-
52
- ### Executar Public Endpoint
53
- **Método:** POST (ou conforme configurado)
54
- **Endpoint:** `/v3/pub/{apikey}/{slug}`
55
- **Descrição:** Executa o endpoint público. Não requer token de autenticação.
56
-
57
- ## Script Runtime Environment (Wrapper Class)
58
-
59
- O script que você escreve no Studio **não é standalone** — ele é inserido dentro de uma classe Java wrapper gerada automaticamente pelo Funifier. Isso tem implicações importantes:
60
-
61
- ### Estrutura do Wrapper
62
-
63
- ```java
64
- // --- Imports automáticos (NÃO escreva import no seu script) ---
65
- import groovyx.net.http.*
66
- import static groovyx.net.http.Method.*
67
- import static groovyx.net.http.ContentType.*
68
- import groovy.json.*
69
- import groovy.transform.TimedInterrupt
70
- import java.util.concurrent.TimeUnit
71
- import java.util.Arrays
72
- import java.util.Scanner
73
- import org.apache.http.HttpResponse
74
- import org.apache.http.client.HttpClient
75
- import org.apache.http.client.methods.HttpGet
76
- import org.apache.http.client.methods.HttpPost
77
- import org.apache.http.impl.client.HttpClientBuilder
78
- import org.apache.commons.io.IOUtils
79
- import java.lang.StringBuffer
80
- import java.io.*
81
- import com.fasterxml.jackson.core.JsonProcessingException
82
- // Unirest (HTTP client)
83
- import com.mashape.unirest.http.HttpResponse
84
- import com.mashape.unirest.http.JsonNode
85
- import com.mashape.unirest.http.Unirest
86
- import com.mashape.unirest.http.exceptions.UnirestException
87
- import com.mashape.unirest.request.GetRequest
88
- import com.mashape.unirest.request.HttpRequestWithBody
89
- // Mail (Simple Java Mail)
90
- import org.simplejavamail.email.Email
91
- import org.simplejavamail.email.EmailBuilder
92
- import org.simplejavamail.mailer.MailerBuilder
93
- // Funifier entities
94
- import com.funifier.engine.action.*
95
- import com.funifier.engine.achievement.*
96
- import com.funifier.engine.lottery.*
97
- import com.funifier.engine.challenge.*
98
- import com.funifier.engine.constant.*
99
- import com.funifier.engine.guid.*
100
- import com.funifier.engine.level.*
101
- import com.funifier.engine.notify.*
102
- import com.funifier.engine.player.*
103
- import com.funifier.engine.point.*
104
- import com.funifier.engine.team.*
105
- import com.funifier.engine.catalog.*
106
- import com.funifier.engine.mail.*
107
- // Funifier utils
108
- import com.funifier.engine.util.DateUtil
109
- import com.funifier.engine.util.JsonUtil
110
- import com.funifier.engine.util.HttpUtil
111
- import com.funifier.engine.util.HttpClientFunifier
112
- import com.funifier.engine.util.MustacheUtils
113
- import com.funifier.controller.ManagerFactory
114
-
115
- @TimedInterrupt(value = 5L, unit = TimeUnit.SECONDS)
116
- class FunifierPublicEndpoint {
117
- ManagerFactory manager = null;
118
- void setManager(ManagerFactory c) { manager = c; }
119
-
120
- // --- SEU SCRIPT É INSERIDO AQUI ---
121
- }
122
- ```
123
-
124
- ### Regras para escrever scripts
125
-
126
- 1. **NÃO use `import`** — todos os imports necessários já estão no wrapper. Se precisar de uma classe não importada, use o nome completo (fully qualified): `com.example.MinhaClasse`
127
- 2. **O método principal é `public Object handle(Object payload)`** — este é o entry point
128
- 3. **`manager` está disponível** como campo da classe (ManagerFactory)
129
- 4. **Timeout padrão de 10 segundos** — o executor limita a execução. **Pode ser customizado** via campo `timeout` (em segundos) no objeto do endpoint — ver seção "Campo timeout" abaixo. Nota: o `@TimedInterrupt(5s)` no wrapper Groovy é uma segunda camada de proteção
130
- 5. **Groovy, não Java puro** — o script roda em Groovy, então features como GString, closures e tipagem dinâmica funcionam
131
- 6. **Cuidado com `$` em GStrings** — operadores MongoDB (`$set`, `$inc`) e strings com `$` são interpretados como variáveis Groovy. Use `String.valueOf((char)0x24)` para escapar:
132
- ```groovy
133
- def d = String.valueOf((char)0x24) // = "$"
134
- def setCmd = '{"' + d + 'set": {"name": "Novo Nome"}}'
135
- ```
136
- 7. **Use apenas ASCII em comentários** — caracteres UTF-8 especiais (em dash `—`, acentos em comentários) podem causar erros de parse no Groovy
137
- 8. **`instanceof` é bloqueado** pelo SecureASTCustomizer — use `getClass().getName()` ou try/catch
138
- 9. **NÃO use `groovy.json.JsonSlurper`** para parsear JSON que será retornado na response — o `LazyMap` do JsonSlurper causa `ClassCastException: [B incompatible with [C` quando o Funifier tenta serializar. Use `JsonUtil.fromJsonToMap(jsonString)` em vez de `slurper.parseText(jsonString)`
139
- 10. **Player.extra é público** — acesse direto com `player.extra`, não com `player.getExtra()` (método não existe)
140
- 11. **Para salvar player:** `manager.getPlayerManager().findById(id)` → modifique → `manager.getPlayerManager().insert(player)` (upsert)
141
-
142
- ### Campo `timeout` (Customizar timeout do script)
143
-
144
- O timeout padrão de **10 segundos** (confirmado no source: `PublicManager.java` linha 255) pode ser insuficiente para endpoints que fazem chamadas HTTP externas (ex: verificar token OAuth, chamar APIs de IA, download de imagens). O campo `timeout` permite aumentar esse limite.
145
-
146
- **Campo:** `timeout` (Long, em **segundos**)
147
- **Padrão:** `null` (usa 10 segundos — source: `PublicManager.java:255`)
148
- **Nota:** Este campo **não aparece no formulário do Studio** — só pode ser configurado via API.
149
-
150
- ```bash
151
- # Exemplo: definir timeout de 30 segundos para o endpoint google_login
152
- curl -X POST "https://service2.funifier.com/v3/public" \
153
- -H "Authorization: Basic <token>" \
154
- -H "Content-Type: application/json" \
155
- -d '{"_id": "google_login", "timeout": 30}'
156
- ```
157
-
158
- **Este campo também existe em Triggers e Schedulers** — mesma estrutura, mesma API (`/v3/trigger`, `/v3/scheduler`).
159
-
160
- **Mecanismo de execução (Java):**
161
- ```java
162
- // O executor usa Executors.newSingleThreadExecutor() com Future.get(timeout, TimeUnit.SECONDS)
163
- long timeout = 10; // padrão
164
- if (endpoint.timeout != null && endpoint.timeout > 0) {
165
- timeout = endpoint.timeout;
166
- }
167
- Object result = future.get(timeout, TimeUnit.SECONDS);
168
- ```
169
-
170
- **Nota:** O `@TimedInterrupt(value = 5L, unit = TimeUnit.SECONDS)` no wrapper Groovy é uma segunda camada de proteção. O timeout do executor (campo `timeout`) é a camada principal e deve ser >= ao TimedInterrupt para funcionar corretamente.
171
-
172
- **Estrutura da classe PublicEndpoint (Java):**
173
- ```java
174
- public class PublicEndpoint {
175
- public String _id; // slug (URL identifier)
176
- public String apikey; // gamification apikey
177
- public String title; // friendly name
178
- public String description; // internal description
179
- public boolean active; // enabled/disabled
180
- public String method; // "POST" or "GET" (default: "POST")
181
- public String script; // Groovy script body
182
- public Long timeout; // timeout in SECONDS (null = default 10s)
183
- public String sample; // example payload
184
- public Date updated; // last update timestamp
185
- public Map<String, Object> extra; // extra metadata
186
- }
187
- ```
188
-
189
- **Quando usar timeout customizado:**
190
- - Endpoints que fazem chamadas HTTP externas (OAuth, APIs externas)
191
- - Endpoints com BCrypt hashing (custo computacional)
192
- - Endpoints que fazem múltiplas operações em sequência
193
- - **Recomendação:** 20-30s para endpoints com chamadas externas
194
-
195
- ### Atualizar Public Endpoint via API
196
-
197
- ```
198
- POST /v3/public
199
- Authorization: Basic <base64(apiKey + ":")>
200
- Content-Type: application/json
201
-
202
- {
203
- "_id": "slug_do_endpoint",
204
- "title": "Titulo",
205
- "description": "Descricao",
206
- "active": true,
207
- "method": "POST",
208
- "script": "public Object handle(Object payload) { ... }",
209
- "sample": "{}",
210
- "extra": {}
211
- }
212
- ```
213
-
214
- O mesmo endpoint serve para criar e atualizar (upsert por `_id`)
215
-
216
- ### Bibliotecas HTTP disponíveis
217
-
218
- | Biblioteca | Uso recomendado |
219
- |-----------|----------------|
220
- | **Unirest** | HTTP client mais simples — `Unirest.post(url).header(...).body(...).asJson()` |
221
- | **Apache HttpClient** | Alternativa mais verbosa |
222
- | **groovyx.net.http** | HTTP Builder para Groovy |
223
- | **HttpUtil / HttpClientFunifier** | Utilitários Funifier |
224
-
225
- ### Exemplo usando Unirest
226
-
227
- ```groovy
228
- public Object handle(Object payload) {
229
- def response = Unirest.post("https://api.external.com/endpoint")
230
- .header("Content-Type", "application/json")
231
- .header("Authorization", "Bearer my-token")
232
- .body(JsonUtil.toJson(payload))
233
- .asString()
234
-
235
- def result = new groovy.json.JsonSlurper().parseText(response.getBody())
236
- return result
237
- }
238
- ```
239
-
240
- > ⚠️ **Triggers e Schedulers usam a mesma estrutura de wrapper** com os mesmos imports pré-disponíveis. A diferença é apenas o método principal: triggers usam `void trigger(event, entity, player, database)` e schedulers usam `void execute()`.
241
-
242
- ## Boas Práticas
243
-
244
- - Não expor dados sensíveis em endpoints públicos
245
- - Não realizar operações críticas sem validação adicional
246
- - Validar payload recebido no script Java
247
-
248
- ## Validações e Testes
249
-
250
- - [ ] Endpoint aparece na lista
251
- - [ ] URL pública é acessível sem token
252
- - [ ] Script processa payload corretamente
253
- - [ ] Resposta é retornada conforme esperado
1
+ # Public (Endpoints Públicos)
2
+
3
+ **Acesso Studio:** `/studio/public`
4
+ **API Endpoint:** `/v3/public`
5
+
6
+ ## O que é
7
+
8
+ Criação e gerenciamento de endpoints públicos personalizados. Esses endpoints são ideais para integração com plataformas externas (como Circle, Zapier, Typeform), pois não exigem autenticação via token. A autenticação é feita via `apikey` incluída na URL pública no formato: `/v3/pub/{apikey}/{slug}`.
9
+
10
+ ## Quando usar
11
+
12
+ - Para receber dados de formulários externos (Typeform, Google Forms)
13
+ - Para integrar com automações (Zapier, Make)
14
+ - Para criar webhooks de recebimento
15
+ - Para endpoints que não requerem autenticação por token
16
+
17
+ ## Checklist de Configuração no Studio
18
+
19
+ - [ ] Definir _id (slug) do endpoint
20
+ - [ ] Definir título e descrição
21
+ - [ ] Definir método HTTP (GET, POST, etc.)
22
+ - [ ] Escrever script Java com método `public Object handle(Object payload)`
23
+ - [ ] Ativar endpoint (active: true)
24
+ - [ ] Testar via URL pública
25
+
26
+ ## API Endpoints
27
+
28
+ ### Listar Public Endpoints
29
+ **Método:** GET
30
+ **Endpoint:** `/v3/database/public_endpoint`
31
+
32
+ **Exemplo de Resposta:**
33
+ ```json
34
+ [
35
+ {
36
+ "_id": "recebe_email",
37
+ "title": "Recebe Email",
38
+ "description": "Recebe email e registra na colecao email__c",
39
+ "active": true,
40
+ "method": "POST",
41
+ "script": "public Object handle(Object payload) { payload._id = payload.email; manager.getJongoConnection().getCollection(\"email__c\").save(payload); Map<String, Object> response = new HashMap<>(); response.put(\"email\", payload.email); return response; }",
42
+ "sample": "{ \"email\": \"user@email.com\" }",
43
+ "extra": {}
44
+ }
45
+ ]
46
+ ```
47
+
48
+ ### Criar Public Endpoint
49
+ **Método:** POST
50
+ **Endpoint:** `/v3/public`
51
+
52
+ ### Executar Public Endpoint
53
+ **Método:** POST (ou conforme configurado)
54
+ **Endpoint:** `/v3/pub/{apikey}/{slug}`
55
+ **Descrição:** Executa o endpoint público. Não requer token de autenticação.
56
+
57
+ ## Script Runtime Environment (Wrapper Class)
58
+
59
+ O script que você escreve no Studio **não é standalone** — ele é inserido dentro de uma classe Java wrapper gerada automaticamente pelo Funifier. Isso tem implicações importantes:
60
+
61
+ ### Estrutura do Wrapper
62
+
63
+ ```java
64
+ // --- Imports automáticos (NÃO escreva import no seu script) ---
65
+ import groovyx.net.http.*
66
+ import static groovyx.net.http.Method.*
67
+ import static groovyx.net.http.ContentType.*
68
+ import groovy.json.*
69
+ import groovy.transform.TimedInterrupt
70
+ import java.util.concurrent.TimeUnit
71
+ import java.util.Arrays
72
+ import java.util.Scanner
73
+ import org.apache.http.HttpResponse
74
+ import org.apache.http.client.HttpClient
75
+ import org.apache.http.client.methods.HttpGet
76
+ import org.apache.http.client.methods.HttpPost
77
+ import org.apache.http.impl.client.HttpClientBuilder
78
+ import org.apache.commons.io.IOUtils
79
+ import java.lang.StringBuffer
80
+ import java.io.*
81
+ import com.fasterxml.jackson.core.JsonProcessingException
82
+ // Unirest (HTTP client)
83
+ import com.mashape.unirest.http.HttpResponse
84
+ import com.mashape.unirest.http.JsonNode
85
+ import com.mashape.unirest.http.Unirest
86
+ import com.mashape.unirest.http.exceptions.UnirestException
87
+ import com.mashape.unirest.request.GetRequest
88
+ import com.mashape.unirest.request.HttpRequestWithBody
89
+ // Mail (Simple Java Mail)
90
+ import org.simplejavamail.email.Email
91
+ import org.simplejavamail.email.EmailBuilder
92
+ import org.simplejavamail.mailer.MailerBuilder
93
+ // Funifier entities
94
+ import com.funifier.engine.action.*
95
+ import com.funifier.engine.achievement.*
96
+ import com.funifier.engine.lottery.*
97
+ import com.funifier.engine.challenge.*
98
+ import com.funifier.engine.constant.*
99
+ import com.funifier.engine.guid.*
100
+ import com.funifier.engine.level.*
101
+ import com.funifier.engine.notify.*
102
+ import com.funifier.engine.player.*
103
+ import com.funifier.engine.point.*
104
+ import com.funifier.engine.team.*
105
+ import com.funifier.engine.catalog.*
106
+ import com.funifier.engine.mail.*
107
+ // Funifier utils
108
+ import com.funifier.engine.util.DateUtil
109
+ import com.funifier.engine.util.JsonUtil
110
+ import com.funifier.engine.util.HttpUtil
111
+ import com.funifier.engine.util.HttpClientFunifier
112
+ import com.funifier.engine.util.MustacheUtils
113
+ import com.funifier.controller.ManagerFactory
114
+
115
+ @TimedInterrupt(value = 5L, unit = TimeUnit.SECONDS)
116
+ class FunifierPublicEndpoint {
117
+ ManagerFactory manager = null;
118
+ void setManager(ManagerFactory c) { manager = c; }
119
+
120
+ // --- SEU SCRIPT É INSERIDO AQUI ---
121
+ }
122
+ ```
123
+
124
+ ### Regras para escrever scripts
125
+
126
+ 1. **NÃO use `import`** — todos os imports necessários já estão no wrapper. Se precisar de uma classe não importada, use o nome completo (fully qualified): `com.example.MinhaClasse`
127
+ 2. **O método principal é `public Object handle(Object payload)`** — este é o entry point
128
+ 3. **`manager` está disponível** como campo da classe (ManagerFactory)
129
+ 4. **Timeout padrão de 10 segundos** — o executor limita a execução. **Pode ser customizado** via campo `timeout` (em segundos) no objeto do endpoint — ver seção "Campo timeout" abaixo. Nota: o `@TimedInterrupt(5s)` no wrapper Groovy é uma segunda camada de proteção
130
+ 5. **Groovy, não Java puro** — o script roda em Groovy, então features como GString, closures e tipagem dinâmica funcionam
131
+ 6. **Cuidado com `$` em GStrings** — operadores MongoDB (`$set`, `$inc`) e strings com `$` são interpretados como variáveis Groovy. Use `String.valueOf((char)0x24)` para escapar:
132
+ ```groovy
133
+ def d = String.valueOf((char)0x24) // = "$"
134
+ def setCmd = '{"' + d + 'set": {"name": "Novo Nome"}}'
135
+ ```
136
+ 7. **Use apenas ASCII em comentários** — caracteres UTF-8 especiais (em dash `—`, acentos em comentários) podem causar erros de parse no Groovy
137
+ 8. **`instanceof` é bloqueado** pelo SecureASTCustomizer — use `getClass().getName()` ou try/catch
138
+ 9. **NÃO use `groovy.json.JsonSlurper`** para parsear JSON que será retornado na response — o `LazyMap` do JsonSlurper causa `ClassCastException: [B incompatible with [C` quando o Funifier tenta serializar. Use `JsonUtil.fromJsonToMap(jsonString)` em vez de `slurper.parseText(jsonString)`
139
+ 10. **Player.extra é público** — acesse direto com `player.extra`, não com `player.getExtra()` (método não existe)
140
+ 11. **Para salvar player:** `manager.getPlayerManager().findById(id)` → modifique → `manager.getPlayerManager().insert(player)` (upsert)
141
+
142
+ ### Campo `timeout` (Customizar timeout do script)
143
+
144
+ O timeout padrão de **10 segundos** (confirmado no source: `PublicManager.java` linha 255) pode ser insuficiente para endpoints que fazem chamadas HTTP externas (ex: verificar token OAuth, chamar APIs de IA, download de imagens). O campo `timeout` permite aumentar esse limite.
145
+
146
+ **Campo:** `timeout` (Long, em **segundos**)
147
+ **Padrão:** `null` (usa 10 segundos — source: `PublicManager.java:255`)
148
+ **Nota:** Este campo **não aparece no formulário do Studio** — só pode ser configurado via API.
149
+
150
+ ```bash
151
+ # Exemplo: definir timeout de 30 segundos para o endpoint google_login
152
+ curl -X POST "https://service2.funifier.com/v3/public" \
153
+ -H "Authorization: Basic <token>" \
154
+ -H "Content-Type: application/json" \
155
+ -d '{"_id": "google_login", "timeout": 30}'
156
+ ```
157
+
158
+ **Este campo também existe em Triggers e Schedulers** — mesma estrutura, mesma API (`/v3/trigger`, `/v3/scheduler`).
159
+
160
+ **Mecanismo de execução (Java):**
161
+ ```java
162
+ // O executor usa Executors.newSingleThreadExecutor() com Future.get(timeout, TimeUnit.SECONDS)
163
+ long timeout = 10; // padrão
164
+ if (endpoint.timeout != null && endpoint.timeout > 0) {
165
+ timeout = endpoint.timeout;
166
+ }
167
+ Object result = future.get(timeout, TimeUnit.SECONDS);
168
+ ```
169
+
170
+ **Nota:** O `@TimedInterrupt(value = 5L, unit = TimeUnit.SECONDS)` no wrapper Groovy é uma segunda camada de proteção. O timeout do executor (campo `timeout`) é a camada principal e deve ser >= ao TimedInterrupt para funcionar corretamente.
171
+
172
+ **Estrutura da classe PublicEndpoint (Java):**
173
+ ```java
174
+ public class PublicEndpoint {
175
+ public String _id; // slug (URL identifier)
176
+ public String apikey; // gamification apikey
177
+ public String title; // friendly name
178
+ public String description; // internal description
179
+ public boolean active; // enabled/disabled
180
+ public String method; // "POST" or "GET" (default: "POST")
181
+ public String script; // Groovy script body
182
+ public Long timeout; // timeout in SECONDS (null = default 10s)
183
+ public String sample; // example payload
184
+ public Date updated; // last update timestamp
185
+ public Map<String, Object> extra; // extra metadata
186
+ }
187
+ ```
188
+
189
+ **Quando usar timeout customizado:**
190
+ - Endpoints que fazem chamadas HTTP externas (OAuth, APIs externas)
191
+ - Endpoints com BCrypt hashing (custo computacional)
192
+ - Endpoints que fazem múltiplas operações em sequência
193
+ - **Recomendação:** 20-30s para endpoints com chamadas externas
194
+
195
+ ### Atualizar Public Endpoint via API
196
+
197
+ ```
198
+ POST /v3/public
199
+ Authorization: Basic <base64(apiKey + ":")>
200
+ Content-Type: application/json
201
+
202
+ {
203
+ "_id": "slug_do_endpoint",
204
+ "title": "Titulo",
205
+ "description": "Descricao",
206
+ "active": true,
207
+ "method": "POST",
208
+ "script": "public Object handle(Object payload) { ... }",
209
+ "sample": "{}",
210
+ "extra": {}
211
+ }
212
+ ```
213
+
214
+ O mesmo endpoint serve para criar e atualizar (upsert por `_id`)
215
+
216
+ ### Bibliotecas HTTP disponíveis
217
+
218
+ | Biblioteca | Uso recomendado |
219
+ |-----------|----------------|
220
+ | **Unirest** | HTTP client mais simples — `Unirest.post(url).header(...).body(...).asJson()` |
221
+ | **Apache HttpClient** | Alternativa mais verbosa |
222
+ | **groovyx.net.http** | HTTP Builder para Groovy |
223
+ | **HttpUtil / HttpClientFunifier** | Utilitários Funifier |
224
+
225
+ ### Exemplo usando Unirest
226
+
227
+ ```groovy
228
+ public Object handle(Object payload) {
229
+ def response = Unirest.post("https://api.external.com/endpoint")
230
+ .header("Content-Type", "application/json")
231
+ .header("Authorization", "Bearer my-token")
232
+ .body(JsonUtil.toJson(payload))
233
+ .asString()
234
+
235
+ def result = new groovy.json.JsonSlurper().parseText(response.getBody())
236
+ return result
237
+ }
238
+ ```
239
+
240
+ > ⚠️ **Triggers e Schedulers usam a mesma estrutura de wrapper** com os mesmos imports pré-disponíveis. A diferença é apenas o método principal: triggers usam `void trigger(event, entity, player, database)` e schedulers usam `void execute()`.
241
+
242
+ ## Boas Práticas
243
+
244
+ - Não expor dados sensíveis em endpoints públicos
245
+ - Não realizar operações críticas sem validação adicional
246
+ - Validar payload recebido no script Java
247
+
248
+ ## Validações e Testes
249
+
250
+ - [ ] Endpoint aparece na lista
251
+ - [ ] URL pública é acessível sem token
252
+ - [ ] Script processa payload corretamente
253
+ - [ ] Resposta é retornada conforme esperado