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.
- package/.cursor/rules/funifier.mdc +91 -0
- package/.github/copilot-instructions.md +83 -0
- package/AGENTS.md +97 -0
- package/README.md +247 -78
- package/datasource-funifier-docs/knowledge/guides/aggregates.md +152 -152
- package/datasource-funifier-docs/knowledge/guides/database-access.md +132 -132
- package/datasource-funifier-docs/knowledge/guides/java-entities.md +373 -373
- package/datasource-funifier-docs/knowledge/guides/java-libraries.md +330 -330
- package/datasource-funifier-docs/knowledge/guides/java-managers.md +509 -509
- package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +271 -271
- package/datasource-funifier-docs/knowledge/index.md +121 -121
- package/datasource-funifier-docs/knowledge/modules/achievement.md +46 -46
- package/datasource-funifier-docs/knowledge/modules/action-log.md +88 -88
- package/datasource-funifier-docs/knowledge/modules/action.md +80 -80
- package/datasource-funifier-docs/knowledge/modules/auth.md +104 -104
- package/datasource-funifier-docs/knowledge/modules/avatar.md +28 -28
- package/datasource-funifier-docs/knowledge/modules/backup.md +40 -40
- package/datasource-funifier-docs/knowledge/modules/challenge.md +91 -91
- package/datasource-funifier-docs/knowledge/modules/compact.md +40 -40
- package/datasource-funifier-docs/knowledge/modules/competition.md +149 -149
- package/datasource-funifier-docs/knowledge/modules/crossword.md +41 -41
- package/datasource-funifier-docs/knowledge/modules/csv-data.md +30 -30
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +53 -53
- package/datasource-funifier-docs/knowledge/modules/database.md +241 -241
- package/datasource-funifier-docs/knowledge/modules/folder.md +111 -111
- package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +23 -23
- package/datasource-funifier-docs/knowledge/modules/lastmile.md +45 -45
- package/datasource-funifier-docs/knowledge/modules/leaderboard.md +98 -98
- package/datasource-funifier-docs/knowledge/modules/level.md +83 -83
- package/datasource-funifier-docs/knowledge/modules/lottery.md +112 -112
- package/datasource-funifier-docs/knowledge/modules/marketplace.md +27 -27
- package/datasource-funifier-docs/knowledge/modules/mystery.md +82 -82
- package/datasource-funifier-docs/knowledge/modules/notification.md +40 -40
- package/datasource-funifier-docs/knowledge/modules/patterns.md +1096 -1096
- package/datasource-funifier-docs/knowledge/modules/player.md +101 -101
- package/datasource-funifier-docs/knowledge/modules/point.md +67 -67
- package/datasource-funifier-docs/knowledge/modules/public.md +253 -253
- package/datasource-funifier-docs/knowledge/modules/question.md +136 -136
- package/datasource-funifier-docs/knowledge/modules/quiz.md +163 -163
- package/datasource-funifier-docs/knowledge/modules/scheduler.md +58 -58
- package/datasource-funifier-docs/knowledge/modules/security.md +169 -169
- package/datasource-funifier-docs/knowledge/modules/staging.md +28 -28
- package/datasource-funifier-docs/knowledge/modules/static-repo.md +41 -41
- package/datasource-funifier-docs/knowledge/modules/story.md +42 -42
- package/datasource-funifier-docs/knowledge/modules/studio-page.md +180 -180
- package/datasource-funifier-docs/knowledge/modules/swap.md +132 -132
- package/datasource-funifier-docs/knowledge/modules/team.md +75 -75
- package/datasource-funifier-docs/knowledge/modules/trigger.md +189 -189
- package/datasource-funifier-docs/knowledge/modules/upload.md +155 -155
- package/datasource-funifier-docs/knowledge/modules/virtual-good.md +99 -99
- package/datasource-funifier-docs/knowledge/modules/webhook.md +41 -41
- package/datasource-funifier-docs/knowledge/modules/websocket.md +41 -41
- package/datasource-funifier-docs/knowledge/modules/widget.md +42 -42
- package/datasource-funifier-docs/process-gtm-saas.md +143 -143
- package/datasource-funifier-docs/process-instagram.md +88 -88
- package/datasource-funifier-docs/process.md +1826 -1826
- package/datasource-funifier-docs/readme.md +132 -132
- package/dist/cli/config-writers.js +11 -11
- package/dist/mcp/bundle.js +55 -52
- package/package.json +70 -67
- package/skills/funifier-create-aggregate/SKILL.md +126 -126
- package/skills/funifier-create-custom-page/SKILL.md +126 -126
- package/skills/funifier-create-scheduler/SKILL.md +126 -126
- 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
|